Skip to content

Commit 332bb2d

Browse files
authored
[mypyc] Detect if attribute definition conflicts with base class/trait (#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.
1 parent b64bd3d commit 332bb2d

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

mypyc/irbuild/prepare.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
is_trait,
6161
)
6262
from mypyc.options import CompilerOptions
63+
from mypyc.sametype import is_same_type
6364

6465

6566
def build_type_map(
@@ -112,6 +113,24 @@ def build_type_map(
112113
prepare_func_def(module.fullname, None, func, mapper)
113114
# TODO: what else?
114115

116+
# Check for incompatible attribute definitions that were not
117+
# flagged by mypy but can't be supported when compiling.
118+
for module, cdef in classes:
119+
class_ir = mapper.type_to_ir[cdef.info]
120+
for attr in class_ir.attributes:
121+
for base_ir in class_ir.mro[1:]:
122+
if attr in base_ir.attributes:
123+
if not is_same_type(class_ir.attributes[attr], base_ir.attributes[attr]):
124+
node = cdef.info.names[attr].node
125+
assert node is not None
126+
kind = "trait" if base_ir.is_trait else "class"
127+
errors.error(
128+
f'Type of "{attr}" is incompatible with '
129+
f'definition in {kind} "{base_ir.name}"',
130+
module.path,
131+
node.line,
132+
)
133+
115134

116135
def is_from_module(node: SymbolNode, module: MypyFile) -> bool:
117136
return node.fullname == module.fullname + "." + node.name

mypyc/test-data/irbuild-classes.test

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,3 +1245,45 @@ L2:
12451245
y = 4
12461246
L3:
12471247
return 1
1248+
1249+
[case testIncompatibleDefinitionOfAttributeInSubclass]
1250+
from mypy_extensions import trait
1251+
1252+
class Base:
1253+
x: int
1254+
1255+
class Bad1(Base):
1256+
x: bool # E: Type of "x" is incompatible with definition in class "Base"
1257+
1258+
class Good1(Base):
1259+
x: int
1260+
1261+
class Good2(Base):
1262+
x: int = 0
1263+
1264+
class Good3(Base):
1265+
x = 0
1266+
1267+
class Good4(Base):
1268+
def __init__(self) -> None:
1269+
self.x = 0
1270+
1271+
class Good5(Base):
1272+
def __init__(self) -> None:
1273+
self.x: int = 0
1274+
1275+
class Base2(Base):
1276+
pass
1277+
1278+
class Bad2(Base2):
1279+
x: bool = False # E: Type of "x" is incompatible with definition in class "Base"
1280+
1281+
class Bad3(Base):
1282+
x = False # E: Type of "x" is incompatible with definition in class "Base"
1283+
1284+
@trait
1285+
class T:
1286+
y: object
1287+
1288+
class E(T):
1289+
y: str # E: Type of "y" is incompatible with definition in trait "T"

0 commit comments

Comments
 (0)