From e1bd6cf3c06f5d96cd42fc86ed740586e278018d Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 5 Sep 2021 18:32:36 -0700 Subject: [PATCH 1/8] make typeguard an improper type --- mypy/binder.py | 4 +-- mypy/checker.py | 4 +-- mypy/checkexpr.py | 6 +++- mypy/constraints.py | 5 +--- mypy/erasetype.py | 5 +--- mypy/expandtype.py | 5 +--- mypy/fixup.py | 5 +--- mypy/indirection.py | 3 -- mypy/join.py | 5 +--- mypy/meet.py | 16 ++--------- mypy/sametypes.py | 8 +----- mypy/server/astdiff.py | 5 +--- mypy/server/astmerge.py | 5 +--- mypy/server/deps.py | 5 +--- mypy/subtypes.py | 13 +-------- mypy/type_visitor.py | 13 +-------- mypy/typeanal.py | 5 +--- mypy/types.py | 22 ++++++--------- mypy/typetraverser.py | 5 +--- test-data/unit/check-typeguard.test | 44 +++++++++++++++++++++++++++++ 20 files changed, 77 insertions(+), 106 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index de12d153f6214..d8f751b66b071 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -5,7 +5,7 @@ from typing_extensions import DefaultDict from mypy.types import ( - Type, AnyType, PartialType, UnionType, TypeOfAny, NoneType, TypeGuardType, get_proper_type + Type, AnyType, PartialType, UnionType, TypeOfAny, NoneType, TypeGuardedType, get_proper_type ) from mypy.subtypes import is_subtype from mypy.join import join_simple @@ -444,7 +444,7 @@ def get_declaration(expr: BindableExpression) -> Optional[Type]: def contains_type_guard(other: Type) -> bool: # Ignore the error about using get_proper_type(). - if isinstance(other, TypeGuardType): # type: ignore[misc] + if isinstance(other, TypeGuardedType): # type: ignore[misc] return True other = get_proper_type(other) if isinstance(other, UnionType): diff --git a/mypy/checker.py b/mypy/checker.py index a143e46856ea0..2ee4c0a8ab753 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -36,7 +36,7 @@ UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, is_named_instance, union_items, TypeQuery, LiteralType, is_optional, remove_optional, TypeTranslator, StarType, get_proper_type, ProperType, - get_proper_types, is_literal_type, TypeAliasType, TypeGuardType) + get_proper_types, is_literal_type, TypeAliasType, TypeGuardedType) from mypy.sametypes import is_same_type from mypy.messages import ( MessageBuilder, make_inferred_type_note, append_invariance_notes, pretty_seq, @@ -4267,7 +4267,7 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM # considered "always right" (i.e. even if the types are not overlapping). # Also note that a care must be taken to unwrap this back at read places # where we use this to narrow down declared type. - return {expr: TypeGuardType(node.callee.type_guard)}, {} + return {expr: TypeGuardedType(node.callee.type_guard)}, {} elif isinstance(node, ComparisonExpr): # Step 1: Obtain the types of each operand and whether or not we can # narrow their types. (For example, we shouldn't try narrowing the diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index cc32717a27370..f455cf23128f9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -15,7 +15,7 @@ make_optional_type, ) from mypy.types import ( - Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType, + Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType, TypeGuardedType, TupleType, TypedDictType, Instance, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue, is_named_instance, FunctionLike, ParamSpecType, @@ -4175,6 +4175,10 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type, """ if literal(expr) >= LITERAL_TYPE: restriction = self.chk.binder.get(expr) + # Ignore the error about using get_proper_type(). + if isinstance(restriction, TypeGuardedType): # type: ignore[misc] + # A type guard forces the new type even if it doesn't overlap the old. + return restriction.type_guard # If the current node is deferred, some variables may get Any types that they # otherwise wouldn't have. We don't want to narrow down these since it may # produce invalid inferred Optional[Any] types, at least. diff --git a/mypy/constraints.py b/mypy/constraints.py index 48e1be357d3b6..d6aea5500f9fe 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -7,7 +7,7 @@ CallableType, Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarType, Instance, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId, TypeQuery, is_named_instance, TypeOfAny, LiteralType, - ProperType, get_proper_type, TypeAliasType, TypeGuardType + ProperType, get_proper_type, TypeAliasType ) from mypy.maptype import map_instance_to_supertype import mypy.subtypes @@ -544,9 +544,6 @@ def visit_union_type(self, template: UnionType) -> List[Constraint]: def visit_type_alias_type(self, template: TypeAliasType) -> List[Constraint]: assert False, "This should be never called, got {}".format(template) - def visit_type_guard_type(self, template: TypeGuardType) -> List[Constraint]: - assert False, "This should be never called, got {}".format(template) - def infer_against_any(self, types: Iterable[Type], any_type: AnyType) -> List[Constraint]: res: List[Constraint] = [] for t in types: diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 70b7c3b6de32a..7a56eceacf5f1 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -4,7 +4,7 @@ Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarId, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny, LiteralType, ProperType, - get_proper_type, TypeAliasType, TypeGuardType + get_proper_type, TypeAliasType ) from mypy.nodes import ARG_STAR, ARG_STAR2 @@ -90,9 +90,6 @@ def visit_union_type(self, t: UnionType) -> ProperType: from mypy.typeops import make_simplified_union return make_simplified_union(erased_items) - def visit_type_guard_type(self, t: TypeGuardType) -> ProperType: - return TypeGuardType(t.type_guard.accept(self)) - def visit_type_type(self, t: TypeType) -> ProperType: return TypeType.make_normalized(t.item.accept(self), line=t.line) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 5679d5802782a..6dc86bff1f352 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -1,7 +1,7 @@ from typing import Dict, Iterable, List, TypeVar, Mapping, cast from mypy.types import ( - Type, Instance, CallableType, TypeGuardType, TypeVisitor, UnboundType, AnyType, + Type, Instance, CallableType, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarType, Overloaded, TupleType, TypedDictType, UnionType, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId, FunctionLike, TypeVarType, LiteralType, get_proper_type, ProperType, @@ -129,9 +129,6 @@ def visit_union_type(self, t: UnionType) -> Type: from mypy.typeops import make_simplified_union # asdf return make_simplified_union(self.expand_types(t.items), t.line, t.column) - def visit_type_guard_type(self, t: TypeGuardType) -> ProperType: - return TypeGuardType(t.type_guard.accept(self)) - def visit_partial_type(self, t: PartialType) -> Type: return t diff --git a/mypy/fixup.py b/mypy/fixup.py index 948a1471ce414..b6d15b60e9e85 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -9,7 +9,7 @@ TypeVarExpr, ClassDef, Block, TypeAlias, ) from mypy.types import ( - CallableType, Instance, Overloaded, TupleType, TypeGuardType, TypedDictType, + CallableType, Instance, Overloaded, TupleType, TypedDictType, TypeVarType, UnboundType, UnionType, TypeVisitor, LiteralType, TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny ) @@ -254,9 +254,6 @@ def visit_union_type(self, ut: UnionType) -> None: for it in ut.items: it.accept(self) - def visit_type_guard_type(self, t: TypeGuardType) -> None: - t.type_guard.accept(self) - def visit_void(self, o: Any) -> None: pass # Nothing to descend into. diff --git a/mypy/indirection.py b/mypy/indirection.py index 952bccc36c06c..7dbd4d1307cb5 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -97,9 +97,6 @@ def visit_literal_type(self, t: types.LiteralType) -> Set[str]: def visit_union_type(self, t: types.UnionType) -> Set[str]: return self._visit(t.items) - def visit_type_guard_type(self, t: types.TypeGuardType) -> Set[str]: - return self._visit(t.type_guard) - def visit_partial_type(self, t: types.PartialType) -> Set[str]: return set() diff --git a/mypy/join.py b/mypy/join.py index 2cbc1a9edc8fa..09122940c3aa9 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -7,7 +7,7 @@ Type, AnyType, NoneType, TypeVisitor, Instance, UnboundType, TypeVarType, CallableType, TupleType, TypedDictType, ErasedType, UnionType, FunctionLike, Overloaded, LiteralType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, get_proper_type, - ProperType, get_proper_types, TypeAliasType, PlaceholderType, TypeGuardType + ProperType, get_proper_types, TypeAliasType, PlaceholderType ) from mypy.maptype import map_instance_to_supertype from mypy.subtypes import ( @@ -432,9 +432,6 @@ def visit_type_type(self, t: TypeType) -> ProperType: def visit_type_alias_type(self, t: TypeAliasType) -> ProperType: assert False, "This should be never called, got {}".format(t) - def visit_type_guard_type(self, t: TypeGuardType) -> ProperType: - assert False, "This should be never called, got {}".format(t) - def join(self, s: Type, t: Type) -> ProperType: return join_types(s, t) diff --git a/mypy/meet.py b/mypy/meet.py index 465b25dd7c0d0..59a29181c7775 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -5,7 +5,7 @@ Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, - ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardType + ProperType, get_proper_type, get_proper_types, TypeAliasType ) from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type @@ -56,11 +56,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: if declared == narrowed: return declared - # Ignore the error about using get_proper_type(). - if isinstance(narrowed, TypeGuardType): # type: ignore[misc] - # A type guard forces the new type even if it doesn't overlap the old. - return narrowed.type_guard - elif isinstance(declared, UnionType): + if isinstance(declared, UnionType): return make_simplified_union([narrow_declared_type(x, narrowed) for x in declared.relevant_items()]) elif not is_overlapping_types(declared, narrowed, @@ -161,11 +157,6 @@ def _is_overlapping_types(left: Type, right: Type) -> bool: if isinstance(left, PartialType) or isinstance(right, PartialType): assert False, "Unexpectedly encountered partial type" - # Ignore the error about using get_proper_type(). - if isinstance(left, TypeGuardType) or isinstance(right, TypeGuardType): # type: ignore[misc] - # A type guard forces the new type even if it doesn't overlap the old. - return True - # We should also never encounter these types, but it's possible a few # have snuck through due to unrelated bugs. For now, we handle these # in the same way we handle 'Any'. @@ -657,9 +648,6 @@ def visit_type_type(self, t: TypeType) -> ProperType: def visit_type_alias_type(self, t: TypeAliasType) -> ProperType: assert False, "This should be never called, got {}".format(t) - def visit_type_guard_type(self, t: TypeGuardType) -> ProperType: - assert False, "This should be never called, got {}".format(t) - def meet(self, s: Type, t: Type) -> ProperType: return meet_types(s, t) diff --git a/mypy/sametypes.py b/mypy/sametypes.py index f599cc2f7b142..8a04436a79ed3 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -1,7 +1,7 @@ from typing import Sequence from mypy.types import ( - Type, TypeGuardType, UnboundType, AnyType, NoneType, TupleType, TypedDictType, + Type, UnboundType, AnyType, NoneType, TupleType, TypedDictType, UnionType, CallableType, TypeVarType, Instance, TypeVisitor, ErasedType, Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, LiteralType, ProperType, get_proper_type, TypeAliasType) @@ -151,12 +151,6 @@ def visit_union_type(self, left: UnionType) -> bool: else: return False - def visit_type_guard_type(self, left: TypeGuardType) -> bool: - if isinstance(self.right, TypeGuardType): - return is_same_type(left.type_guard, self.right.type_guard) - else: - return False - def visit_overloaded(self, left: Overloaded) -> bool: if isinstance(self.right, Overloaded): return is_same_types(left.items(), self.right.items()) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 2588479eed132..7ea131218cbcf 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -57,7 +57,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' FuncBase, OverloadedFuncDef, FuncItem, MypyFile, UNBOUND_IMPORTED ) from mypy.types import ( - Type, TypeGuardType, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType, + Type, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType, ErasedType, DeletedType, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, PartialType, TypeType, LiteralType, TypeAliasType ) @@ -335,9 +335,6 @@ def visit_union_type(self, typ: UnionType) -> SnapshotItem: normalized = tuple(sorted(items)) return ('UnionType', normalized) - def visit_type_guard_type(self, typ: TypeGuardType) -> SnapshotItem: - return ('TypeGuardType', snapshot_type(typ.type_guard)) - def visit_overloaded(self, typ: Overloaded) -> SnapshotItem: return ('Overloaded', snapshot_types(typ.items())) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index cfcb01c2ee50e..c815ddcc0a119 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -59,7 +59,7 @@ Type, SyntheticTypeVisitor, Instance, AnyType, NoneType, CallableType, ErasedType, DeletedType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, Overloaded, TypeVarType, TypeList, CallableArgument, EllipsisType, StarType, LiteralType, - RawExpressionType, PartialType, PlaceholderType, TypeAliasType, TypeGuardType + RawExpressionType, PartialType, PlaceholderType, TypeAliasType ) from mypy.util import get_prefix, replace_object_state from mypy.typestate import TypeState @@ -389,9 +389,6 @@ def visit_erased_type(self, t: ErasedType) -> None: def visit_deleted_type(self, typ: DeletedType) -> None: pass - def visit_type_guard_type(self, typ: TypeGuardType) -> None: - raise RuntimeError - def visit_partial_type(self, typ: PartialType) -> None: raise RuntimeError diff --git a/mypy/server/deps.py b/mypy/server/deps.py index f67fda425ecdc..97095c9b2a74a 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -99,7 +99,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a Type, Instance, AnyType, NoneType, TypeVisitor, CallableType, DeletedType, PartialType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, FunctionLike, Overloaded, TypeOfAny, LiteralType, ErasedType, get_proper_type, ProperType, - TypeAliasType, TypeGuardType + TypeAliasType ) from mypy.server.trigger import make_trigger, make_wildcard_trigger from mypy.util import correct_relative_import @@ -973,9 +973,6 @@ def visit_unbound_type(self, typ: UnboundType) -> List[str]: def visit_uninhabited_type(self, typ: UninhabitedType) -> List[str]: return [] - def visit_type_guard_type(self, typ: TypeGuardType) -> List[str]: - return typ.type_guard.accept(self) - def visit_union_type(self, typ: UnionType) -> List[str]: triggers = [] for item in typ.items: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 49689db7ea412..a30436e3101ba 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -4,7 +4,7 @@ from typing_extensions import Final from mypy.types import ( - Type, AnyType, TypeGuardType, UnboundType, TypeVisitor, FormalArgument, NoneType, + Type, AnyType, UnboundType, TypeVisitor, FormalArgument, NoneType, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance, FunctionLike, TypeOfAny, LiteralType, get_proper_type, TypeAliasType @@ -475,9 +475,6 @@ def visit_overloaded(self, left: Overloaded) -> bool: def visit_union_type(self, left: UnionType) -> bool: return all(self._is_subtype(item, self.orig_right) for item in left.items) - def visit_type_guard_type(self, left: TypeGuardType) -> bool: - raise RuntimeError("TypeGuard should not appear here") - def visit_partial_type(self, left: PartialType) -> bool: # This is indeterminate as we don't really know the complete type yet. raise RuntimeError @@ -1377,14 +1374,6 @@ def visit_overloaded(self, left: Overloaded) -> bool: def visit_union_type(self, left: UnionType) -> bool: return all([self._is_proper_subtype(item, self.orig_right) for item in left.items]) - def visit_type_guard_type(self, left: TypeGuardType) -> bool: - if isinstance(self.right, TypeGuardType): - # TypeGuard[bool] is a subtype of TypeGuard[int] - return self._is_proper_subtype(left.type_guard, self.right.type_guard) - else: - # TypeGuards aren't a subtype of anything else for now (but see #10489) - return False - def visit_partial_type(self, left: PartialType) -> bool: # TODO: What's the right thing to do here? return False diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 0c5cfedcf3854..c9d8359906b95 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -19,7 +19,7 @@ T = TypeVar('T') from mypy.types import ( - Type, AnyType, CallableType, Overloaded, TupleType, TypeGuardType, TypedDictType, LiteralType, + Type, AnyType, CallableType, Overloaded, TupleType, TypedDictType, LiteralType, RawExpressionType, Instance, NoneType, TypeType, UnionType, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarLikeType, UnboundType, ErasedType, StarType, EllipsisType, TypeList, CallableArgument, @@ -103,11 +103,6 @@ def visit_type_type(self, t: TypeType) -> T: def visit_type_alias_type(self, t: TypeAliasType) -> T: pass - @abstractmethod - def visit_type_guard_type(self, t: TypeGuardType) -> T: - pass - - @trait @mypyc_attr(allow_interpreted_subclasses=True) class SyntheticTypeVisitor(TypeVisitor[T]): @@ -224,9 +219,6 @@ def visit_union_type(self, t: UnionType) -> Type: def translate_types(self, types: Iterable[Type]) -> List[Type]: return [t.accept(self) for t in types] - def visit_type_guard_type(self, t: TypeGuardType) -> Type: - return TypeGuardType(t.type_guard.accept(self)) - def translate_variables(self, variables: Sequence[TypeVarLikeType]) -> Sequence[TypeVarLikeType]: return variables @@ -326,9 +318,6 @@ def visit_star_type(self, t: StarType) -> T: def visit_union_type(self, t: UnionType) -> T: return self.query_types(t.items) - def visit_type_guard_type(self, t: TypeGuardType) -> T: - return t.type_guard.accept(self) - def visit_overloaded(self, t: Overloaded) -> T: return self.query_types(t.items()) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 07dc704e42ea0..6e2cb350f6e64 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -14,7 +14,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType, CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarType, SyntheticTypeVisitor, - StarType, PartialType, EllipsisType, UninhabitedType, TypeType, TypeGuardType, TypeVarLikeType, + StarType, PartialType, EllipsisType, UninhabitedType, TypeType, TypeVarLikeType, CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType, PlaceholderType, Overloaded, get_proper_type, TypeAliasType, TypeVarLikeType, ParamSpecType ) @@ -547,9 +547,6 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: ) return ret - def visit_type_guard_type(self, t: TypeGuardType) -> Type: - return t - def anal_type_guard(self, t: Type) -> Optional[Type]: if isinstance(t, UnboundType): sym = self.lookup_qualified(t.name, t) diff --git a/mypy/types.py b/mypy/types.py index e6cf92e879b52..1fe1aa8b3e7f6 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -276,14 +276,7 @@ def copy_modified(self, *, self.line, self.column) -class ProperType(Type): - """Not a type alias. - - Every type except TypeAliasType must inherit from this type. - """ - - -class TypeGuardType(ProperType): +class TypeGuardedType(Type): """Only used by find_instance_check() etc.""" def __init__(self, type_guard: Type): super().__init__(line=type_guard.line, column=type_guard.column) @@ -292,8 +285,12 @@ def __init__(self, type_guard: Type): def __repr__(self) -> str: return "TypeGuard({})".format(self.type_guard) - def accept(self, visitor: 'TypeVisitor[T]') -> T: - return visitor.visit_type_guard_type(self) + +class ProperType(Type): + """Not a type alias. + + Every type except TypeAliasType must inherit from this type. + """ class TypeVarId: @@ -1944,6 +1941,8 @@ def get_proper_type(typ: Optional[Type]) -> Optional[ProperType]: """ if typ is None: return None + if isinstance(typ, TypeGuardedType): # type: ignore[misc] + typ = typ.type_guard while isinstance(typ, TypeAliasType): typ = typ._expand_once() assert isinstance(typ, ProperType), typ @@ -2143,9 +2142,6 @@ def visit_union_type(self, t: UnionType) -> str: s = self.list_str(t.items) return 'Union[{}]'.format(s) - def visit_type_guard_type(self, t: TypeGuardType) -> str: - return 'TypeGuard[{}]'.format(t.type_guard.accept(self)) - def visit_partial_type(self, t: PartialType) -> str: if t.type is None: return '' diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index e8f22a62e7c43..8d7459f7a551c 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -6,7 +6,7 @@ Type, SyntheticTypeVisitor, AnyType, UninhabitedType, NoneType, ErasedType, DeletedType, TypeVarType, LiteralType, Instance, CallableType, TupleType, TypedDictType, UnionType, Overloaded, TypeType, CallableArgument, UnboundType, TypeList, StarType, EllipsisType, - PlaceholderType, PartialType, RawExpressionType, TypeAliasType, TypeGuardType + PlaceholderType, PartialType, RawExpressionType, TypeAliasType ) @@ -62,9 +62,6 @@ def visit_typeddict_type(self, t: TypedDictType) -> None: def visit_union_type(self, t: UnionType) -> None: self.traverse_types(t.items) - def visit_type_guard_type(self, t: TypeGuardType) -> None: - t.type_guard.accept(self) - def visit_overloaded(self, t: Overloaded) -> None: self.traverse_types(t.items()) diff --git a/test-data/unit/check-typeguard.test b/test-data/unit/check-typeguard.test index c4f88ca3f018c..4b93a6bfc305e 100644 --- a/test-data/unit/check-typeguard.test +++ b/test-data/unit/check-typeguard.test @@ -414,3 +414,47 @@ def test(x: List[object]) -> None: return g(reveal_type(x)) # N: Revealed type is "Union[builtins.list[builtins.str], __main__.]" [builtins fixtures/tuple.pyi] + +[case testTypeGuardMultipleCondition] +from typing_extensions import TypeGuard +from typing import Any, List + +class Foo: ... +class Bar: ... + +def is_foo(item: object) -> TypeGuard[Foo]: + return isinstance(item, Foo) + +def is_bar(item: object) -> TypeGuard[Bar]: + return isinstance(item, Bar) + +def foobar(x: object): + if not isinstance(x, Foo) or not isinstance(x, Bar): + return + reveal_type(x) # N: Revealed type is "__main__." + +def foobar_typeguard(x: object): + if not is_foo(x) or not is_bar(x): + return + reveal_type(x) # N: Revealed type is "__main__." +[builtins fixtures/tuple.pyi] + +[case testTypeGuardComprehensionSubtype] +from typing import List +from typing_extensions import TypeGuard + +class Base: ... +class Foo(Base): ... +class Bar(Base): ... + +def is_foo(item: object) -> TypeGuard[Foo]: + return isinstance(item, Foo) + +def is_bar(item: object) -> TypeGuard[Bar]: + return isinstance(item, Bar) + +def foobar(items: List[object]): + a: List[Base] = [x for x in items if is_foo(x) or is_bar(x)] + b: List[Base] = [x for x in items if is_foo(x)] + c: List[Bar] = [x for x in items if is_foo(x)] # E: List comprehension has incompatible type List[Foo]; expected List[Bar] +[builtins fixtures/tuple.pyi] From ac8c2a3212175678eea1b1797e284a8ebe5d25ad Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 5 Sep 2021 19:27:16 -0700 Subject: [PATCH 2/8] remove some more special casing --- mypy/binder.py | 14 +------------- mypy/checkexpr.py | 4 ---- mypy/meet.py | 5 ++++- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index d8f751b66b071..3d92d0cd96dd7 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -210,9 +210,7 @@ def update_from_options(self, frames: List[Frame]) -> bool: else: for other in resulting_values[1:]: assert other is not None - # Ignore the error about using get_proper_type(). - if not contains_type_guard(other): - type = join_simple(self.declarations[key], type, other) + type = join_simple(self.declarations[key], type, other) if current_value is None or not is_same_type(type, current_value): self._put(key, type) changed = True @@ -440,13 +438,3 @@ def get_declaration(expr: BindableExpression) -> Optional[Type]: if not isinstance(type, PartialType): return type return None - - -def contains_type_guard(other: Type) -> bool: - # Ignore the error about using get_proper_type(). - if isinstance(other, TypeGuardedType): # type: ignore[misc] - return True - other = get_proper_type(other) - if isinstance(other, UnionType): - return any(contains_type_guard(item) for item in other.relevant_items()) - return False diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f455cf23128f9..cc9f221a83f74 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4175,10 +4175,6 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type, """ if literal(expr) >= LITERAL_TYPE: restriction = self.chk.binder.get(expr) - # Ignore the error about using get_proper_type(). - if isinstance(restriction, TypeGuardedType): # type: ignore[misc] - # A type guard forces the new type even if it doesn't overlap the old. - return restriction.type_guard # If the current node is deferred, some variables may get Any types that they # otherwise wouldn't have. We don't want to narrow down these since it may # produce invalid inferred Optional[Any] types, at least. diff --git a/mypy/meet.py b/mypy/meet.py index 59a29181c7775..6715e11856b3e 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -2,7 +2,7 @@ from typing import List, Optional, Tuple, Callable from mypy.types import ( - Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, + Type, AnyType, TypeGuardedType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, ProperType, get_proper_type, get_proper_types, TypeAliasType @@ -51,6 +51,9 @@ def meet_types(s: Type, t: Type) -> ProperType: def narrow_declared_type(declared: Type, narrowed: Type) -> Type: """Return the declared type narrowed down to another type.""" # TODO: check infinite recursion for aliases here. + if isinstance(narrowed, TypeGuardedType): + return narrowed.type_guard + declared = get_proper_type(declared) narrowed = get_proper_type(narrowed) From 436e9ad5aabe68d68e341d38a87e2bbc6b716252 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 5 Sep 2021 19:28:11 -0700 Subject: [PATCH 3/8] mark as xfail --- mypy/meet.py | 2 +- test-data/unit/check-typeguard.test | 44 ++++++++++++++--------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 6715e11856b3e..13dea249e6d1f 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -51,7 +51,7 @@ def meet_types(s: Type, t: Type) -> ProperType: def narrow_declared_type(declared: Type, narrowed: Type) -> Type: """Return the declared type narrowed down to another type.""" # TODO: check infinite recursion for aliases here. - if isinstance(narrowed, TypeGuardedType): + if isinstance(narrowed, TypeGuardedType): # type: ignore[misc] return narrowed.type_guard declared = get_proper_type(declared) diff --git a/test-data/unit/check-typeguard.test b/test-data/unit/check-typeguard.test index 4b93a6bfc305e..fb26f0d3d5374 100644 --- a/test-data/unit/check-typeguard.test +++ b/test-data/unit/check-typeguard.test @@ -401,7 +401,27 @@ def test(x: object) -> None: g(reveal_type(x)) # N: Revealed type is "Union[__main__.A, __main__.B]" [builtins fixtures/tuple.pyi] -[case testTypeGuardNestedRestrictionUnionIsInstance] +[case testTypeGuardComprehensionSubtype] +from typing import List +from typing_extensions import TypeGuard + +class Base: ... +class Foo(Base): ... +class Bar(Base): ... + +def is_foo(item: object) -> TypeGuard[Foo]: + return isinstance(item, Foo) + +def is_bar(item: object) -> TypeGuard[Bar]: + return isinstance(item, Bar) + +def foobar(items: List[object]): + a: List[Base] = [x for x in items if is_foo(x) or is_bar(x)] + b: List[Base] = [x for x in items if is_foo(x)] + c: List[Bar] = [x for x in items if is_foo(x)] # E: List comprehension has incompatible type List[Foo]; expected List[Bar] +[builtins fixtures/tuple.pyi] + +[case testTypeGuardNestedRestrictionUnionIsInstance-xfail] from typing_extensions import TypeGuard from typing import Any, List @@ -415,7 +435,7 @@ def test(x: List[object]) -> None: g(reveal_type(x)) # N: Revealed type is "Union[builtins.list[builtins.str], __main__.]" [builtins fixtures/tuple.pyi] -[case testTypeGuardMultipleCondition] +[case testTypeGuardMultipleCondition-xfail] from typing_extensions import TypeGuard from typing import Any, List @@ -438,23 +458,3 @@ def foobar_typeguard(x: object): return reveal_type(x) # N: Revealed type is "__main__." [builtins fixtures/tuple.pyi] - -[case testTypeGuardComprehensionSubtype] -from typing import List -from typing_extensions import TypeGuard - -class Base: ... -class Foo(Base): ... -class Bar(Base): ... - -def is_foo(item: object) -> TypeGuard[Foo]: - return isinstance(item, Foo) - -def is_bar(item: object) -> TypeGuard[Bar]: - return isinstance(item, Bar) - -def foobar(items: List[object]): - a: List[Base] = [x for x in items if is_foo(x) or is_bar(x)] - b: List[Base] = [x for x in items if is_foo(x)] - c: List[Bar] = [x for x in items if is_foo(x)] # E: List comprehension has incompatible type List[Foo]; expected List[Bar] -[builtins fixtures/tuple.pyi] From 2494cd41813d09b8f3f8316e85d234a9e12a6e14 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 5 Sep 2021 20:04:01 -0700 Subject: [PATCH 4/8] add back comment --- mypy/meet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/meet.py b/mypy/meet.py index 13dea249e6d1f..6285083107f4a 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -52,6 +52,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: """Return the declared type narrowed down to another type.""" # TODO: check infinite recursion for aliases here. if isinstance(narrowed, TypeGuardedType): # type: ignore[misc] + # A type guard forces the new type even if it doesn't overlap the old. return narrowed.type_guard declared = get_proper_type(declared) From 884307c14de941db42851572361b65cea90ae2bf Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 5 Sep 2021 20:17:04 -0700 Subject: [PATCH 5/8] lint --- mypy/binder.py | 2 +- mypy/checkexpr.py | 2 +- mypy/meet.py | 4 ++-- mypy/type_visitor.py | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 3d92d0cd96dd7..523367a7685ca 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -5,7 +5,7 @@ from typing_extensions import DefaultDict from mypy.types import ( - Type, AnyType, PartialType, UnionType, TypeOfAny, NoneType, TypeGuardedType, get_proper_type + Type, AnyType, PartialType, UnionType, TypeOfAny, NoneType, get_proper_type ) from mypy.subtypes import is_subtype from mypy.join import join_simple diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index cc9f221a83f74..cc32717a27370 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -15,7 +15,7 @@ make_optional_type, ) from mypy.types import ( - Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType, TypeGuardedType, + Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType, TupleType, TypedDictType, Instance, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue, is_named_instance, FunctionLike, ParamSpecType, diff --git a/mypy/meet.py b/mypy/meet.py index 6285083107f4a..e56c925701f38 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -2,10 +2,10 @@ from typing import List, Optional, Tuple, Callable from mypy.types import ( - Type, AnyType, TypeGuardedType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, + Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, - ProperType, get_proper_type, get_proper_types, TypeAliasType + ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardedType ) from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index c9d8359906b95..6ac7a7ab2c99c 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -103,6 +103,7 @@ def visit_type_type(self, t: TypeType) -> T: def visit_type_alias_type(self, t: TypeAliasType) -> T: pass + @trait @mypyc_attr(allow_interpreted_subclasses=True) class SyntheticTypeVisitor(TypeVisitor[T]): From 75808e0ab2ee18827d4035b2d92dd9258ea3f533 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Mon, 13 Sep 2021 21:26:46 -0700 Subject: [PATCH 6/8] . --- mypy/meet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mypy/meet.py b/mypy/meet.py index 90c800f080a9c..d549ced2c6147 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -146,6 +146,10 @@ def is_overlapping_types(left: Type, If 'prohibit_none_typevar_overlap' is True, we disallow None from overlapping with TypeVars (in both strict-optional and non-strict-optional mode). """ + if isinstance(left, TypeGuardedType) or isinstance(right, TypeGuardedType): + # A type guard forces the new type even if it doesn't overlap the old. + return True + left, right = get_proper_types((left, right)) def _is_overlapping_types(left: Type, right: Type) -> bool: From 74f04f4c00574f93b73e4cfd78679f2fa8648373 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Mon, 13 Sep 2021 21:58:25 -0700 Subject: [PATCH 7/8] . --- mypy/meet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index d549ced2c6147..fe5ea16295e5a 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -146,7 +146,7 @@ def is_overlapping_types(left: Type, If 'prohibit_none_typevar_overlap' is True, we disallow None from overlapping with TypeVars (in both strict-optional and non-strict-optional mode). """ - if isinstance(left, TypeGuardedType) or isinstance(right, TypeGuardedType): + if isinstance(left, TypeGuardedType) or isinstance(right, TypeGuardedType): # type: ignore[misc] # A type guard forces the new type even if it doesn't overlap the old. return True From 6d4af01cc443affc2671461be8b0a1a8b8f704f0 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Mon, 13 Sep 2021 22:37:55 -0700 Subject: [PATCH 8/8] . --- mypy/meet.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index fe5ea16295e5a..f89c1fc7b16f7 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -146,7 +146,10 @@ def is_overlapping_types(left: Type, If 'prohibit_none_typevar_overlap' is True, we disallow None from overlapping with TypeVars (in both strict-optional and non-strict-optional mode). """ - if isinstance(left, TypeGuardedType) or isinstance(right, TypeGuardedType): # type: ignore[misc] + if ( + isinstance(left, TypeGuardedType) # type: ignore[misc] + or isinstance(right, TypeGuardedType) # type: ignore[misc] + ): # A type guard forces the new type even if it doesn't overlap the old. return True