Skip to content

[mypyc] Reject instance attribute access through class object #10798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 10, 2021
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
34 changes: 33 additions & 1 deletion mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 10 additions & 1 deletion mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down