Skip to content

Refactoring: Make the state of type forward references explicit #4092

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
Oct 10, 2017
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
5 changes: 4 additions & 1 deletion mypy/indirection.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,7 @@ def visit_type_type(self, t: types.TypeType) -> Set[str]:
return self._visit(t.item)

def visit_forwardref_type(self, t: types.ForwardRef) -> Set[str]:
return self._visit(t.link)
if t.resolved:
return self._visit(t.resolved)
else:
return set()
5 changes: 4 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,10 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str:
elif isinstance(typ, TypeType):
return 'Type[{}]'.format(self.format_bare(typ.item, verbosity))
elif isinstance(typ, ForwardRef): # may appear in semanal.py
return self.format_bare(typ.link, verbosity)
if typ.resolved:
return self.format_bare(typ.resolved, verbosity)
else:
return self.format_bare(typ.unbound, verbosity)
elif isinstance(typ, FunctionLike):
func = typ
if func.is_type_obj():
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4836,7 +4836,9 @@ def visit_forwardref_type(self, t: ForwardRef) -> Type:
# its content is updated in ThirdPass, now we need to unwrap this type.
A = NewType('A', int)
"""
return t.link.accept(self)
assert t.resolved, 'Internal error: Unresolved forward reference: {}'.format(
t.unbound.name)
return t.resolved.accept(self)

def visit_instance(self, t: Instance, from_fallback: bool = False) -> Type:
"""This visitor method tracks situations like this:
Expand Down
2 changes: 1 addition & 1 deletion mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def visit_type_type(self, typ: TypeType) -> List[str]:
return []

def visit_forwardref_type(self, typ: ForwardRef) -> List[str]:
return get_type_dependencies(typ.link)
assert False, 'Internal error: Leaked forward reference object {}'.format(typ)

def visit_type_var(self, typ: TypeVarType) -> List[str]:
# TODO: replace with actual implementation
Expand Down
20 changes: 12 additions & 8 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,8 @@ def visit_instance(self, t: Instance) -> None:
arg_values = [arg]
self.check_type_var_values(info, arg_values, tvar.name, tvar.values, i + 1, t)
# TODO: These hacks will be not necessary when this will be moved to later stage.
arg = self.update_type(arg)
bound = self.update_type(tvar.upper_bound)
arg = self.resolve_type(arg)
bound = self.resolve_type(tvar.upper_bound)
if not is_subtype(arg, bound):
self.fail('Type argument "{}" of "{}" must be '
'a subtype of "{}"'.format(
Expand All @@ -719,9 +719,10 @@ def visit_instance(self, t: Instance) -> None:
def check_type_var_values(self, type: TypeInfo, actuals: List[Type], arg_name: str,
valids: List[Type], arg_number: int, context: Context) -> None:
for actual in actuals:
actual = self.update_type(actual)
actual = self.resolve_type(actual)
if (not isinstance(actual, AnyType) and
not any(is_same_type(actual, self.update_type(value)) for value in valids)):
not any(is_same_type(actual, self.resolve_type(value))
for value in valids)):
if len(actuals) > 1 or not isinstance(actual, Instance):
self.fail('Invalid type argument value for "{}"'.format(
type.name()), context)
Expand All @@ -731,11 +732,13 @@ def check_type_var_values(self, type: TypeInfo, actuals: List[Type], arg_name: s
self.fail(messages.INCOMPATIBLE_TYPEVAR_VALUE.format(
arg_name, class_name, actual_type_name), context)

def update_type(self, tp: Type) -> Type:
def resolve_type(self, tp: Type) -> Type:
# This helper is only needed while is_subtype and is_same_type are
# called in third pass. This can be removed when TODO in visit_instance is fixed.
if isinstance(tp, ForwardRef):
tp = tp.link
if tp.resolved is None:
return tp.unbound
tp = tp.resolved
if isinstance(tp, Instance) and tp.type.replaced:
replaced = tp.type.replaced
if replaced.tuple_type:
Expand Down Expand Up @@ -799,8 +802,9 @@ def visit_type_type(self, t: TypeType) -> None:

def visit_forwardref_type(self, t: ForwardRef) -> None:
self.indicator['forward'] = True
if isinstance(t.link, UnboundType):
t.link = self.anal_type(t.link)
if t.resolved is None:
resolved = self.anal_type(t.unbound)
t.resolve(resolved)

def anal_type(self, tp: UnboundType) -> Type:
tpan = TypeAnalyser(self.lookup_func,
Expand Down
40 changes: 29 additions & 11 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1390,21 +1390,33 @@ class ForwardRef(Type):
So that ForwardRefs are temporary and will be completely replaced with the linked types
or Any (to avoid cyclic references) before the type checking stage.
"""
link = None # type: Type # The wrapped type
_unbound = None # type: UnboundType # The original wrapped type
_resolved = None # type: Optional[Type] # The resolved forward reference (initially None)

def __init__(self, link: Type) -> None:
self.link = link
def __init__(self, unbound: UnboundType) -> None:
self._unbound = unbound
self._resolved = None

@property
def unbound(self) -> UnboundType:
# This is read-only to make it clear that resolution happens through resolve().
return self._unbound

@property
def resolved(self) -> Optional[Type]:
# Similar to above.
return self._resolved

def resolve(self, resolved: Type) -> None:
"""Resolve an unbound forward reference to point to a type."""
assert self._resolved is None
self._resolved = resolved

def accept(self, visitor: 'TypeVisitor[T]') -> T:
return visitor.visit_forwardref_type(self)

def serialize(self):
if isinstance(self.link, UnboundType):
name = self.link.name
if isinstance(self.link, Instance):
name = self.link.type.name()
else:
name = self.link.__class__.__name__
name = self.unbound.name
# We should never get here since all forward references should be resolved
# and removed during semantic analysis.
assert False, "Internal error: Unresolved forward reference to {}".format(name)
Expand Down Expand Up @@ -1749,7 +1761,10 @@ def visit_type_type(self, t: TypeType) -> str:
return 'Type[{}]'.format(t.item.accept(self))

def visit_forwardref_type(self, t: ForwardRef) -> str:
return '~{}'.format(t.link.accept(self))
if t.resolved:
return '~{}'.format(t.resolved.accept(self))
else:
return '~{}'.format(t.unbound.accept(self))

def list_str(self, a: List[Type]) -> str:
"""Convert items of an array to strings (pretty-print types)
Expand Down Expand Up @@ -1831,7 +1846,10 @@ def visit_type_type(self, t: TypeType) -> T:
return t.item.accept(self)

def visit_forwardref_type(self, t: ForwardRef) -> T:
return t.link.accept(self)
if t.resolved:
return t.resolved.accept(self)
else:
return t.unbound.accept(self)

def visit_ellipsis_type(self, t: EllipsisType) -> T:
return self.strategy([])
Expand Down