[ty] Normalize recursive protocol growth during cycle recovery#26246
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 94.37%. The percentage of expected errors that received a diagnostic held steady at 89.00%. The number of fully passing files held steady at 94/134. |
Memory usage reportMemory usage unchanged ✅ |
|
| current.to_nominal_instance()?, | ||
| previous.to_nominal_instance()?, |
There was a problem hiding this comment.
I think it's somewhat subtle here that we unwrap to nominal instance types for the normalization, but then the normalization (if it occurs) reconstructs the type with Type::instance(...), which will recognize a protocol type. Might be worth a comment to explain what's going on here?
| /// Normalizes nominal growth that wraps the previous cycle result in one or more | ||
| /// specializations, either directly or beneath an unambiguous union wrapper. | ||
| /// | ||
| /// For example, an inference cycle can otherwise grow indefinitely as | ||
| /// `C[int]`, `C[C[int]]`, `C[C[C[int]]]`, and so on. Once fixed-point iteration has passed its | ||
| /// tainted cycles, replace the recursive type argument with the cycle's `Divergent` marker so | ||
| /// that the existing recursive-type normalization can converge on `C[Divergent]`. |
There was a problem hiding this comment.
This docstring should probably mention the behavior with class-backed protocol types?
|
|
||
| ```py | ||
| while 1: | ||
| x = iter([[None] + [x]]) # error: [possibly-unresolved-reference] |
There was a problem hiding this comment.
A reveal_type(x) here showing Iterator[Divergent] wouldn't hurt.
d694fd2 to
db6ed39
Compare
## Summary This reverts #26163 and its follow-up changes in #26230, #26246, #26274, #26275, and #26316. The recursive nominal-growth recovery replaces a previous cycle result nested inside a specialization with `Divergent`. The follow-up regressions show that this local replacement is not structurally safe: multi-arm union recovery can lose stable arms, and variadic tuple growth can continue after replacement. Addressing those cases required increasingly shape-specific alignment and guards without a general way to establish correspondence between fixed-point iterations. This restores the cycle-recovery behavior from before #26163. The direct self-referential `TypeOf` case and the regression coverage for astral-sh/ty#3835 and astral-sh/ty#3838 continue to pass without the heuristic and are retained. We remove only the cases that no longer converge: `TypeOf` references nested in nominal specializations and the nested-iterable case from astral-sh/ty#3827. The independent `Divergent` constraint fixes from #26288 and #26334, including the regression coverage for astral-sh/ty#3836, are also retained.
Summary
A loop-carried value can grow through a protocol specialization on every fixed-point iteration:
Prior to this change, we repeatedly inferred a deeper
Iterator[...]specialization. The existing recursive-growth recovery only recognizedNominalInstance, whileiterreturns a class-backedProtocolInstance, so inference never converged.This converts class-backed protocol instances to their nominal representation when comparing cycle iterations, allowing the existing wrapper replacement and
Divergentnormalization to converge. Synthesized protocols remain excluded from this path.Closes astral-sh/ty#3827.