From 5446ea944ef593767671c3f022258d521b566301 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 5 Jun 2026 11:09:20 +0100 Subject: [PATCH 1/2] Add failing test case --- mypyc/test-data/run-classes.test | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 39172a6385696..7722cf26ca910 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -1113,6 +1113,24 @@ assert f() == 10 A.x = 200 assert f() == 200 +[case testClassVarDoesNotShadowMethodGlobal] +from typing import ClassVar +from testutil import assertRaises + +class E(Exception): + pass + +class C: + E: ClassVar[type[E]] = E + + def m(self, b: bytes) -> None: + if not b: + raise E() + +def test_class_var_does_not_shadow_method_global() -> None: + with assertRaises(E): + C().m(b"") + [case testInitSubclassWithClassVar] from typing import ClassVar From 83e86f9b4125afa02c9a0e099c838a043db32012 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 5 Jun 2026 11:55:56 +0100 Subject: [PATCH 2/2] [mypyc] Fix name lookup when class var and module var have the same name Disambiguate using the Var node, since the short name itself is not enough to disambiguate between global variable and class variable with the same name. Fixes mypyc/mypyc#1201. --- mypyc/irbuild/builder.py | 4 ++-- mypyc/irbuild/classdef.py | 4 +++- mypyc/irbuild/expression.py | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 066954e920165..587be873a46d9 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -251,11 +251,11 @@ def __init__( self.visitor = visitor - # Class body context: tracks ClassVar names defined so far when processing + # Class body context: tracks ClassVars defined so far when processing # a class body, so that intra-class references (e.g. C = A | B where A is # a ClassVar defined earlier in the same class) can be resolved correctly. # Without this, mypyc looks up such names in module globals, which fails. - self.class_body_classvars: dict[str, None] = {} + self.class_body_classvars: dict[Var, None] = {} self.class_body_obj: Value | None = None self.class_body_ir: ClassIR | None = None diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index 5bc19c961010b..4fd9a2e2cc748 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -26,6 +26,7 @@ TempNode, TypeInfo, TypeParam, + Var, is_class_var, ) from mypy.types import Instance, UnboundType, get_proper_type @@ -186,7 +187,8 @@ def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None: cls_builder.add_attr(lvalue, stmt) # Track this ClassVar so subsequent class body statements can reference it. if is_class_var(lvalue) or stmt.is_final_def: - builder.class_body_classvars[lvalue.name] = None + assert isinstance(lvalue.node, Var), lvalue.node + builder.class_body_classvars[lvalue.node] = None elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr): # Docstring. Ignore diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index f953dd3825ef2..cd7295ef709ad 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -232,7 +232,11 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: # If we're evaluating a class body and this name is a ClassVar defined earlier # in the same class, load it from the class being built (type object for ext classes, # class dict for non-ext classes) instead of module globals. - if builder.class_body_obj is not None and expr.name in builder.class_body_classvars: + if ( + builder.class_body_obj is not None + and isinstance(expr.node, Var) + and expr.node in builder.class_body_classvars + ): if builder.class_body_ir is not None and builder.class_body_ir.is_ext_class: return builder.py_get_attr(builder.class_body_obj, expr.name, expr.line) else: