diff --git a/mypy/semanal.py b/mypy/semanal.py index d769178dc2985..86234d100c277 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4194,7 +4194,16 @@ def disable_invalid_recursive_aliases( ) -> None: """Prohibit and fix recursive type aliases that are invalid/unsupported.""" messages = [] - if is_invalid_recursive_alias({current_node}, current_node.target): + if ( + isinstance(current_node.target, TypeAliasType) + and current_node.target.alias is current_node + ): + # We want to have consistent error messages, but not calling name_not_defined(), + # since it will do a bunch of unrelated things we don't want here. + messages.append( + f'Cannot resolve name "{current_node.name}" (possible cyclic definition)' + ) + elif is_invalid_recursive_alias({current_node}, current_node.target): target = ( "tuple" if isinstance(get_proper_type(current_node.target), TupleType) else "union" ) @@ -6315,12 +6324,24 @@ class C: if self.statement is None: # Assume it's fine -- don't have enough context to check return True - return ( + if ( node is None or self.is_textually_before_statement(node) or not self.is_defined_in_current_module(node.fullname) - or isinstance(node, (TypeInfo, TypeAlias)) - or (isinstance(node, PlaceholderNode) and node.becomes_typeinfo) + ): + return True + if self.is_type_like(node): + # Allow forward references to classes/type aliases (see docstring), but + # a forward reference should never shadow an existing regular reference. + if node.name not in self.globals: + return True + global_node = self.globals[node.name] + return not self.is_type_like(global_node.node) + return False + + def is_type_like(self, node: SymbolNode | None) -> bool: + return isinstance(node, (TypeInfo, TypeAlias)) or ( + isinstance(node, PlaceholderNode) and node.becomes_typeinfo ) def is_textually_before_statement(self, node: SymbolNode) -> bool: diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index d5101e2e25f37..1eafd462aa515 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3140,6 +3140,22 @@ from typing import Final x: Final = 0 x = x # E: Cannot assign to final name "x" +[case testNewAnalyzerIdentityAssignmentClassImplicit] +class C: ... +class A: + C = C[str] # E: "C" expects no type arguments, but 1 given +[builtins fixtures/tuple.pyi] + +[case testNewAnalyzerIdentityAssignmentClassExplicit] +from typing_extensions import TypeAlias + +class A: + C: TypeAlias = C +class C: ... +c: A.C +reveal_type(c) # N: Revealed type is "__main__.C" +[builtins fixtures/tuple.pyi] + [case testNewAnalyzerClassPropertiesInAllScopes] from abc import abstractmethod, ABCMeta diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 80cceea85581f..ba4104a50048e 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -1988,3 +1988,44 @@ bis: RK_functionBIS = ff res: int = bis(1.0, 2, 3) [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + +[case testPEP695TypeAliasNotReadyClass] +class CustomizeResponse: + related_resources: "ResourceRule" + +class ResourceRule: pass + +class DecoratorController: + type CustomizeResponse = CustomizeResponse + +x: DecoratorController.CustomizeResponse +reveal_type(x.related_resources) # N: Revealed type is "__main__.ResourceRule" +[builtins fixtures/tuple.pyi] + +[case testPEP695TypeAliasRecursiveOuterClass] +class A: + type X = X +class X: ... + +class Y: ... +class B: + type Y = Y + +x: A.X +reveal_type(x) # N: Revealed type is "__main__.X" +y: B.Y +reveal_type(y) # N: Revealed type is "__main__.Y" +[builtins fixtures/tuple.pyi] + +[case testPEP695TypeAliasRecursiveInvalid] +type X = X # E: Cannot resolve name "X" (possible cyclic definition) +type Z = Z[int] # E: Cannot resolve name "Z" (possible cyclic definition) +def foo() -> None: + type X = X # OK, refers to outer (invalid) X + x: X + reveal_type(x) # N: Revealed type is "Any" + type Y = Y # E: Cannot resolve name "Y" (possible cyclic definition) \ + # N: Recursive types are not allowed at function scope +class Z: ... # E: Name "Z" already defined on line 2 +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 9527c85ed26a7..c5915176a5ffb 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -1286,3 +1286,31 @@ x2: Explicit[str] # E: Bad number of arguments for type alias, expected 0, give assert_type(x1, Callable[..., Any]) assert_type(x2, Callable[..., Any]) [builtins fixtures/tuple.pyi] + +[case testExplicitTypeAliasToSameNameOuterProhibited] +from typing import TypeVar, Generic +from typing_extensions import TypeAlias + +T = TypeVar("T") +class Foo(Generic[T]): + bar: Bar[T] + +class Bar(Generic[T]): + Foo: TypeAlias = Foo[T] # E: Can't use bound type variable "T" to define generic alias +[builtins fixtures/tuple.pyi] + +[case testExplicitTypeAliasToSameNameOuterAllowed] +from typing import TypeVar, Generic +from typing_extensions import TypeAlias + +T = TypeVar("T") +class Foo(Generic[T]): + bar: Bar[T] + +U = TypeVar("U") +class Bar(Generic[T]): + Foo: TypeAlias = Foo[U] + var: Foo[T] +x: Bar[int] +reveal_type(x.var.bar) # N: Revealed type is "__main__.Bar[builtins.int]" +[builtins fixtures/tuple.pyi]