Skip to content

Conversation

@sharkdp
Copy link
Contributor

@sharkdp sharkdp commented Oct 20, 2025

Summary

Fall back to C[Divergent] if we are trying to specialize C[T] with a type that itself already contains deeply nested specialized generic classes. This is a way to prevent infinite recursion for cases like self.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.

@sharkdp sharkdp added the ty Multi-file analysis & type inference label Oct 20, 2025
@sharkdp sharkdp closed this Oct 20, 2025
@sharkdp sharkdp reopened this Oct 20, 2025
specialization = specialization.with_replaced_type(
db,
idx,
Type::divergent(self.body_scope(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.

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?

Copy link
Contributor

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.

https://github.com/astral-sh/ruff/pull/17371/files#diff-20b910c6e20faa962bb1642e111db1cbad8e66ace089bdd966ac9d7f9fa99ff2R649-R665

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@mtshiba mtshiba Oct 22, 2025

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.

cc (as author of #20333, etc.): @carljm

Copy link
Contributor Author

@sharkdp sharkdp Oct 22, 2025

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

Copy link
Contributor

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.

Copy link
Contributor Author

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!

@sharkdp sharkdp marked this pull request as ready for review October 20, 2025 13:07
@sharkdp sharkdp closed this Oct 20, 2025
@sharkdp sharkdp reopened this Oct 20, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 20, 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 Oct 20, 2025

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

@github-actions
Copy link
Contributor

github-actions bot commented Oct 21, 2025

ecosystem-analyzer results

No diagnostic changes detected ✅
Full report with detailed diff (timing results)

@sharkdp sharkdp marked this pull request as ready for review October 22, 2025 11:27
Copy link
Member

@AlexWaygood AlexWaygood 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

@sharkdp sharkdp merged commit 58a68f1 into main Oct 22, 2025
41 checks passed
@sharkdp sharkdp deleted the david/fix-1383 branch October 22, 2025 12:29
dcreager added a commit that referenced this pull request Oct 22, 2025
* 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)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Too many cycle iterations for infinitely wrapped instance of generic class Panic when building divergent tuple type for implicit instance attributes

5 participants