Skip to content

Conversation

@carljm
Copy link
Contributor

@carljm carljm commented Sep 12, 2025

Summary

Use Type::Divergent to short-circuit diverging types in type expressions. This avoids panicking in a wide variety of cases of recursive type expressions.

Avoids many panics (but not yet all -- I'll be tracking down the rest) from astral-sh/ty#256 by falling back to Divergent. For many of these recursive type aliases, we'd like to support them properly (i.e. really understand the recursive nature of the type, not just fall back to Divergent) but that will be future work.

This switches Type::has_divergent_type from using any_over_type to a custom set of visit methods, because any_over_type visits more than we need to visit, and exercises some lazy attributes of type, causing significantly more work. This change means this diff doesn't regress perf; it even reclaims some of the perf regression from #20333.

Test Plan

Added mdtest for recursive type alias that panics on main.

Verified that we can now type-check packaging (and projects depending on it) without panic; this will allow moving a number of mypy-primer projects from bad.txt to good.txt in a subsequent PR.

@carljm carljm added the ty Multi-file analysis & type inference label Sep 12, 2025
@carljm carljm force-pushed the cjm/divergent-generic branch from 7c3fc35 to c18fe17 Compare September 12, 2025 01:43
@github-actions
Copy link
Contributor

github-actions bot commented Sep 12, 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 Sep 12, 2025

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

@codspeed-hq
Copy link

codspeed-hq bot commented Sep 12, 2025

CodSpeed WallTime Performance Report

Merging #20359 will improve performances by 5.89%

Comparing cjm/divergent-generic (814c42d) with main (c3f2187)

Summary

⚡ 1 improvement
✅ 7 untouched

Benchmarks breakdown

Benchmark BASE HEAD Change
small[tanjun] 1.7 s 1.6 s +5.89%

@carljm carljm marked this pull request as ready for review September 12, 2025 02:01
@carljm
Copy link
Contributor Author

carljm commented Sep 12, 2025

@mtshiba I can't request you as reviewer, but would be happy for you to take a look at this PR.

@AlexWaygood
Copy link
Member

For many of these recursive type aliases, we'd like to support them properly (i.e. really understand the recursive nature of the type, not just fall back to Divergent) but that will be future work.

Will it still be easy to identify type aliases in user code that we don't properly support, if they no longer cause us to panic? The panics are obviously really bad UX, but the nice thing about them is it makes it very easy for us to spot where we have bugs

@carljm
Copy link
Contributor Author

carljm commented Sep 12, 2025

For many of these recursive type aliases, we'd like to support them properly (i.e. really understand the recursive nature of the type, not just fall back to Divergent) but that will be future work.

Will it still be easy to identify type aliases in user code that we don't properly support, if they no longer cause us to panic? The panics are obviously really bad UX, but the nice thing about them is it makes it very easy for us to spot where we have bugs

I agree this is a potential downside, but a) I think it's outweighed by the value of eliminating the cycles ASAP, and b) I'm not that worried about it. We already know the core recursive-alias cases that we need to support, and if there are a few edge-case bugs hiding, if they matter to anyone we'll discover them.

It's also quite easy to temporarily remove the Type::Divergent fallback, if we want to do that and run it on ecosystem projects as a bug-finding tool.

@AlexWaygood
Copy link
Member

Sounds good!

@carljm carljm force-pushed the cjm/divergent-generic branch 2 times, most recently from 934b62e to aca2a4e Compare September 12, 2025 20:40
Copy link
Contributor

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

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

Thank you very much!

Very much looking forward to fewer panics related to these.

Type::Dynamic(DynamicType::Divergent(_)) => ty == div,
_ => false,
})
self.has_divergent_type_impl(db, div, &HasDivergentTypeVisitor::new(false))
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you thought about implementing has_divergent_type using a custom TypeVisitor (similar to what any_over_type does), that would overwrite selected methods like visit_bound_type_var_type in order to skip visiting the parts that this needs/wants to skip?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the prompt. I tried this but was slightly unhappy with how many types the visitor needed to acquire detailed knowledge of the internals of in order to correctly implement visiting types without visiting lazily-inferred type attributes. So I ended up creating a general visitor flag for this, so that each type's walk_ function can implement this distinction internally.

@marcelroed
Copy link

marcelroed commented Sep 15, 2025

Here's an unhandled cycle using the latest commit:

from typing import Generic, TypeVar

AT = TypeVar("AT", bound="A")
BT = TypeVar("BT", bound="B")

class A(Generic[BT]):
    ...

class B(Generic[AT]):
    ...

This is from an ML modeling library where type A (model configs) need to be able to construct type B (model instances) and type B (model instances) need to be able to return type A (model configs). Feel free to delete/hide this comment if it should be a part of a separate issue instead.

@carljm
Copy link
Contributor Author

carljm commented Sep 15, 2025

@marcelroed Yes, thanks -- I'm aware this PR doesn't fix all the cases in astral-sh/ty#256. Your case is already mentioned in astral-sh/ty#256 (comment), so it will be addressed before we close that issue.

@carljm carljm force-pushed the cjm/divergent-generic branch from aca2a4e to e93a45f Compare September 15, 2025 20:18
@carljm carljm force-pushed the cjm/divergent-generic branch from c8b4c85 to 814c42d Compare September 16, 2025 23:29
@carljm carljm merged commit d121a76 into main Sep 16, 2025
38 checks passed
@carljm carljm deleted the cjm/divergent-generic branch September 16, 2025 23:44
sharkdp pushed a commit that referenced this pull request Sep 17, 2025
## Summary

After #20359 we can move all but
three remaining projects over to `good.txt`.

## Test Plan

CI
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.

6 participants