Skip to content

Commit

Permalink
[mypyc] Detect if attribute definition conflicts with base class/trait (
Browse files Browse the repository at this point in the history
#14535)

Require that all the attribute definitions have the same type.
Overriding with a different type is unsafe, and we don't want to add
runtime checks when accessing an attribute that might be overridden with
a different type. If the types would have different runtime
representations, supporting that at all would be complicated.

Fixes mypyc/mypyc#970.
  • Loading branch information
JukkaL authored Feb 5, 2023
1 parent b64bd3d commit 332bb2d
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 0 deletions.
19 changes: 19 additions & 0 deletions mypyc/irbuild/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
is_trait,
)
from mypyc.options import CompilerOptions
from mypyc.sametype import is_same_type


def build_type_map(
Expand Down Expand Up @@ -112,6 +113,24 @@ def build_type_map(
prepare_func_def(module.fullname, None, func, mapper)
# TODO: what else?

# Check for incompatible attribute definitions that were not
# flagged by mypy but can't be supported when compiling.
for module, cdef in classes:
class_ir = mapper.type_to_ir[cdef.info]
for attr in class_ir.attributes:
for base_ir in class_ir.mro[1:]:
if attr in base_ir.attributes:
if not is_same_type(class_ir.attributes[attr], base_ir.attributes[attr]):
node = cdef.info.names[attr].node
assert node is not None
kind = "trait" if base_ir.is_trait else "class"
errors.error(
f'Type of "{attr}" is incompatible with '
f'definition in {kind} "{base_ir.name}"',
module.path,
node.line,
)


def is_from_module(node: SymbolNode, module: MypyFile) -> bool:
return node.fullname == module.fullname + "." + node.name
Expand Down
42 changes: 42 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1245,3 +1245,45 @@ L2:
y = 4
L3:
return 1

[case testIncompatibleDefinitionOfAttributeInSubclass]
from mypy_extensions import trait

class Base:
x: int

class Bad1(Base):
x: bool # E: Type of "x" is incompatible with definition in class "Base"

class Good1(Base):
x: int

class Good2(Base):
x: int = 0

class Good3(Base):
x = 0

class Good4(Base):
def __init__(self) -> None:
self.x = 0

class Good5(Base):
def __init__(self) -> None:
self.x: int = 0

class Base2(Base):
pass

class Bad2(Base2):
x: bool = False # E: Type of "x" is incompatible with definition in class "Base"

class Bad3(Base):
x = False # E: Type of "x" is incompatible with definition in class "Base"

@trait
class T:
y: object

class E(T):
y: str # E: Type of "y" is incompatible with definition in trait "T"

0 comments on commit 332bb2d

Please sign in to comment.