Skip to content

Commit 1b41df3

Browse files
committed
better error message for nested classes multiple inheritance
1 parent b05ab3a commit 1b41df3

File tree

2 files changed

+260
-10
lines changed

2 files changed

+260
-10
lines changed

mypy/checker.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,16 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
16051605
if name in base2.names and base2 not in base.mro:
16061606
self.check_compatibility(name, base, base2, typ)
16071607

1608+
def determine_type_of_class_member(self, sym: SymbolTableNode) -> Optional[Type]:
1609+
if sym.type is not None:
1610+
return sym.type
1611+
if isinstance(sym.node, FuncBase):
1612+
return self.function_type(sym.node)
1613+
if isinstance(sym.node, TypeInfo):
1614+
# nested class
1615+
return type_object_type(sym.node, self.named_type)
1616+
return None
1617+
16081618
def check_compatibility(self, name: str, base1: TypeInfo,
16091619
base2: TypeInfo, ctx: Context) -> None:
16101620
"""Check if attribute name in base1 is compatible with base2 in multiple inheritance.
@@ -1618,19 +1628,21 @@ def check_compatibility(self, name: str, base1: TypeInfo,
16181628
return
16191629
first = base1[name]
16201630
second = base2[name]
1621-
first_type = first.type
1622-
if first_type is None and isinstance(first.node, FuncBase):
1623-
first_type = self.function_type(first.node)
1624-
second_type = second.type
1625-
if second_type is None and isinstance(second.node, FuncBase):
1626-
second_type = self.function_type(second.node)
1631+
first_type = self.determine_type_of_class_member(first)
1632+
second_type = self.determine_type_of_class_member(second)
1633+
16271634
# TODO: What if some classes are generic?
16281635
if (isinstance(first_type, FunctionLike) and
16291636
isinstance(second_type, FunctionLike)):
1630-
# Method override
1631-
first_sig = bind_self(first_type)
1632-
second_sig = bind_self(second_type)
1633-
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
1637+
if first_type.is_type_obj() and second_type.is_type_obj():
1638+
# For class objects only check the subtype relationship of the classes,
1639+
# since we allow incompatible overrides of '__init__'/'__new__'
1640+
ok = is_subtype(left=fill_typevars(first_type.type_object()),
1641+
right=fill_typevars(second_type.type_object()))
1642+
else:
1643+
first_sig = bind_self(first_type)
1644+
second_sig = bind_self(second_type)
1645+
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
16341646
elif first_type and second_type:
16351647
ok = is_equivalent(first_type, second_type)
16361648
else:

test-data/unit/check-multiple-inheritance.test

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,241 @@ def dec(f: Callable[..., T]) -> Callable[..., T]:
240240
[out]
241241
main:3: error: Cannot determine type of 'f' in base class 'B'
242242
main:3: error: Cannot determine type of 'f' in base class 'C'
243+
244+
[case testMultipleInheritance_NestedClassesWithSameName]
245+
class Mixin1:
246+
class Meta:
247+
pass
248+
class Mixin2:
249+
class Meta:
250+
pass
251+
class A(Mixin1, Mixin2):
252+
pass
253+
[out]
254+
main:7: error: Definition of "Meta" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
255+
256+
[case testMultipleInheritance_NestedClassesWithSameNameCustomMetaclass]
257+
class Metaclass(type):
258+
pass
259+
class Mixin1:
260+
class Meta(metaclass=Metaclass):
261+
pass
262+
class Mixin2:
263+
class Meta(metaclass=Metaclass):
264+
pass
265+
class A(Mixin1, Mixin2):
266+
pass
267+
[out]
268+
main:9: error: Definition of "Meta" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
269+
270+
[case testMultipleInheritance_NestedClassesWithSameNameOverloadedNew]
271+
from mixins import Mixin1, Mixin2
272+
class A(Mixin1, Mixin2):
273+
pass
274+
[file mixins.py]
275+
class Mixin1:
276+
class Meta:
277+
pass
278+
class Mixin2:
279+
class Meta:
280+
pass
281+
[file mixins.pyi]
282+
from typing import overload, Any, Mapping, Dict
283+
class Mixin1:
284+
class Meta:
285+
@overload
286+
def __new__(cls, *args, **kwargs: None) -> Mixin1.Meta:
287+
pass
288+
@overload
289+
def __new__(cls, *args, **kwargs: Dict[str, Any]) -> Mixin1.Meta:
290+
pass
291+
class Mixin2:
292+
class Meta:
293+
pass
294+
[builtins fixtures/dict.pyi]
295+
[out]
296+
main:2: error: Definition of "Meta" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
297+
298+
[case testMultipleInheritance_NestedClassAndAttrHaveSameName]
299+
class Mixin1:
300+
class Nested1:
301+
pass
302+
class Mixin2:
303+
Nested1: str
304+
class A(Mixin1, Mixin2):
305+
pass
306+
[out]
307+
main:6: error: Definition of "Nested1" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
308+
309+
[case testMultipleInheritance_NestedClassAndFunctionHaveSameName]
310+
class Mixin1:
311+
class Nested1:
312+
pass
313+
class Mixin2:
314+
def Nested1(self) -> str:
315+
pass
316+
class A(Mixin1, Mixin2):
317+
pass
318+
[out]
319+
main:7: error: Definition of "Nested1" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
320+
321+
[case testMultipleInheritance_NestedClassAndRefToOtherClass]
322+
class Outer:
323+
pass
324+
class Mixin1:
325+
class Nested1:
326+
pass
327+
class Mixin2:
328+
Nested1 = Outer
329+
class A(Mixin2, Mixin1):
330+
pass
331+
[out]
332+
main:8: error: Definition of "Nested1" in base class "Mixin2" is incompatible with definition in base class "Mixin1"
333+
334+
[case testMultipleInheritance_ReferenceToSubclassesFromSameMRO]
335+
class A:
336+
def __init__(self, arg: str) -> None:
337+
pass
338+
class B(A):
339+
pass
340+
class Base1:
341+
NestedVar = A
342+
class Base2:
343+
NestedVar = B
344+
class Combo(Base2, Base1): ...
345+
[out]
346+
347+
[case testMultipleInheritance_ReferenceToSubclassesFromSameMROCustomMetaclass]
348+
class Metaclass(type):
349+
pass
350+
class A(metaclass=Metaclass):
351+
pass
352+
class B(A):
353+
pass
354+
class Base1:
355+
NestedVar = A
356+
class Base2:
357+
NestedVar = B
358+
class Combo(Base2, Base1): ...
359+
[out]
360+
361+
[case testMultipleInheritance_ReferenceToSubclassesFromSameMROOverloadedNew]
362+
from mixins import A, B
363+
class Base1:
364+
NestedVar = A
365+
class Base2:
366+
NestedVar = B
367+
class Combo(Base2, Base1): ...
368+
[file mixins.py]
369+
class A:
370+
pass
371+
class B(A):
372+
pass
373+
[file mixins.pyi]
374+
from typing import overload, Dict, Any
375+
class A:
376+
@overload
377+
def __new__(cls, *args, **kwargs: None) -> A:
378+
pass
379+
@overload
380+
def __new__(cls, *args, **kwargs: Dict[str, Any]) -> A:
381+
pass
382+
class B:
383+
pass
384+
[builtins fixtures/dict.pyi]
385+
[out]
386+
main:6: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"
387+
388+
[case testMultipleInheritance_ReferenceToGenericClasses]
389+
from typing import TypeVar, Generic
390+
T = TypeVar('T')
391+
class Generic1(Generic[T]):
392+
pass
393+
class Generic2(Generic[T]):
394+
pass
395+
class Base1:
396+
Nested = Generic1
397+
class Base2:
398+
Nested = Generic2
399+
class A(Base1, Base2):
400+
pass
401+
[out]
402+
main:11: error: Definition of "Nested" in base class "Base1" is incompatible with definition in base class "Base2"
403+
404+
[case testMultipleInheritance_RefersToNamedTuples]
405+
from typing import NamedTuple
406+
class NamedTuple1:
407+
attr1: int
408+
class NamedTuple2:
409+
attr2: int
410+
class Base1:
411+
Nested = NamedTuple1
412+
class Base2:
413+
Nested = NamedTuple2
414+
class A(Base1, Base2):
415+
pass
416+
[out]
417+
main:10: error: Definition of "Nested" in base class "Base1" is incompatible with definition in base class "Base2"
418+
419+
[case testMultipleInheritance_NestedVariableRefersToSuperlassUnderSubclass]
420+
class A:
421+
def __init__(self, arg: str) -> None:
422+
pass
423+
class B(A):
424+
pass
425+
class Base1:
426+
NestedVar = B
427+
class Base2:
428+
NestedVar = A
429+
class Combo(Base2, Base1): ...
430+
[out]
431+
main:10: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"
432+
433+
[case testNestedVariableRefersToSubclassOfAnotherNestedClass]
434+
class Mixin1:
435+
class Meta:
436+
pass
437+
class Outer(Mixin1.Meta):
438+
pass
439+
class Mixin2:
440+
NestedVar = Outer
441+
class Combo(Mixin2, Mixin1): ...
442+
[out]
443+
444+
[case testNestedVariableRefersToCompletelyDifferentClasses]
445+
class A:
446+
pass
447+
class B:
448+
pass
449+
class Base1:
450+
NestedVar = A
451+
class Base2:
452+
NestedVar = B
453+
class Combo(Base2, Base1): ...
454+
[out]
455+
main:9: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"
456+
457+
[case testDoNotFailIfBothNestedClassesInheritFromAny]
458+
from typing import Any
459+
class Mixin1:
460+
class Meta(Any):
461+
pass
462+
class Mixin2:
463+
class Meta(Any):
464+
pass
465+
class A(Mixin1, Mixin2):
466+
pass
467+
[out]
468+
469+
[case testDoNotFailIfOneOfNestedClassesIsOuterInheritedFromAny]
470+
from typing import Any
471+
class Outer(Any):
472+
pass
473+
class Mixin1:
474+
Meta = Outer
475+
class Mixin2:
476+
class Meta(Any):
477+
pass
478+
class A(Mixin1, Mixin2):
479+
pass
480+
[out]

0 commit comments

Comments
 (0)