-
Notifications
You must be signed in to change notification settings - Fork 208
fix: Generic ParamSpec for constructors #1660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit fixes issue facebook#43 where generic class constructors don't preserve type parameters when used with ParamSpec functions. Changes: 1. Modified CallTarget::Class to use TargetWithTParams in call.rs, allowing generic type parameters to be preserved through the call target system 2. Updated Type::Forall handling in call.rs to also set tparams for Class targets 3. Improved subset checking in subset.rs to instantiate fresh type variables for generic class constructors, allowing the variables to be properly unified during ParamSpec inference instead of being finalized to Unknown 4. Updated call_graph.rs to handle the new CallTarget::Class structure 5. Added helper methods to type_order.rs for accessing class tparams and instantiating fresh class instances The key fix is in subset.rs where we now call instantiate_fresh_class() to create fresh solver variables for the generic class's type parameters. These variables remain as solver variables (represented as @_) that can be unified during callable inference, instead of being immediately finalized to Unknown. Test result: Generic class constructors now preserve type parameters as @_ (type variables) instead of Unknown, allowing ParamSpec inference to work correctly.
This reverts commit dedeeb2.
Run cargo fmt to ensure consistent code formatting across the project.
|
Thanks! This change looks good, though I don't think this completely handles #43, per sam's comment here: #43 (comment) |
|
@yangdanny97 has imported this pull request. If you are a Meta employee, you can view this in D87784439. |
|
Hmm, one issue I found is that the type variable is not being solved by the call arguments. In the test case you added, I see that |
|
I think the solution for #43 probably requires making a new Forallable type for ParamSpecValue and possibly also Concatenate That would allow passing the type params when we solve a ParamSpec, which would enable us to properly synthesize a generic function type later. |
I wonder if we might be able to do something similar to #1650; the fix there is specific to decorators, but we might be able to extend it. |
When a generic function or constructor is passed through a ParamSpec-using function, preserve its generic nature so each call creates fresh type variables rather than locking types on first call. - Add ParamSpecValue and Concatenate variants to Forallable enum - Detect generic params in create_paramspec_value() and wrap in Forall - Lift Forall wrapper to outer Callable in simplify_mut() - Handle Forall<ParamSpecValue> in callable.rs to instantiate fresh vars - Use transform_mut instead of visit_mut to recurse into ClassType's TArgs
Summary
For #43
This fixes a type checking bug where generic class constructors would lose their type parameters when passed through ParamSpec functions. The type checker now correctly preserves type variables (
@_) instead of finalizing them toUnknown, allowing proper type inference.The Issue
When passing a generic class constructor through a function using ParamSpec, the type parameters were incorrectly finalized to
Unknown. For example:At runtime, generic class constructors should preserve their type parameters for inference. However, Pyrefly's subset checking in
subset.rswasn't instantiating fresh type variables for generic classes, causing them to be finalized toUnknownduring ParamSpec inference.The Fix
I made several changes to handle generic class constructors properly:
CallTarget::Classin pyrefly/lib/alt/call.rs:16 to useTargetWithTParams:This ensures generic type parameters remain as solver variables (
@_) that can be unified during inference, rather than being prematurely finalized toUnknown.Test Plan
Updated the existing test case
test_param_spec_generic_constructorin pyrefly/lib/test/paramspec.rs:bug = "Generic class constructors don't work with ParamSpec"marker# E: revealed type: (x: Unknown) -> C[Unknown]to# E: revealed type: (x: @_) -> C[@_]x: C[int] = c2(1)to ensure type inference worksVerification:
cargo test test_param_spec_generic_constructorResult: passes
cargo test paramspecResult: passes