-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Introduce a representation for the top/bottom materialization of an invariant generic #20076
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
[ty] Introduce a representation for the top/bottom materialization of an invariant generic #20076
Conversation
…ion_of() function Part of astral-sh#994
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
| type_mapping: &TypeMapping<'a, 'db>, | ||
| visitor: &ApplyTypeMappingVisitor<'db>, | ||
| ) -> Self { | ||
| let applied_specialization_type = type_mapping.get_specialization_type(db); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels not great and I tried to implement it in apply_specialization instead, but couldn't get that to work for a reason I don't fully understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect the reason it didn't work is because apply_specialization is not called recursively, only apply_type_mapping_impl is? I think this would be difficult to change, so I suspect this is the way it needs to be done.
Maybe this would feel clearer if instead there were a top-level conditional here "are we applying a specialization?" (or it could be "are we applying a specialization with a materialization?" which I think is less conceptually clear but practically equivalent), and then bifurcate into the previous simple logic (if not) or the more complex logic accounting for specializations if so. (The latter could even be extracted into its own method.) This would duplicate the core "map over types" -- not really sure if it would feel like an improvement without trying it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I tried was this:
--- a/crates/ty_python_semantic/src/types.rs
+++ b/crates/ty_python_semantic/src/types.rs
@@ -5971,7 +5971,11 @@ impl<'db> Type<'db> {
db: &'db dyn Db,
specialization: Specialization<'db>,
) -> Type<'db> {
- self.apply_type_mapping(db, &TypeMapping::Specialization(specialization))
+ let new_specialization = self.apply_type_mapping(db, &TypeMapping::Specialization(specialization));
+ match specialization.specialization_type(db) {
+ None => new_specialization,
+ Some(materialization_type) => new_specialization.materialize(db, materialization_type)
+ }
}
fn apply_type_mapping<'a>(
Which should apply recursively since materialize is recursive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, interesting. Yes, I see how that would be a lot nicer if it works; would eliminate the significant redundancy we currently have between apply_type_mapping_impl and materialize implementations.
It's not immediately clear to me why that wouldn't work. I think it's worth putting a TODO here to investigate this further, but it doesn't have to block this PR. What was the failure symptom? Was it widespread or just one or two specific cases?
If you have the failing version and want to push it up as a draft stacked PR, I can take a look and see if anything is apparent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has something to do with how bases are specialized. Given this test code:
def _(y: Top[InvariantChild[Any]]) -> None:
reveal_type(y.__mro__)In the working branch, this reveals tuple[<class 'Top[InvariantChild[Any]]'>, <class 'CovariantBase[object]'>, typing.Generic, <class 'object'>], which is expected: the covariant base class materializes the typevar to object.
In the non-working branch, this reveals tuple[<class 'Top[InvariantChild[Any]]'>, <class 'CovariantBase[object]'>, typing.Generic, <class 'object'>, <class 'CovariantBase[Any]'>] -- for some reason we have both CovariantBase[object] and CovariantBase[Any] still in the MRO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm out of time to look into this for now; I think we should go ahead and land this with a TODO comment. I'll add the comment and then merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic!
* main: Fix mdtest ignore python code blocks (#20139) [ty] add support for cyclic legacy generic protocols (#20125) [ty] add cycle detection for find_legacy_typevars (#20124) Use new diff rendering format in tests (#20101) [ty] Fix 'too many cycle iterations' for unions of literals (#20137) [ty] No boundness analysis for implicit instance attributes (#20128) Bump 0.12.11 (#20136) [ty] Benchmarks for problematic implicit instance attributes cases (#20133) [`pyflakes`] Fix `allowed-unused-imports` matching for top-level modules (`F401`) (#20115) Move GitLab output rendering to `ruff_db` (#20117) [ty] Evaluate reachability of non-definitely-bound to Ambiguous (#19579) [ty] Introduce a representation for the top/bottom materialization of an invariant generic (#20076) [`flake8-async`] Implement `blocking-http-call-httpx` (`ASYNC212`) (#20091) [ty] print diagnostics with fully qualified name to disambiguate some cases (#19850) [`ruff`] Preserve relative whitespace in multi-line expressions (`RUF033`) (#19647)
This is a variant of #20076 that moves some complexity out of `apply_type_mapping_impl` in `generics.rs`. The tradeoff is that now every place that applies `TypeMapping::Specialization` must take care to call `.materialize()` afterwards. (A previous version of this didn't work because I had missed a spot where I had to call `.materialize()`.) @carljm as asked in #20076 (comment) .
… an invariant generic (astral-sh#20076) Part of astral-sh#994. This adds a new field to the Specialization struct to record when we're dealing with the top or bottom materialization of an invariant generic. It also implements subtyping and assignability for these objects. Next planned steps after this is done are to implement other operations on top/bottom materializations; probably attribute access is an important one. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This is a variant of astral-sh#20076 that moves some complexity out of `apply_type_mapping_impl` in `generics.rs`. The tradeoff is that now every place that applies `TypeMapping::Specialization` must take care to call `.materialize()` afterwards. (A previous version of this didn't work because I had missed a spot where I had to call `.materialize()`.) @carljm as asked in astral-sh#20076 (comment) .
Part of #994. This adds a new field to the Specialization struct to record when we're dealing with the top or bottom materialization of an invariant generic. It also implements subtyping and assignability for these objects.
Next planned steps after this is done are to implement other operations on top/bottom materializations; probably attribute access is an important one.