Skip to content

Commit 33559c0

Browse files
rwbartonJukkaL
authored andcommitted
Recompute class MROs in ThirdPass, to handle import loops (#1397)
Fixes #1394. This is a bit of a hack, but at least it's simple.
1 parent 1c9b3b9 commit 33559c0

File tree

2 files changed

+60
-15
lines changed

2 files changed

+60
-15
lines changed

mypy/semanal.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
it will reject Dict[int]. We don't do this in the second pass,
3131
since we infer the type argument counts of classes during this
3232
pass, and it is possible to refer to classes defined later in a
33-
file, which would not have the type argument count set yet.
33+
file, which would not have the type argument count set yet. This
34+
pass also recomputes the method resolution order of each class, in
35+
case one of its bases belongs to a module involved in an import
36+
loop.
3437
3538
Semantic analysis of types is implemented in module mypy.typeanal.
3639
@@ -41,7 +44,7 @@
4144
"""
4245

4346
from typing import (
44-
List, Dict, Set, Tuple, cast, Any, overload, TypeVar, Union, Optional
47+
List, Dict, Set, Tuple, cast, Any, overload, TypeVar, Union, Optional, Callable
4548
)
4649

4750
from mypy.nodes import (
@@ -753,22 +756,17 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
753756
obj = self.object_type()
754757
defn.base_types.insert(0, obj)
755758
defn.info.bases = defn.base_types
759+
# Calculate the MRO. It might be incomplete at this point if
760+
# the bases of defn include classes imported from other
761+
# modules in an import loop. We'll recompute it in ThirdPass.
756762
if not self.verify_base_classes(defn):
757763
defn.info.mro = []
758764
return
759-
try:
760-
defn.info.calculate_mro()
761-
except MroError:
762-
self.fail("Cannot determine consistent method resolution order "
763-
'(MRO) for "%s"' % defn.name, defn)
764-
defn.info.mro = []
765-
else:
766-
# If there are cyclic imports, we may be missing 'object' in
767-
# the MRO. Fix MRO if needed.
768-
if defn.info.mro[-1].fullname() != 'builtins.object':
769-
defn.info.mro.append(self.object_type().type)
770-
# The property of falling back to Any is inherited.
771-
defn.info.fallback_to_any = any(baseinfo.fallback_to_any for baseinfo in defn.info.mro)
765+
calculate_class_mro(defn, self.fail)
766+
# If there are cyclic imports, we may be missing 'object' in
767+
# the MRO. Fix MRO if needed.
768+
if defn.info.mro and defn.info.mro[-1].fullname() != 'builtins.object':
769+
defn.info.mro.append(self.object_type().type)
772770

773771
def expr_to_analyzed_type(self, expr: Node) -> Type:
774772
if isinstance(expr, CallExpr):
@@ -2437,6 +2435,12 @@ def visit_func_def(self, fdef: FuncDef) -> None:
24372435
def visit_class_def(self, tdef: ClassDef) -> None:
24382436
for type in tdef.info.bases:
24392437
self.analyze(type)
2438+
# Recompute MRO now that we have analyzed all modules, to pick
2439+
# up superclasses of bases imported from other modules in an
2440+
# import loop. (Only do so if we succeeded the first time.)
2441+
if tdef.info.mro:
2442+
tdef.info.mro = [] # Force recomputation
2443+
calculate_class_mro(tdef, self.fail)
24402444
super().visit_class_def(tdef)
24412445

24422446
def visit_decorator(self, dec: Decorator) -> None:
@@ -2569,6 +2573,17 @@ def refers_to_class_or_function(node: Node) -> bool:
25692573
OverloadedFuncDef)))
25702574

25712575

2576+
def calculate_class_mro(defn: ClassDef, fail: Callable[[str, Context], None]) -> None:
2577+
try:
2578+
defn.info.calculate_mro()
2579+
except MroError:
2580+
fail("Cannot determine consistent method resolution order "
2581+
'(MRO) for "%s"' % defn.name, defn)
2582+
defn.info.mro = []
2583+
# The property of falling back to Any is inherited.
2584+
defn.info.fallback_to_any = any(baseinfo.fallback_to_any for baseinfo in defn.info.mro)
2585+
2586+
25722587
def find_duplicate(list: List[T]) -> T:
25732588
"""If the list has duplicates, return one of the duplicates.
25742589

mypy/test/data/check-modules.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,3 +739,33 @@ y = 42
739739
[builtins fixtures/module.py]
740740
[out]
741741
tmp/main.py:4: error: Unsupported left operand type for + ("int")
742+
743+
[case testSuperclassInImportCycle]
744+
import a
745+
import d
746+
a.A().f(d.D())
747+
[file a.py]
748+
if 0:
749+
import d
750+
class B: pass
751+
class C(B): pass
752+
class A:
753+
def f(self, x: B) -> None: pass
754+
[file d.py]
755+
import a
756+
class D(a.C): pass
757+
758+
[case testSuperclassInImportCycleReversedImports]
759+
import d
760+
import a
761+
a.A().f(d.D())
762+
[file a.py]
763+
if 0:
764+
import d
765+
class B: pass
766+
class C(B): pass
767+
class A:
768+
def f(self, x: B) -> None: pass
769+
[file d.py]
770+
import a
771+
class D(a.C): pass

0 commit comments

Comments
 (0)