diff --git a/mypy/indirection.py b/mypy/indirection.py index badbe38cae38..a40b718b2b46 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -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() diff --git a/mypy/messages.py b/mypy/messages.py index bdde2e1bbc0b..094244920410 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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(): diff --git a/mypy/semanal.py b/mypy/semanal.py index 555d8cadd4f3..05eb1eb57c58 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 0402a511a7ab..05ef0fb9e0ed 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -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 diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3119c19a05e3..5aa4f1f38d42 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -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( @@ -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) @@ -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: @@ -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, diff --git a/mypy/types.py b/mypy/types.py index dcc845922419..5efb145e9e7e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -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) @@ -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) @@ -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([])