Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion mypyc/irbuild/classdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
TempNode,
TypeInfo,
TypeParam,
Var,
is_class_var,
)
from mypy.types import Instance, UnboundType, get_proper_type
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 18 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading