Skip to content

Conversation

@JelleZijlstra
Copy link
Contributor

@JelleZijlstra JelleZijlstra commented Aug 25, 2025

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.

@github-actions
Copy link
Contributor

github-actions bot commented Aug 25, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@github-actions
Copy link
Contributor

github-actions bot commented Aug 25, 2025

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

@JelleZijlstra

This comment was marked as resolved.

@JelleZijlstra

This comment was marked as resolved.

@JelleZijlstra

This comment was marked as resolved.

@JelleZijlstra

This comment was marked as resolved.

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Aug 25, 2025
@JelleZijlstra JelleZijlstra marked this pull request as ready for review August 25, 2025 14:31
@JelleZijlstra JelleZijlstra marked this pull request as draft August 25, 2025 14:31
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
let applied_specialization_type = type_mapping.get_specialization_type(db);
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@carljm carljm Aug 27, 2025

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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.

Copy link
Contributor

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.

@JelleZijlstra JelleZijlstra marked this pull request as ready for review August 26, 2025 02:27
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic!

@carljm carljm merged commit 18eaa65 into astral-sh:main Aug 28, 2025
37 checks passed
dcreager added a commit that referenced this pull request Aug 28, 2025
* 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)
carljm pushed a commit that referenced this pull request Aug 28, 2025
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) .
second-ed pushed a commit to second-ed/ruff that referenced this pull request Sep 9, 2025
… 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>
second-ed pushed a commit to second-ed/ruff that referenced this pull request Sep 9, 2025
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) .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants