From 14792c7c9fa4705e0c93609466df2f366ed72e35 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sun, 23 Nov 2025 13:29:30 +0900 Subject: [PATCH 1/3] 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 eb8964b30218..d6662e59824d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1878,8 +1878,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 @@ -1887,15 +1889,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 @@ -2343,7 +2345,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 8115bfb65e42..7252969ff2aa 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2296,6 +2296,13 @@ class D[*Ts](Generic[Unpack[Us]]): # E: Generic[...] base class is redundant \ pass [builtins fixtures/tuple.pyi] +[case testPEP695TypeVarReusedName] +class C: + X = 5 + class Inner[X]: + pass +[builtins fixtures/tuple.pyi] + [case testPEP695VariadicAliasUnpack] class C[*Ts]: pass From bf9b13452bb26d16a123843879b783fe02b4619f Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sun, 23 Nov 2025 13:45:05 +0900 Subject: [PATCH 2/3] 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 d6662e59824d..b42c52c08c2f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1878,10 +1878,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 @@ -1889,7 +1887,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 @@ -2345,16 +2343,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): From 896f12223e62b3871fc419cdaeb2b64d1c723181 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 14 Oct 2026 16:49:19 -0400 Subject: [PATCH 3/3] PR review --- mypy/semanal.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b42c52c08c2f..a816108f59f4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1888,10 +1888,8 @@ def push_type_args( return tvs def get_defined_type_param(self, name: str) -> TypeVarLikeExpr | None: - for names in self.locals: - if names is None: - continue - if name in names: + for names in reversed(self.locals): + if names is not None and name in names: node = names[name].node if isinstance(node, TypeVarLikeExpr): return node