Skip to content

Commit

Permalink
[PEP 695] Fix nested generic classes (#17776)
Browse files Browse the repository at this point in the history
There was confusion about the fullnames of type variables in nested
generic classes. A type variable could be defined internally as
`m.OuterClass.T`, but it was sometimes accessed as `m.T`. The root cause
was that the semantic analyzer didn't initialize the attribute that
refers to the enclosing class consistently.

Fixes #17596. Fixes #17630.
  • Loading branch information
JukkaL committed Sep 18, 2024
1 parent 5c38427 commit f68f76d
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
5 changes: 5 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,11 @@ def file_context(
self.num_incomplete_refs = 0

if active_type:
enclosing_fullname = active_type.fullname.rsplit(".", 1)[0]
if "." in enclosing_fullname:
enclosing_node = self.lookup_fully_qualified_or_none(enclosing_fullname)
if enclosing_node and isinstance(enclosing_node.node, TypeInfo):
self._type = enclosing_node.node
self.push_type_args(active_type.defn.type_args, active_type.defn)
self.incomplete_type_stack.append(False)
scope.enter_class(active_type)
Expand Down
66 changes: 66 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1713,3 +1713,69 @@ type XNested = (1 + (yield 1)) # E: Yield expression cannot be used within a ty
type YNested = (1 + (yield from [])) # E: Yield expression cannot be used within a type alias
type ZNested = (1 + (a := 1)) # E: Named expression cannot be used within a type alias
type KNested = (1 + (await 1)) # E: Await expression cannot be used within a type alias

[case testPEP695NestedGenericClass1]
# flags: --enable-incomplete-feature=NewGenericSyntax
class C[T]:
def f(self) -> T: ...

class A:
class B[Q]:
def __init__(self, a: Q) -> None:
self.a = a

def f(self) -> Q:
return self.a

def g(self, x: Q) -> None: ...

b: B[str]

x: A.B[int]
x.g("x") # E: Argument 1 to "g" of "B" has incompatible type "str"; expected "int"
reveal_type(x.a) # N: Revealed type is "builtins.int"
reveal_type(x) # N: Revealed type is "__main__.A.B[builtins.int]"
reveal_type(A.b) # N: Revealed type is "__main__.A.B[builtins.str]"

[case testPEP695NestedGenericClass2]
# flags: --enable-incomplete-feature=NewGenericSyntax
class A:
def m(self) -> None:
class B[T]:
def f(self) -> T: ...
x: B[int]
reveal_type(x.f()) # N: Revealed type is "builtins.int"
self.a = B[str]()

reveal_type(A().a) # N: Revealed type is "__main__.B@4[builtins.str]"
reveal_type(A().a.f()) # N: Revealed type is "builtins.str"

[case testPEP695NestedGenericClass3]
# flags: --enable-incomplete-feature=NewGenericSyntax
class C[T]:
def f(self) -> T: ...
class D[S]:
x: T # E: Name "T" is not defined
def g(self) -> S: ...

a: C[int]
reveal_type(a.f()) # N: Revealed type is "builtins.int"
b: C.D[str]
reveal_type(b.g()) # N: Revealed type is "builtins.str"

class E[T]:
class F[T]: # E: "T" already defined as a type parameter
x: T

c: E.F[int]

[case testPEP695NestedGenericClass4]
# flags: --enable-incomplete-feature=NewGenericSyntax
class A:
class B[T]:
def __get__(self, instance: A, owner: type[A]) -> T:
return None # E: Incompatible return value type (got "None", expected "T")
f = B[int]()

a = A()
v = a.f
20 changes: 20 additions & 0 deletions test-data/unit/fine-grained-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,23 @@ from builtins import tuple as B
==
main:4: error: Incompatible types in assignment (expression has type "int", variable has type "tuple[int, str]")
main:5: error: Incompatible types in assignment (expression has type "str", variable has type "tuple[int, str]")

[case testPEP695NestedGenericClassMethodUpdated]
# flags: --enable-incomplete-feature=NewGenericSyntax
from a import f

class C:
class D[T]:
x: T
def m(self) -> T:
f()
return self.x

[file a.py]
def f() -> None: pass

[file a.py.2]
def f(x: int) -> None: pass
[out]
==
main:8: error: Missing positional argument "x" in call to "f"

0 comments on commit f68f76d

Please sign in to comment.