[ty] Normalize recursive types using Any (#19003)

## Summary

This just replaces one temporary solution to recursive protocols (the
`SelfReference` mechanism) with another one (track seen types when
recursively descending in `normalize` and replace recursive references
with `Any`). But this temporary solution can handle mutually-recursive
types, not just self-referential ones, and it's sufficient for the
primer ecosystem and some other projects we are testing on to no longer
stack overflow.

The follow-up here will be to properly handle these self-references
instead of replacing them with `Any`.

We will also eventually need cycle detection on more recursive-descent
type transformations and tests.

## Test Plan

Existing tests (including recursive-protocol tests) and primer.

Added mdtest for mutually-recursive protocols that stack-overflowed
before this PR.
This commit is contained in:
Carl Meyer 2025-06-30 12:07:57 -07:00 committed by GitHub
parent 34052a1185
commit 2ae0bd9464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 356 additions and 368 deletions

View file

@ -1729,6 +1729,21 @@ def _(r: Recursive):
reveal_type(r.method(r).callable1(1).direct.t[1][1]) # revealed: Recursive
```
### Mutually-recursive protocols
```py
from typing import Protocol
from ty_extensions import is_equivalent_to, static_assert
class Foo(Protocol):
x: "Bar"
class Bar(Protocol):
x: Foo
static_assert(is_equivalent_to(Foo, Bar))
```
### Regression test: narrowing with self-referential protocols
This snippet caused us to panic on an early version of the implementation for protocols.