Skip to content

Refactor: move MRO calculation to a new module #6133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
from mypy.constraints import SUPERTYPE_OF
from mypy.maptype import map_instance_to_supertype
from mypy.typevars import fill_typevars, has_no_typevars
from mypy.semanal import set_callable_name, refers_to_fullname, calculate_mro
from mypy.semanal import set_callable_name, refers_to_fullname
from mypy.mro import calculate_mro
from mypy.erasetype import erase_typevars
from mypy.expandtype import expand_type, expand_type_by_instance
from mypy.visitor import NodeVisitor
Expand Down
61 changes: 61 additions & 0 deletions mypy/mro.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import Optional, Callable, List

from mypy.nodes import TypeInfo
from mypy.types import Instance
from mypy.typestate import TypeState


def calculate_mro(info: TypeInfo, obj_type: Optional[Callable[[], Instance]] = None) -> None:
"""Calculate and set mro (method resolution order).

Raise MroError if cannot determine mro.
"""
mro = linearize_hierarchy(info, obj_type)
assert mro, "Could not produce a MRO at all for %s" % (info,)
info.mro = mro
# The property of falling back to Any is inherited.
info.fallback_to_any = any(baseinfo.fallback_to_any for baseinfo in info.mro)
TypeState.reset_all_subtype_caches_for(info)


class MroError(Exception):
"""Raised if a consistent mro cannot be determined for a class."""


def linearize_hierarchy(info: TypeInfo,
obj_type: Optional[Callable[[], Instance]] = None) -> List[TypeInfo]:
# TODO describe
if info.mro:
return info.mro
bases = info.direct_base_classes()
if (not bases and info.fullname() != 'builtins.object' and
obj_type is not None):
# Second pass in import cycle, add a dummy `object` base class,
# otherwise MRO calculation may spuriously fail.
# MRO will be re-calculated for real in the third pass.
bases = [obj_type().type]
lin_bases = []
for base in bases:
assert base is not None, "Cannot linearize bases for %s %s" % (info.fullname(), bases)
lin_bases.append(linearize_hierarchy(base, obj_type))
lin_bases.append(bases)
return [info] + merge(lin_bases)


def merge(seqs: List[List[TypeInfo]]) -> List[TypeInfo]:
seqs = [s[:] for s in seqs]
result = [] # type: List[TypeInfo]
while True:
seqs = [s for s in seqs if s]
if not seqs:
return result
for seq in seqs:
head = seq[0]
if not [s for s in seqs if head in s[1:]]:
break
else:
raise MroError()
result.append(head)
for s in seqs:
if s[0] is head:
del s[0]
58 changes: 1 addition & 57 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE,
MYPY_TRUE, MYPY_FALSE
)
from mypy.typestate import TypeState
from mypy.mro import calculate_mro, MroError
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, third pass does self.sem.calculate_class_mro(tdef), maybe we can also import calculate_mro there to reduce coupling between second and third passes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea. I'll do this in a separate PR since this requires moving some code around.


MYPY = False
if MYPY:
Expand Down Expand Up @@ -3732,62 +3732,6 @@ def refers_to_class_or_function(node: Expression) -> bool:
isinstance(node.node, (TypeInfo, FuncDef, OverloadedFuncDef)))


def calculate_mro(info: TypeInfo, obj_type: Optional[Callable[[], Instance]] = None) -> None:
"""Calculate and set mro (method resolution order).

Raise MroError if cannot determine mro.
"""
mro = linearize_hierarchy(info, obj_type)
assert mro, "Could not produce a MRO at all for %s" % (info,)
info.mro = mro
# The property of falling back to Any is inherited.
info.fallback_to_any = any(baseinfo.fallback_to_any for baseinfo in info.mro)
TypeState.reset_all_subtype_caches_for(info)


class MroError(Exception):
"""Raised if a consistent mro cannot be determined for a class."""


def linearize_hierarchy(info: TypeInfo,
obj_type: Optional[Callable[[], Instance]] = None) -> List[TypeInfo]:
# TODO describe
if info.mro:
return info.mro
bases = info.direct_base_classes()
if (not bases and info.fullname() != 'builtins.object' and
obj_type is not None):
# Second pass in import cycle, add a dummy `object` base class,
# otherwise MRO calculation may spuriously fail.
# MRO will be re-calculated for real in the third pass.
bases = [obj_type().type]
lin_bases = []
for base in bases:
assert base is not None, "Cannot linearize bases for %s %s" % (info.fullname(), bases)
lin_bases.append(linearize_hierarchy(base, obj_type))
lin_bases.append(bases)
return [info] + merge(lin_bases)


def merge(seqs: List[List[TypeInfo]]) -> List[TypeInfo]:
seqs = [s[:] for s in seqs]
result = [] # type: List[TypeInfo]
while True:
seqs = [s for s in seqs if s]
if not seqs:
return result
for seq in seqs:
head = seq[0]
if not [s for s in seqs if head in s[1:]]:
break
else:
raise MroError()
result.append(head)
for s in seqs:
if s[0] is head:
del s[0]


def find_duplicate(list: List[T]) -> Optional[T]:
"""If the list has duplicates, return one of the duplicates.

Expand Down