Skip to content

Commit 9fe8046

Browse files
committed
address comments
1 parent 9151572 commit 9fe8046

File tree

2 files changed

+96
-9
lines changed

2 files changed

+96
-9
lines changed

mypy/checker.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,7 +1605,7 @@ 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]:
1608+
def determine_type_of_class_member(self, sym: SymbolTableNode) -> Optional[Type]:
16091609
if sym.type is not None:
16101610
return sym.type
16111611
if isinstance(sym.node, FuncBase):
@@ -1629,18 +1629,41 @@ def check_compatibility(self, name: str, base1: TypeInfo,
16291629
first = base1[name]
16301630
second = base2[name]
16311631
if isinstance(first.node, TypeInfo) and isinstance(second.node, TypeInfo):
1632-
# allow nested classes with the same name
1632+
# Checking compatibility of two nested classes with the same name is disabled.
1633+
# It creates a lot of false positives in frameworks like Django, DRF and others,
1634+
# where nested classes is used to define custom properties for the outer class, like
1635+
# class MyModel:
1636+
# class Meta:
1637+
# abstract = True
1638+
#
1639+
# This is technically unsafe, it create false-negatives in cases like
1640+
# class Mixin1:
1641+
# class Meta:
1642+
# def get() -> int: pass
1643+
# class Mixin2:
1644+
# class Meta:
1645+
# def get() -> str: pass
1646+
# class A(Mixin2, Mixin1):
1647+
# pass
1648+
# var: Mixin1 = A(); var.Meta.get() # return type will be "str"
16331649
return
1634-
first_type = self._determine_type_of_class_member(first)
1635-
second_type = self._determine_type_of_class_member(second)
1650+
first_type = self.determine_type_of_class_member(first)
1651+
second_type = self.determine_type_of_class_member(second)
16361652

16371653
# TODO: What if some classes are generic?
16381654
if (isinstance(first_type, FunctionLike) and
16391655
isinstance(second_type, FunctionLike)):
1640-
# Method override
1641-
first_sig = bind_self(first_type)
1642-
second_sig = bind_self(second_type)
1643-
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
1656+
if ((isinstance(first_type, CallableType)
1657+
and first_type.fallback.type.fullname() == 'builtins.type')
1658+
and (isinstance(second_type, CallableType)
1659+
and second_type.fallback.type.fullname() == 'builtins.type')):
1660+
# Both members are classes (not necessary nested), check if compatible
1661+
ok = is_subtype(first_type.ret_type, second_type.ret_type)
1662+
else:
1663+
# Method override
1664+
first_sig = bind_self(first_type)
1665+
second_sig = bind_self(second_type)
1666+
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
16441667
elif first_type and second_type:
16451668
ok = is_equivalent(first_type, second_type)
16461669
else:

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

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,68 @@ class Mixin2:
273273
class A(Mixin1, Mixin2):
274274
pass
275275
[out]
276-
main:7: error: Definition of "Nested1" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
276+
main:7: error: Definition of "Nested1" in base class "Mixin1" is incompatible with definition in base class "Mixin2"
277+
278+
[case testMultipleInheritanceOneClassIsNestedAndAnotherIsRefToOther]
279+
class Outer:
280+
pass
281+
class Mixin1:
282+
class Nested1:
283+
pass
284+
class Mixin2:
285+
Nested1 = Outer
286+
class A(Mixin2, Mixin1):
287+
pass
288+
[out]
289+
main:8: error: Definition of "Nested1" in base class "Mixin2" is incompatible with definition in base class "Mixin1"
290+
291+
[case testNestedVariableRefersToSubclassesFromSameInheritanceChain]
292+
class A:
293+
def __init__(self, arg: str) -> None:
294+
pass
295+
class B(A):
296+
pass
297+
class Base1:
298+
NestedVar = A
299+
class Base2:
300+
NestedVar = B
301+
class Combo(Base2, Base1): ...
302+
[out]
303+
304+
[case testNestedVariableRefersToSuperlassUnderSubclass]
305+
class A:
306+
def __init__(self, arg: str) -> None:
307+
pass
308+
class B(A):
309+
pass
310+
class Base1:
311+
NestedVar = B
312+
class Base2:
313+
NestedVar = A
314+
class Combo(Base2, Base1): ...
315+
[out]
316+
main:10: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"
317+
318+
[case testNestedVariableRefersToSubclassOfAnotherNestedClass]
319+
class Mixin1:
320+
class Meta:
321+
pass
322+
class Outer(Mixin1.Meta):
323+
pass
324+
class Mixin2:
325+
NestedVar = Outer
326+
class Combo(Mixin2, Mixin1): ...
327+
[out]
328+
329+
[case testNestedVariableRefersToCompletelyDifferentClasses]
330+
class A:
331+
pass
332+
class B:
333+
pass
334+
class Base1:
335+
NestedVar = A
336+
class Base2:
337+
NestedVar = B
338+
class Combo(Base2, Base1): ...
339+
[out]
340+
main:9: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"

0 commit comments

Comments
 (0)