Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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:
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-newsemanal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
41 changes: 41 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
28 changes: 28 additions & 0 deletions test-data/unit/check-type-aliases.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]