diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 9e835f5a37e6..9f11dd111c99 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -14,7 +14,7 @@ AssignmentExpr, Var, RefExpr, MypyFile, TypeInfo, TypeApplication, LDEF, ARG_POS ) -from mypy.types import TupleType, get_proper_type, Instance +from mypy.types import TupleType, Instance, TypeType, ProperType, get_proper_type from mypyc.common import MAX_SHORT_INT from mypyc.ir.ops import ( @@ -133,9 +133,41 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: if expr.name in fields: index = builder.builder.load_int(fields.index(expr.name)) return builder.gen_method_call(obj, '__getitem__', [index], rtype, expr.line) + + check_instance_attribute_access_through_class(builder, expr, typ) + return builder.builder.get_attr(obj, expr.name, rtype, expr.line) +def check_instance_attribute_access_through_class(builder: IRBuilder, + expr: MemberExpr, + typ: Optional[ProperType]) -> None: + """Report error if accessing an instance attribute through class object.""" + if isinstance(expr.expr, RefExpr): + node = expr.expr.node + if isinstance(typ, TypeType) and isinstance(typ.item, Instance): + # TODO: Handle other item types + node = typ.item.type + if isinstance(node, TypeInfo): + class_ir = builder.mapper.type_to_ir.get(node) + if class_ir is not None and class_ir.is_ext_class: + sym = node.get(expr.name) + if (sym is not None + and isinstance(sym.node, Var) + and not sym.node.is_classvar + and not sym.node.is_final): + builder.error( + 'Cannot access instance attribute "{}" through class object'.format( + expr.name), + expr.line + ) + builder.note( + '(Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define ' + 'a class attribute)', + expr.line + ) + + def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value: # warning(builder, 'can not optimize super() expression', o.line) sup_val = builder.load_module_attr_by_fullname('builtins.super', o.line) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 58dd99b180bc..8f9b768df47c 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1144,3 +1144,41 @@ class DeletableFinal2: X: Final = 0 # E: Deletable attribute cannot be final __deletable__ = ['X'] + +[case testNeedAnnotateClassVar] +from typing import Final, ClassVar, Type + +class C: + a = 'A' + b: str = 'B' + f: Final = 'F' + c: ClassVar = 'C' + +class D(C): + pass + +def f() -> None: + C.a # E: Cannot access instance attribute "a" through class object \ + # N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute) + C.b # E: Cannot access instance attribute "b" through class object \ + # N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute) + C.f + C.c + + D.a # E: Cannot access instance attribute "a" through class object \ + # N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute) + D.b # E: Cannot access instance attribute "b" through class object \ + # N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute) + D.f + D.c + +def g(c: Type[C], d: Type[D]) -> None: + c.a # E: Cannot access instance attribute "a" through class object \ + # N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute) + c.f + c.c + + d.a # E: Cannot access instance attribute "a" through class object \ + # N: (Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define a class attribute) + d.f + d.c diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 3353222ee2dc..8db2dda08614 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -225,8 +225,15 @@ class Overload: def get(c: Overload, s: str) -> str: return c.get(s) +@decorator +class Var: + x = 'xy' + +def get_class_var() -> str: + return Var.x + [file driver.py] -from native import A, Overload, get +from native import A, Overload, get, get_class_var a = A() assert a.a == 1 assert a.b == 2 @@ -240,6 +247,8 @@ o = Overload() assert get(o, "test") == "test" assert o.get(20) == 20 +assert get_class_var() == 'xy' + [case testEnum] from enum import Enum