Skip to content

Commit a37c388

Browse files
authored
[mypyc] Reject instance attribute access through class object (#10798)
Accessing an instance attribute through a native class object results in unexpected behavior at runtime (e.g. <attribute 'x' of 'C' objects>) so reject these during compilation. Also produce a note that suggests how to work around the issue. Fixes mypyc/mypyc#814.
1 parent 24f3ba0 commit a37c388

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

mypyc/irbuild/expression.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
AssignmentExpr,
1515
Var, RefExpr, MypyFile, TypeInfo, TypeApplication, LDEF, ARG_POS
1616
)
17-
from mypy.types import TupleType, get_proper_type, Instance
17+
from mypy.types import TupleType, Instance, TypeType, ProperType, get_proper_type
1818

1919
from mypyc.common import MAX_SHORT_INT
2020
from mypyc.ir.ops import (
@@ -133,9 +133,41 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value:
133133
if expr.name in fields:
134134
index = builder.builder.load_int(fields.index(expr.name))
135135
return builder.gen_method_call(obj, '__getitem__', [index], rtype, expr.line)
136+
137+
check_instance_attribute_access_through_class(builder, expr, typ)
138+
136139
return builder.builder.get_attr(obj, expr.name, rtype, expr.line)
137140

138141

142+
def check_instance_attribute_access_through_class(builder: IRBuilder,
143+
expr: MemberExpr,
144+
typ: Optional[ProperType]) -> None:
145+
"""Report error if accessing an instance attribute through class object."""
146+
if isinstance(expr.expr, RefExpr):
147+
node = expr.expr.node
148+
if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
149+
# TODO: Handle other item types
150+
node = typ.item.type
151+
if isinstance(node, TypeInfo):
152+
class_ir = builder.mapper.type_to_ir.get(node)
153+
if class_ir is not None and class_ir.is_ext_class:
154+
sym = node.get(expr.name)
155+
if (sym is not None
156+
and isinstance(sym.node, Var)
157+
and not sym.node.is_classvar
158+
and not sym.node.is_final):
159+
builder.error(
160+
'Cannot access instance attribute "{}" through class object'.format(
161+
expr.name),
162+
expr.line
163+
)
164+
builder.note(
165+
'(Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define '
166+
'a class attribute)',
167+
expr.line
168+
)
169+
170+
139171
def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value:
140172
# warning(builder, 'can not optimize super() expression', o.line)
141173
sup_val = builder.load_module_attr_by_fullname('builtins.super', o.line)

mypyc/test-data/irbuild-classes.test

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,3 +1144,41 @@ class DeletableFinal2:
11441144
X: Final = 0 # E: Deletable attribute cannot be final
11451145

11461146
__deletable__ = ['X']
1147+
1148+
[case testNeedAnnotateClassVar]
1149+
from typing import Final, ClassVar, Type
1150+
1151+
class C:
1152+
a = 'A'
1153+
b: str = 'B'
1154+
f: Final = 'F'
1155+
c: ClassVar = 'C'
1156+
1157+
class D(C):
1158+
pass
1159+
1160+
def f() -> None:
1161+
C.a # E: Cannot access instance attribute "a" through class object \
1162+
# N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute)
1163+
C.b # E: Cannot access instance attribute "b" through class object \
1164+
# N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute)
1165+
C.f
1166+
C.c
1167+
1168+
D.a # E: Cannot access instance attribute "a" through class object \
1169+
# N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute)
1170+
D.b # E: Cannot access instance attribute "b" through class object \
1171+
# N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute)
1172+
D.f
1173+
D.c
1174+
1175+
def g(c: Type[C], d: Type[D]) -> None:
1176+
c.a # E: Cannot access instance attribute "a" through class object \
1177+
# N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute)
1178+
c.f
1179+
c.c
1180+
1181+
d.a # E: Cannot access instance attribute "a" through class object \
1182+
# N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute)
1183+
d.f
1184+
d.c

mypyc/test-data/run-classes.test

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,15 @@ class Overload:
225225
def get(c: Overload, s: str) -> str:
226226
return c.get(s)
227227

228+
@decorator
229+
class Var:
230+
x = 'xy'
231+
232+
def get_class_var() -> str:
233+
return Var.x
234+
228235
[file driver.py]
229-
from native import A, Overload, get
236+
from native import A, Overload, get, get_class_var
230237
a = A()
231238
assert a.a == 1
232239
assert a.b == 2
@@ -240,6 +247,8 @@ o = Overload()
240247
assert get(o, "test") == "test"
241248
assert o.get(20) == 20
242249

250+
assert get_class_var() == 'xy'
251+
243252
[case testEnum]
244253
from enum import Enum
245254

0 commit comments

Comments
 (0)