Dedup tparam-default substitution between alt and solver layers - #4406
Dedup tparam-default substitution between alt and solver layers#4406nitishagar wants to merge 1 commit into
Conversation
|
This pull request has been imported. If you are a Meta employee, you can view this in D114523923. (Because this pull request was imported automatically, there will not be any future comments.) |
This comment has been minimized.
This comment has been minimized.
The same TypeVar/TypeVarTuple/ParamSpec/Quantified match-and-substitute idiom was duplicated between `get_tparam_default` (alt/class/targs.rs) and an inline copy in `finish_class_targs` (solver/solver.rs), the latter flagged by a `// TODO: deal with code duplication` marker. Extract a single `substitute_tparam_defaults` helper in `pyrefly_types::quantified` (next to the related `as_gradual_type_helper` that already shares this idiom). The helper takes the lookup and fallback by parameter, so each caller keeps its own lookup source and fallback — those intentionally differ: `get_tparam_default` reads finalized `checked_targs` and falls back to `mk_any_implicit()` (the call path already reported the out-of-scope reference as an error), while `finish_class_targs` reads in-progress `new_targs` and falls back to `param.as_gradual_type()` (the class-finalization path has no such error). Pure behavior-preserving relocation; the existing test suite is the regression oracle. Closes facebook#4298.
46c8136 to
6e09c5f
Compare
rchen152
left a comment
There was a problem hiding this comment.
I'm not convinced this is an improvement. Helpers add conceptual overhead, and callbacks add implementation complexity, whereas the original code is simple and clear, and the duplication is small.
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
|
Thanks for taking the time. A couple of thoughts on the trade-off, happy to drop this if it's not convincing:
That said, I take the point on the |
Summary
The logic for substituting references to earlier type parameters inside a tparam's
defaultwas duplicated in two places:get_tparam_defaultinpyrefly/lib/alt/class/targs.rs(used during call/specialization)finish_class_targsinpyrefly/lib/solver/solver.rs(used during class-level finalization), flagged by a// TODO: deal with code duplication in get_tparam_defaultmarker.The two copies differed only in their lookup source and their fallback value for out-of-scope references — differences that are intentional, since one path runs where an out-of-scope reference is an already-reported error (returns
Any(Implicit)) and the other runs during finalization with no such error (returns the param's gradual type).This PR extracts a shared
substitute_tparam_defaultshelper intopyrefly_types::quantified(next to the existingas_gradual_type_helper, which already uses the sameTypeVar/TypeVarTuple/ParamSpec/Quantifiedmatch idiom). The helper takes the default by value, a&dyn Fn(&Name) -> Option<Type>lookup closure, and a caller-chosen fallbackType, so each call site keeps its own lookup source and fallback semantics. Both callers are rewritten to delegate to it, and the// TODOmarker is removed.Behavior
This is a behavior-preserving refactor. Each call site passes the same lookup and fallback it used inline, so the produced types are identical for every input:
get_tparam_defaultpassesname_to_idx->checked_targslookup withself.heap.mk_any_implicit()fallback (unchanged).finish_class_targspassesseen_params->new_targs-then-targslookup withparam.as_gradual_type()fallback (unchanged).The two fallbacks are deliberately not collapsed — they intentionally differ (error path vs finalization path).
Test plan
cargo build -p pyrefly_types— cleancargo build -p pyrefly— cleancargo test -p pyrefly --lib— 7675 passed, 0 failed, 3 ignored (the full typechecker regression suite; zero behavior diff)python3 test.py --no-test --no-tensor-shapes --no-conformance --no-jsonschema— clean for the touched filesFixes #4298