mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 12:05:57 +00:00 
			
		
		
		
	[ty] Fall back to Divergent for deeply nested specializations (#20988)
				
					
				
			## 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 https://github.com/astral-sh/ty/issues/1383 closes https://github.com/astral-sh/ty/issues/837 ## Test Plan Regression tests.
This commit is contained in:
		
							parent
							
								
									2c9433796a
								
							
						
					
					
						commit
						58a68f1bbd
					
				
					 10 changed files with 317 additions and 26 deletions
				
			
		|  | @ -2457,6 +2457,48 @@ class Counter: | |||
| reveal_type(Counter().count)  # revealed: Unknown | int | ||||
| ``` | ||||
| 
 | ||||
| We also handle infinitely nested generics: | ||||
| 
 | ||||
| ```py | ||||
| class NestedLists: | ||||
|     def __init__(self: "NestedLists"): | ||||
|         self.x = 1 | ||||
| 
 | ||||
|     def f(self: "NestedLists"): | ||||
|         self.x = [self.x] | ||||
| 
 | ||||
| reveal_type(NestedLists().x)  # revealed: Unknown | Literal[1] | list[Divergent] | ||||
| 
 | ||||
| class NestedMixed: | ||||
|     def f(self: "NestedMixed"): | ||||
|         self.x = [self.x] | ||||
| 
 | ||||
|     def g(self: "NestedMixed"): | ||||
|         self.x = {self.x} | ||||
| 
 | ||||
|     def h(self: "NestedMixed"): | ||||
|         self.x = {"a": self.x} | ||||
| 
 | ||||
| reveal_type(NestedMixed().x)  # revealed: Unknown | list[Divergent] | set[Divergent] | dict[Unknown | str, Divergent] | ||||
| ``` | ||||
| 
 | ||||
| And cases where the types originate from annotations: | ||||
| 
 | ||||
| ```py | ||||
| from typing import TypeVar | ||||
| 
 | ||||
| T = TypeVar("T") | ||||
| 
 | ||||
| def make_list(value: T) -> list[T]: | ||||
|     return [value] | ||||
| 
 | ||||
| class NestedLists2: | ||||
|     def f(self: "NestedLists2"): | ||||
|         self.x = make_list(self.x) | ||||
| 
 | ||||
| reveal_type(NestedLists2().x)  # revealed: Unknown | list[Divergent] | ||||
| ``` | ||||
| 
 | ||||
| ### Builtin types attributes | ||||
| 
 | ||||
| This test can probably be removed eventually, but we currently include it because we do not yet | ||||
|  | @ -2551,13 +2593,54 @@ reveal_type(Answer.__members__)  # revealed: MappingProxyType[str, Unknown] | |||
| ## Divergent inferred implicit instance attribute types | ||||
| 
 | ||||
| ```py | ||||
| # TODO: This test currently panics, see https://github.com/astral-sh/ty/issues/837 | ||||
| class C: | ||||
|     def f(self, other: "C"): | ||||
|         self.x = (other.x, 1) | ||||
| 
 | ||||
| # class C: | ||||
| #    def f(self, other: "C"): | ||||
| #        self.x = (other.x, 1) | ||||
| # | ||||
| # reveal_type(C().x)  # revealed: Unknown | tuple[Divergent, Literal[1]] | ||||
| reveal_type(C().x)  # revealed: Unknown | tuple[Divergent, Literal[1]] | ||||
| ``` | ||||
| 
 | ||||
| This also works if the tuple is not constructed directly: | ||||
| 
 | ||||
| ```py | ||||
| from typing import TypeVar, Literal | ||||
| 
 | ||||
| T = TypeVar("T") | ||||
| 
 | ||||
| def make_tuple(x: T) -> tuple[T, Literal[1]]: | ||||
|     return (x, 1) | ||||
| 
 | ||||
| class D: | ||||
|     def f(self, other: "D"): | ||||
|         self.x = make_tuple(other.x) | ||||
| 
 | ||||
| reveal_type(D().x)  # revealed: Unknown | tuple[Divergent, Literal[1]] | ||||
| ``` | ||||
| 
 | ||||
| The tuple type may also expand exponentially "in breadth": | ||||
| 
 | ||||
| ```py | ||||
| def duplicate(x: T) -> tuple[T, T]: | ||||
|     return (x, x) | ||||
| 
 | ||||
| class E: | ||||
|     def f(self: "E"): | ||||
|         self.x = duplicate(self.x) | ||||
| 
 | ||||
| reveal_type(E().x)  # revealed: Unknown | tuple[Divergent, Divergent] | ||||
| ``` | ||||
| 
 | ||||
| And it also works for homogeneous tuples: | ||||
| 
 | ||||
| ```py | ||||
| def make_homogeneous_tuple(x: T) -> tuple[T, ...]: | ||||
|     return (x, x) | ||||
| 
 | ||||
| class E: | ||||
|     def f(self, other: "E"): | ||||
|         self.x = make_homogeneous_tuple(other.x) | ||||
| 
 | ||||
| reveal_type(E().x)  # revealed: Unknown | tuple[Divergent, ...] | ||||
| ``` | ||||
| 
 | ||||
| ## Attributes of standard library modules that aren't yet defined | ||||
|  |  | |||
|  | @ -0,0 +1,17 @@ | |||
| # PEP 613 type aliases | ||||
| 
 | ||||
| We do not support PEP 613 type aliases yet. For now, just make sure that we don't panic: | ||||
| 
 | ||||
| ```py | ||||
| from typing import TypeAlias | ||||
| 
 | ||||
| RecursiveTuple: TypeAlias = tuple[int | "RecursiveTuple", str] | ||||
| 
 | ||||
| def _(rec: RecursiveTuple): | ||||
|     reveal_type(rec)  # revealed: tuple[Divergent, str] | ||||
| 
 | ||||
| RecursiveHomogeneousTuple: TypeAlias = tuple[int | "RecursiveHomogeneousTuple", ...] | ||||
| 
 | ||||
| def _(rec: RecursiveHomogeneousTuple): | ||||
|     reveal_type(rec)  # revealed: tuple[Divergent, ...] | ||||
| ``` | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 David Peter
						David Peter