From 2a550d8dcb35ce015191bb01995c680af370a788 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 9 Aug 2019 21:08:49 -0700 Subject: [PATCH 1/2] Make TypeVisitor and SyntheticTypeVisitor fully abstract This pull request resolves https://github.com/python/mypy/issues/730 -- it makes all TypeVisitor and SyntheticTypeVisitor methods abstract. This thankfully required fairly minimal changes. I ended up needing to make only three non-trivial changes: 1. The visitor for PlaceholderType was moved out of TypeVisitor into SyntheticTypeVisitor. My understanding is that PlaceholderType is a semantic analysis only type, so this ought to be safe. 2. As a consequence of 1, I ended up removing the existing PlaceholderType visitor method from TypeTranslator. (It seems that method was added about 6 months ago when PlaceholderType was first introduced, and was untouched since then). 3. TypeIndirectionVisitor was turned from a SyntheticTypeVisitor into a TypeVisitor. I believe we call this visitor only in the final pass, after the type-checking phase. --- mypy/fixup.py | 3 +++ mypy/indirection.py | 19 ++----------------- mypy/server/astmerge.py | 11 +++++++++-- mypy/server/deps.py | 5 ++++- mypy/type_visitor.py | 22 ++++++++-------------- mypy/typeanal.py | 10 ++++++++-- mypy/types.py | 1 + 7 files changed, 35 insertions(+), 36 deletions(-) diff --git a/mypy/fixup.py b/mypy/fixup.py index c9a45b890b92..559454f497db 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -185,6 +185,9 @@ def visit_overloaded(self, t: Overloaded) -> None: for ct in t.items(): ct.accept(self) + def visit_erased_type(self, o: Any) -> None: + pass # Nothing to descend into. + def visit_deleted_type(self, o: Any) -> None: pass # Nothing to descend into. diff --git a/mypy/indirection.py b/mypy/indirection.py index b565d53fe61c..0d5b3135560b 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -1,6 +1,6 @@ from typing import Dict, Iterable, List, Optional, Set, Union -from mypy.types import SyntheticTypeVisitor +from mypy.types import TypeVisitor import mypy.types as types from mypy.util import split_module_names @@ -15,7 +15,7 @@ def extract_module_names(type_name: Optional[str]) -> List[str]: return [] -class TypeIndirectionVisitor(SyntheticTypeVisitor[Set[str]]): +class TypeIndirectionVisitor(TypeVisitor[Set[str]]): """Returns all module references within a particular type.""" def __init__(self) -> None: @@ -39,12 +39,6 @@ def _visit(self, typ_or_typs: Union[types.Type, Iterable[types.Type]]) -> Set[st def visit_unbound_type(self, t: types.UnboundType) -> Set[str]: return self._visit(t.args) - def visit_type_list(self, t: types.TypeList) -> Set[str]: - return self._visit(t.items) - - def visit_callable_argument(self, t: types.CallableArgument) -> Set[str]: - return self._visit(t.typ) - def visit_any(self, t: types.AnyType) -> Set[str]: return set() @@ -90,23 +84,14 @@ def visit_tuple_type(self, t: types.TupleType) -> Set[str]: def visit_typeddict_type(self, t: types.TypedDictType) -> Set[str]: return self._visit(t.items.values()) | self._visit(t.fallback) - def visit_raw_expression_type(self, t: types.RawExpressionType) -> Set[str]: - assert False, "Unexpected RawExpressionType after semantic analysis phase" - def visit_literal_type(self, t: types.LiteralType) -> Set[str]: return self._visit(t.fallback) - def visit_star_type(self, t: types.StarType) -> Set[str]: - return set() - def visit_union_type(self, t: types.UnionType) -> Set[str]: return self._visit(t.items) def visit_partial_type(self, t: types.PartialType) -> Set[str]: return set() - def visit_ellipsis_type(self, t: types.EllipsisType) -> Set[str]: - return set() - def visit_type_type(self, t: types.TypeType) -> Set[str]: return self._visit(t.item) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 41b483a56502..5eb8d57debf8 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -56,10 +56,10 @@ ) from mypy.traverser import TraverserVisitor from mypy.types import ( - Type, SyntheticTypeVisitor, Instance, AnyType, NoneType, CallableType, DeletedType, + Type, SyntheticTypeVisitor, Instance, AnyType, NoneType, CallableType, ErasedType, DeletedType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, Overloaded, TypeVarDef, TypeList, CallableArgument, EllipsisType, StarType, LiteralType, - RawExpressionType, PartialType, + RawExpressionType, PartialType, PlaceholderType, ) from mypy.util import get_prefix, replace_object_state from mypy.typestate import TypeState @@ -373,6 +373,9 @@ def visit_overloaded(self, t: Overloaded) -> None: if t.fallback is not None: t.fallback.accept(self) + def visit_erased_type(self, t: ErasedType) -> None: + pass + def visit_deleted_type(self, typ: DeletedType) -> None: pass @@ -429,6 +432,10 @@ def visit_union_type(self, typ: UnionType) -> None: for item in typ.items: item.accept(self) + def visit_placeholder_type(self, t: PlaceholderType) -> None: + for item in t.args: + item.accept(self) + # Helpers def fixup(self, node: SN) -> SN: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 885fb1709711..b5ec8d52f105 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -96,7 +96,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a from mypy.types import ( Type, Instance, AnyType, NoneType, TypeVisitor, CallableType, DeletedType, PartialType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, - FunctionLike, Overloaded, TypeOfAny, LiteralType, + FunctionLike, Overloaded, TypeOfAny, LiteralType, ErasedType, ) from mypy.server.trigger import make_trigger, make_wildcard_trigger from mypy.util import correct_relative_import @@ -900,6 +900,9 @@ def visit_overloaded(self, typ: Overloaded) -> List[str]: triggers.extend(self.get_type_triggers(item)) return triggers + def visit_erased_type(self, t: ErasedType) -> List[str]: + return [] + def visit_deleted_type(self, typ: DeletedType) -> List[str]: return [] diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 3c8a0545f154..53757c7f7d0f 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -34,12 +34,6 @@ class TypeVisitor(Generic[T]): The parameter T is the return type of the visit methods. """ - def _notimplemented_helper(self, name: str) -> NotImplementedError: - return NotImplementedError("Method {}.visit_{}() not implemented\n" - .format(type(self).__name__, name) - + "This is a known bug, track development in " - + "'https://github.com/JukkaL/mypy/issues/730'") - @abstractmethod def visit_unbound_type(self, t: UnboundType) -> T: pass @@ -56,8 +50,9 @@ def visit_none_type(self, t: NoneType) -> T: def visit_uninhabited_type(self, t: UninhabitedType) -> T: pass + @abstractmethod def visit_erased_type(self, t: ErasedType) -> T: - raise self._notimplemented_helper('erased_type') + pass @abstractmethod def visit_deleted_type(self, t: DeletedType) -> T: @@ -75,8 +70,9 @@ def visit_instance(self, t: Instance) -> T: def visit_callable_type(self, t: CallableType) -> T: pass + @abstractmethod def visit_overloaded(self, t: Overloaded) -> T: - raise self._notimplemented_helper('overloaded') + pass @abstractmethod def visit_tuple_type(self, t: TupleType) -> T: @@ -102,9 +98,6 @@ def visit_partial_type(self, t: PartialType) -> T: def visit_type_type(self, t: TypeType) -> T: pass - def visit_placeholder_type(self, t: PlaceholderType) -> T: - raise RuntimeError('Internal error: unresolved placeholder type {}'.format(t.fullname)) - @trait class SyntheticTypeVisitor(TypeVisitor[T]): @@ -132,6 +125,10 @@ def visit_ellipsis_type(self, t: EllipsisType) -> T: def visit_raw_expression_type(self, t: RawExpressionType) -> T: pass + @abstractmethod + def visit_placeholder_type(self, t: PlaceholderType) -> T: + pass + @trait class TypeTranslator(TypeVisitor[Type]): @@ -234,9 +231,6 @@ def visit_overloaded(self, t: Overloaded) -> Type: def visit_type_type(self, t: TypeType) -> Type: return TypeType.make_normalized(t.item.accept(self), line=t.line, column=t.column) - def visit_placeholder_type(self, t: PlaceholderType) -> Type: - return PlaceholderType(t.fullname, self.translate_types(t.args), t.line) - @trait class TypeQuery(SyntheticTypeVisitor[T]): diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 7e8fc3b92909..cef5dee97e75 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -12,10 +12,10 @@ from mypy.options import Options from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType, - CallableType, NoneType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor, + CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args, CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, - LiteralType, RawExpressionType, PlaceholderType + LiteralType, RawExpressionType, PlaceholderType, Overloaded ) from mypy.nodes import ( @@ -449,6 +449,9 @@ def visit_none_type(self, t: NoneType) -> Type: def visit_uninhabited_type(self, t: UninhabitedType) -> Type: return t + def visit_erased_type(self, t: ErasedType) -> Type: + return t + def visit_deleted_type(self, t: DeletedType) -> Type: return t @@ -483,6 +486,9 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: variables=self.anal_var_defs(variables)) return ret + def visit_overloaded(self, t: Overloaded) -> Type: + return t + def visit_tuple_type(self, t: TupleType) -> Type: # Types such as (t1, t2, ...) only allowed in assignment statements. They'll # generate errors elsewhere, and Tuple[t1, t2, ...] must be used instead. diff --git a/mypy/types.py b/mypy/types.py index 071b6decf54f..0f2156a2e095 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1830,6 +1830,7 @@ def __init__(self, fullname: Optional[str], args: List[Type], line: int) -> None self.args = args def accept(self, visitor: 'TypeVisitor[T]') -> T: + assert isinstance(visitor, SyntheticTypeVisitor) return visitor.visit_placeholder_type(self) def serialize(self) -> str: From d27ab28eafab44fb22fdb3ad6df53460c91c4a1c Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 16 Aug 2019 13:03:36 -0700 Subject: [PATCH 2/2] Respond to code review --- mypy/fixup.py | 3 ++- mypy/server/astmerge.py | 3 ++- mypy/server/deps.py | 3 ++- mypy/typeanal.py | 8 +++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/mypy/fixup.py b/mypy/fixup.py index 559454f497db..f290dbab3b60 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -186,7 +186,8 @@ def visit_overloaded(self, t: Overloaded) -> None: ct.accept(self) def visit_erased_type(self, o: Any) -> None: - pass # Nothing to descend into. + # This type should exist only temporarily during type inference + raise RuntimeError("Shouldn't get here", o) def visit_deleted_type(self, o: Any) -> None: pass # Nothing to descend into. diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 5eb8d57debf8..ab3ce0e56c56 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -374,7 +374,8 @@ def visit_overloaded(self, t: Overloaded) -> None: t.fallback.accept(self) def visit_erased_type(self, t: ErasedType) -> None: - pass + # This type should exist only temporarily during type inference + raise RuntimeError def visit_deleted_type(self, typ: DeletedType) -> None: pass diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 82db4c024b19..db457e3e9c72 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -902,7 +902,8 @@ def visit_overloaded(self, typ: Overloaded) -> List[str]: return triggers def visit_erased_type(self, t: ErasedType) -> List[str]: - return [] + # This type should exist only temporarily during type inference + assert False, "Should not see an erased type here" def visit_deleted_type(self, typ: DeletedType) -> List[str]: return [] diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 69c1582e70da..27d8f78c6a61 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -452,7 +452,8 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> Type: return t def visit_erased_type(self, t: ErasedType) -> Type: - return t + # This type should exist only temporarily during type inference + assert False, "Internal error: Unexpected erased type" def visit_deleted_type(self, t: DeletedType) -> Type: return t @@ -489,6 +490,11 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: return ret def visit_overloaded(self, t: Overloaded) -> Type: + # Overloaded types are manually constructed in semanal.py by analyzing the + # AST and combining together the Callable types this visitor converts. + # + # So if we're ever asked to reanalyze an Overloaded type, we know it's + # fine to just return it as-is. return t def visit_tuple_type(self, t: TupleType) -> Type: