From 7feaeadcebb60b5bbc0e803d4b5c6968e863e008 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sun, 23 Nov 2025 13:29:30 +0900 Subject: [PATCH 1/2] Bypass class attribute lookup for PEP 695 typevars --- mypy/semanal.py | 13 ++++++++----- test-data/unit/check-python312.test | 7 +++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index a9dbc8a2dd20..75b38fc6339c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1842,8 +1842,10 @@ def push_type_args( return None tvs.append((p.name, tv)) - if self.is_defined_type_param(p.name): + if old_tv := self.is_defined_type_param(p.name): self.fail(f'"{p.name}" already defined as a type parameter', context) + # we rely on the typevar being at self.locals[-1] later, so this needs to happen + self.add_symbol(p.name, old_tv, context, no_progress=True, type_param=True) else: assert self.add_symbol( p.name, tv, context, no_progress=True, type_param=True @@ -1851,15 +1853,15 @@ def push_type_args( return tvs - def is_defined_type_param(self, name: str) -> bool: + def is_defined_type_param(self, name: str) -> TypeVarLikeExpr | None: for names in self.locals: if names is None: continue if name in names: node = names[name].node if isinstance(node, TypeVarLikeExpr): - return True - return False + return node + return None def analyze_type_param( self, type_param: TypeParam, context: Context @@ -2272,7 +2274,8 @@ class Foo(Bar, Generic[T]): ... has_type_var_tuple = False if defn.type_args is not None: for p in defn.type_args: - node = self.lookup(p.name, context) + assert self.locals[-1], "expected PEP 695 type vars in locals" + node = self.locals[-1][p.name] assert node is not None assert isinstance(node.node, TypeVarLikeExpr) if isinstance(node.node, TypeVarTupleExpr): diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index a67ed4abcffa..849f6c3c0b52 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2237,3 +2237,10 @@ class D[*Ts](Generic[Unpack[Us]]): # E: Generic[...] base class is redundant \ # E: Can only use one type var tuple in a class def pass [builtins fixtures/tuple.pyi] + +[case testPEP695TypeVarReusedName] +class C: + X = 5 + class Inner[X]: + pass +[builtins fixtures/tuple.pyi] From 5feef3993931d2cc3bf9471181ba2ae1a2513107 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sun, 23 Nov 2025 13:45:05 +0900 Subject: [PATCH 2/2] Make the change less risky --- mypy/semanal.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 75b38fc6339c..f30709fa03a7 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1842,10 +1842,8 @@ def push_type_args( return None tvs.append((p.name, tv)) - if old_tv := self.is_defined_type_param(p.name): + if self.get_defined_type_param(p.name): self.fail(f'"{p.name}" already defined as a type parameter', context) - # we rely on the typevar being at self.locals[-1] later, so this needs to happen - self.add_symbol(p.name, old_tv, context, no_progress=True, type_param=True) else: assert self.add_symbol( p.name, tv, context, no_progress=True, type_param=True @@ -1853,7 +1851,7 @@ def push_type_args( return tvs - def is_defined_type_param(self, name: str) -> TypeVarLikeExpr | None: + def get_defined_type_param(self, name: str) -> TypeVarLikeExpr | None: for names in self.locals: if names is None: continue @@ -2274,16 +2272,15 @@ class Foo(Bar, Generic[T]): ... has_type_var_tuple = False if defn.type_args is not None: for p in defn.type_args: - assert self.locals[-1], "expected PEP 695 type vars in locals" - node = self.locals[-1][p.name] + node = self.get_defined_type_param(p.name) assert node is not None - assert isinstance(node.node, TypeVarLikeExpr) - if isinstance(node.node, TypeVarTupleExpr): + assert isinstance(node, TypeVarLikeExpr) + if isinstance(node, TypeVarTupleExpr): if has_type_var_tuple: self.fail("Can only use one type var tuple in a class def", context) continue has_type_var_tuple = True - declared_tvars.append((p.name, node.node)) + declared_tvars.append((p.name, node)) for i, base_expr in enumerate(base_type_exprs): if isinstance(base_expr, StarExpr):