-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Fall back to Divergent for deeply nested specializations
#20988
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
Conversation
79c8f3f to
7aadfc2
Compare
| specialization = specialization.with_replaced_type( | ||
| db, | ||
| idx, | ||
| Type::divergent(self.body_scope(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.
I haven't understood the scope parameter of Type::Divergent. When we see an infinitely wrapped list[list[list[…]]] in a method of class C, is it still correct to set the scope parameter to the body scope of list (not of C or the method) here?
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.
Your use of Divergent in this PR seems to differ slightly from the original intended use. The intended use is to make functions with the potential for divergent type inference tracked, and to set the initial cycle value to Divergent instead of Never. If the resulting type contains Divergent, replace that type with Divergent itself to converge the inference.
When I first introduced Divergent in #20312, I included scope to prevent Divergent from propagating to other types of inference. This was based on the use case in #17371.
e3b896d#diff-fdebb876f3c3f16730728018e4b1223f844d9493cd7caa7ee94e4a7a7b02184f
call_divergent calls divergent, but it does not diverge itself. Knowing where the divergence occurs can help prevent excessive type replacement.
#20333 and #20359 may also be helpful in understanding Divergent.
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 currently don't see how that approach scales. We would need to add dedicated "turn this type into Divergent if it contains Divergent" logic everywhere. We can do this for tuples, but we also need to do it for sets, lists, and dicts. And it doesn't really stop with those syntactical constructs. What about something like
from typing import TypeVar, Literal
T = TypeVar("T")
def make_tuple(a: T) -> tuple[T, Literal[1]]:
return (a, 1)
class D:
def f(self, other: "D"):
self.x = make_tuple(other.x)
reveal_type(D().x)Do we also specialize all call expressions to detect nested Divergent types? At that point, we could probably add it to infer_expression_type (and in fact, that might work, but done naively, we would get Divergent instead of tuple[Divergent, …] etc).
What I'm proposing here is certainly not very elegant, I agree. But it does solve all of these cases (and more) without any special casing to certain syntactical forms.
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 don't actually think it's necessary to apply this logic everywhere. That's work in progress in #20566.
The only place where this logic needs to be applied is within tracked functions that may diverge (e.g. implicit_attribute_inner, infer_expression_type_impl). That means that a Divergent-type suppression needs to be performed by the end of a cycle, and it would be most natural to do this at the end of the tracked function. Other places, such as infer_tuple_expression, don't actually need this logic. In fact, it's been removed in https://github.com/astral-sh/ruff/pull/20566/files#diff-5ade3020ce233d81ee50c1ec2debd673afc8ba08ede8ec664ca62e62aefbb2f7L5273-L5279, https://github.com/astral-sh/ruff/pull/20566/files#diff-411da1a5c9e0560d6b43091ca27247357a1e94a9077f9adb2cd6a23f495578b6L24-L28.
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 had completely missed #20566 — thank you for the reference. It looks like that still requires adding divergent-handling in lots of places, but I'm certainly not opposed to that. I would very much like there to be a more principled solution.
My problem is that I want something that fixes these panics now (to finally merge the type-of-self PR). I rolled back most of the changes here to leave the CycleRecovery setup intact and to be less invasive. I am proposing that we merge this PR to solve the immediate problem, and I'll be happy to remove all of this once we have a better solution (that still solves all of the test cases that are introduced in this PR).
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.
Yes, as I said here, I'm in favor of provisionally merging the PR anyway; the lack of self types means that a lot of recursive type inference is hidden by dynamic types.
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.
Thank you for chiming in here, very much appreciated!
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
7aadfc2 to
b2cb8bf
Compare
|
b2cb8bf to
f1dd038
Compare
f1dd038 to
36c628f
Compare
36c628f to
9d6cce1
Compare
5cabd61 to
d3d90fb
Compare
d3d90fb to
0141269
Compare
AlexWaygood
left a comment
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.
Thank you
c4e30fc to
438706a
Compare
* main: (65 commits) [ty] Some more simplifications when rendering constraint sets (#21009) [ty] Make `attributes.md` mdtests faster (#21030) [ty] Set `INSTA_FORCE_PASS` and `INSTA_OUTPUT` environment variables from mdtest.py (#21029) [ty] Fall back to `Divergent` for deeply nested specializations (#20988) [`ruff`] Autogenerate TypeParam nodes (#21028) [ty] Add assertions to ensure that we never call `KnownClass::Tuple.to_instance()` or similar (#21027) [`ruff`] Auto generate ast Pattern nodes (#21024) [`flake8-simplify`] Skip `SIM911` when unknown arguments are present (#20697) Render a diagnostic for syntax errors introduced in formatter tests (#21021) [ty] Support goto-definition on vendored typeshed stubs (#21020) [ty] Implement go-to for binary and unary operators (#21001) [ty] Avoid ever-growing default types (#20991) [syntax-errors] Name is parameter and global (#20426) [ty] Disable panicking mdtest (#21016) [ty] Fix completions at end of file (#20993) [ty] Fix out-of-order semantic token for function with regular argument after kwargs (#21013) [ty] Fix auto import for files with `from __future__` import (#20987) [`fastapi`] Handle ellipsis defaults in FAST002 autofix (`FAST002`) (#20810) [`ruff`] Skip autofix for keyword and `__debug__` path params (#20960) [`flake8-bugbear`] Skip `B905` and `B912` if <2 iterables and no starred arguments (#20998) ...
Summary
Fall back to
C[Divergent]if we are trying to specializeC[T]with a type that itself already contains deeply nested specialized generic classes. This is a way to prevent infinite recursion for cases likeself.x = [self.x]where type inference for the implicit instance attribute would not converge.closes astral-sh/ty#1383
closes astral-sh/ty#837
Test Plan
Regression tests.