Skip to content

Commit e15a6d5

Browse files
authored
[mypyc] Fix name lookup when class var and module var have the same name (#21594)
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.
1 parent 28751e9 commit e15a6d5

4 files changed

Lines changed: 28 additions & 4 deletions

File tree

mypyc/irbuild/builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ def __init__(
251251

252252
self.visitor = visitor
253253

254-
# Class body context: tracks ClassVar names defined so far when processing
254+
# Class body context: tracks ClassVars defined so far when processing
255255
# a class body, so that intra-class references (e.g. C = A | B where A is
256256
# a ClassVar defined earlier in the same class) can be resolved correctly.
257257
# Without this, mypyc looks up such names in module globals, which fails.
258-
self.class_body_classvars: dict[str, None] = {}
258+
self.class_body_classvars: dict[Var, None] = {}
259259
self.class_body_obj: Value | None = None
260260
self.class_body_ir: ClassIR | None = None
261261

mypyc/irbuild/classdef.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
TempNode,
2727
TypeInfo,
2828
TypeParam,
29+
Var,
2930
is_class_var,
3031
)
3132
from mypy.types import Instance, UnboundType, get_proper_type
@@ -186,7 +187,8 @@ def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None:
186187
cls_builder.add_attr(lvalue, stmt)
187188
# Track this ClassVar so subsequent class body statements can reference it.
188189
if is_class_var(lvalue) or stmt.is_final_def:
189-
builder.class_body_classvars[lvalue.name] = None
190+
assert isinstance(lvalue.node, Var), lvalue.node
191+
builder.class_body_classvars[lvalue.node] = None
190192

191193
elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr):
192194
# Docstring. Ignore

mypyc/irbuild/expression.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,11 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value:
232232
# If we're evaluating a class body and this name is a ClassVar defined earlier
233233
# in the same class, load it from the class being built (type object for ext classes,
234234
# class dict for non-ext classes) instead of module globals.
235-
if builder.class_body_obj is not None and expr.name in builder.class_body_classvars:
235+
if (
236+
builder.class_body_obj is not None
237+
and isinstance(expr.node, Var)
238+
and expr.node in builder.class_body_classvars
239+
):
236240
if builder.class_body_ir is not None and builder.class_body_ir.is_ext_class:
237241
return builder.py_get_attr(builder.class_body_obj, expr.name, expr.line)
238242
else:

mypyc/test-data/run-classes.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,24 @@ assert f() == 10
11131113
A.x = 200
11141114
assert f() == 200
11151115

1116+
[case testClassVarDoesNotShadowMethodGlobal]
1117+
from typing import ClassVar
1118+
from testutil import assertRaises
1119+
1120+
class E(Exception):
1121+
pass
1122+
1123+
class C:
1124+
E: ClassVar[type[E]] = E
1125+
1126+
def m(self, b: bytes) -> None:
1127+
if not b:
1128+
raise E()
1129+
1130+
def test_class_var_does_not_shadow_method_global() -> None:
1131+
with assertRaises(E):
1132+
C().m(b"")
1133+
11161134
[case testInitSubclassWithClassVar]
11171135
from typing import ClassVar
11181136

0 commit comments

Comments
 (0)