From bf20f4988250b5bd03f2b6afd159264ad27677f3 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 29 Mar 2017 19:27:35 +0100 Subject: [PATCH 01/10] Fixes to union simplification, isinstance and more (#3025) The main change is that unions containing Any are no longer simplified to just Any. Also union simplification now has a deterministic result unlike previously, when result could depend on the order of items in a union (this is true modulo remaining bugs). This required changes in various other places to keep the existing semantics, and resulted in some fixes to existing test cases. I also had to fix some tangentially related minor bugs that were triggered by the other changes. We generally don't have fully constructed TypeInfos so we can't do proper union simplification during semantic analysis. Just implement simple-minded simplification that deals with the cases we care about. Fixes #2978. Fixes #1914. --- mypy/checker.py | 77 ++++---- mypy/checkexpr.py | 4 +- mypy/erasetype.py | 3 +- mypy/join.py | 6 +- mypy/meet.py | 31 ++-- mypy/subtypes.py | 221 ++++++++++++++++++---- mypy/test/testtypes.py | 2 +- mypy/typeanal.py | 24 ++- mypy/types.py | 45 ++++- test-data/unit/check-classes.test | 4 +- test-data/unit/check-dynamic-typing.test | 8 +- test-data/unit/check-generics.test | 2 +- test-data/unit/check-isinstance.test | 38 +++- test-data/unit/check-namedtuple.test | 22 +++ test-data/unit/check-optional.test | 42 ++++- test-data/unit/check-statements.test | 6 +- test-data/unit/check-tuples.test | 2 +- test-data/unit/check-typeddict.test | 57 ++++++ test-data/unit/check-unions.test | 226 ++++++++++++++++++++++- test-data/unit/fixtures/primitives.pyi | 2 +- 20 files changed, 696 insertions(+), 126 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c244a7a07d8a..a1395df05827 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -28,7 +28,7 @@ Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType, Instance, NoneTyp, ErrorType, strip_type, TypeType, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef, - true_only, false_only, function_type, is_named_instance + true_only, false_only, function_type, is_named_instance, union_items ) from mypy.sametypes import is_same_type, is_same_types from mypy.messages import MessageBuilder @@ -812,44 +812,45 @@ def check_overlapping_op_methods(self, # of x in __radd__ would not be A, the methods could be # non-overlapping. - if isinstance(forward_type, CallableType): - # TODO check argument kinds - if len(forward_type.arg_types) < 1: - # Not a valid operator method -- can't succeed anyway. - return + for forward_item in union_items(forward_type): + if isinstance(forward_item, CallableType): + # TODO check argument kinds + if len(forward_item.arg_types) < 1: + # Not a valid operator method -- can't succeed anyway. + return - # Construct normalized function signatures corresponding to the - # operator methods. The first argument is the left operand and the - # second operand is the right argument -- we switch the order of - # the arguments of the reverse method. - forward_tweaked = CallableType( - [forward_base, forward_type.arg_types[0]], - [nodes.ARG_POS] * 2, - [None] * 2, - forward_type.ret_type, - forward_type.fallback, - name=forward_type.name) - reverse_args = reverse_type.arg_types - reverse_tweaked = CallableType( - [reverse_args[1], reverse_args[0]], - [nodes.ARG_POS] * 2, - [None] * 2, - reverse_type.ret_type, - fallback=self.named_type('builtins.function'), - name=reverse_type.name) - - if is_unsafe_overlapping_signatures(forward_tweaked, - reverse_tweaked): - self.msg.operator_method_signatures_overlap( - reverse_class.name(), reverse_name, - forward_base.type.name(), forward_name, context) - elif isinstance(forward_type, Overloaded): - for item in forward_type.items(): - self.check_overlapping_op_methods( - reverse_type, reverse_name, reverse_class, - item, forward_name, forward_base, context) - elif not isinstance(forward_type, AnyType): - self.msg.forward_operator_not_callable(forward_name, context) + # Construct normalized function signatures corresponding to the + # operator methods. The first argument is the left operand and the + # second operand is the right argument -- we switch the order of + # the arguments of the reverse method. + forward_tweaked = CallableType( + [forward_base, forward_item.arg_types[0]], + [nodes.ARG_POS] * 2, + [None] * 2, + forward_item.ret_type, + forward_item.fallback, + name=forward_item.name) + reverse_args = reverse_type.arg_types + reverse_tweaked = CallableType( + [reverse_args[1], reverse_args[0]], + [nodes.ARG_POS] * 2, + [None] * 2, + reverse_type.ret_type, + fallback=self.named_type('builtins.function'), + name=reverse_type.name) + + if is_unsafe_overlapping_signatures(forward_tweaked, + reverse_tweaked): + self.msg.operator_method_signatures_overlap( + reverse_class.name(), reverse_name, + forward_base.type.name(), forward_name, context) + elif isinstance(forward_item, Overloaded): + for item in forward_item.items(): + self.check_overlapping_op_methods( + reverse_type, reverse_name, reverse_class, + item, forward_name, forward_base, context) + elif not isinstance(forward_item, AnyType): + self.msg.forward_operator_not_callable(forward_name, context) def check_inplace_operator_method(self, defn: FuncBase) -> None: """Check an inplace operator method such as __iadd__. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 51a6f92e9ce6..6736c4f8f513 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -32,7 +32,7 @@ from mypy import messages from mypy.infer import infer_type_arguments, infer_function_type_arguments from mypy import join -from mypy.meet import meet_simple +from mypy.meet import narrow_declared_type from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent from mypy import applytype @@ -2221,7 +2221,7 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type: if expr.literal >= LITERAL_TYPE: restriction = self.chk.binder.get(expr) if restriction: - ans = meet_simple(known_type, restriction) + ans = narrow_declared_type(known_type, restriction) return ans return known_type diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 9a44b0456522..5ea1df156c6f 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -76,7 +76,8 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type: return t.fallback.accept(self) def visit_union_type(self, t: UnionType) -> Type: - return AnyType() # XXX: return underlying type if only one? + erased_items = [erase_type(item) for item in t.items] + return UnionType.make_simplified_union(erased_items) def visit_type_type(self, t: TypeType) -> Type: return TypeType(t.item.accept(self), line=t.line) diff --git a/mypy/join.py b/mypy/join.py index 06d5416cd2ea..1713cd61ea63 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -10,7 +10,7 @@ UninhabitedType, TypeType, true_or_false ) from mypy.maptype import map_instance_to_supertype -from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars +from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars, is_proper_subtype from mypy import experiments @@ -29,10 +29,10 @@ def join_simple(declaration: Type, s: Type, t: Type) -> Type: if isinstance(s, ErasedType): return t - if is_subtype(s, t): + if is_proper_subtype(s, t): return t - if is_subtype(t, s): + if is_proper_subtype(t, s): return s if isinstance(declaration, UnionType): diff --git a/mypy/meet.py b/mypy/meet.py index 22d0a709838b..82fec33c0cb2 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -25,21 +25,26 @@ def meet_types(s: Type, t: Type) -> Type: return t.accept(TypeMeetVisitor(s)) -def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: - if s == t: - return s - if isinstance(s, UnionType): - return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) - elif not is_overlapping_types(s, t, use_promotions=True): +def narrow_declared_type(declared: Type, narrowed: Type) -> Type: + """Return the declared type narrowed down to another type.""" + if declared == narrowed: + return declared + if isinstance(declared, UnionType): + return UnionType.make_simplified_union([narrow_declared_type(x, narrowed) + for x in declared.items]) + elif not is_overlapping_types(declared, narrowed, use_promotions=True): if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return NoneTyp() - else: - if default_right: - return t - else: - return s + elif isinstance(narrowed, UnionType): + return UnionType.make_simplified_union([narrow_declared_type(declared, x) + for x in narrowed.items]) + elif isinstance(narrowed, AnyType): + return narrowed + elif isinstance(declared, (Instance, TupleType)): + return meet_types(declared, narrowed) + return narrowed def is_overlapping_types(t: Type, s: Type, use_promotions: bool = False) -> bool: @@ -249,6 +254,10 @@ def visit_tuple_type(self, t: TupleType) -> Type: elif (isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.tuple' and self.s.args): return t.copy_modified(items=[meet_types(it, self.s.args[0]) for it in t.items]) + elif (isinstance(self.s, Instance) and t.fallback.type == self.s.type): + # Uh oh, a broken named tuple type (https://github.com/python/mypy/issues/3016). + # Do something reasonable until that bug is fixed. + return t else: return self.default(self.s) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c98bb8e8d144..b96618da28eb 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -7,6 +7,7 @@ ) import mypy.applytype import mypy.constraints +from mypy.erasetype import erase_type # Circular import; done in the function instead. # import mypy.solve from mypy import messages, sametypes @@ -15,6 +16,7 @@ ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, ) from mypy.maptype import map_instance_to_supertype +from mypy.sametypes import is_same_type from mypy import experiments @@ -280,9 +282,15 @@ def visit_type_type(self, left: TypeType) -> bool: def is_callable_subtype(left: CallableType, right: CallableType, ignore_return: bool = False, - ignore_pos_arg_names: bool = False) -> bool: + ignore_pos_arg_names: bool = False, + use_proper_subtype: bool = False) -> bool: """Is left a subtype of right?""" + if use_proper_subtype: + is_compat = is_proper_subtype + else: + is_compat = is_subtype + # If either function is implicitly typed, ignore positional arg names too if left.implicit or right.implicit: ignore_pos_arg_names = True @@ -309,7 +317,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, return False # Check return types. - if not ignore_return and not is_subtype(left.ret_type, right.ret_type): + if not ignore_return and not is_compat(left.ret_type, right.ret_type): return False if right.is_ellipsis_args: @@ -366,7 +374,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_by_position = right.argument_by_position(j) assert right_by_position is not None if not are_args_compatible(left_by_position, right_by_position, - ignore_pos_arg_names): + ignore_pos_arg_names, use_proper_subtype): return False j += 1 continue @@ -389,7 +397,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_by_name = right.argument_by_name(name) assert right_by_name is not None if not are_args_compatible(left_by_name, right_by_name, - ignore_pos_arg_names): + ignore_pos_arg_names, use_proper_subtype): return False continue @@ -398,7 +406,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, if left_arg is None: return False - if not are_args_compatible(left_arg, right_arg, ignore_pos_arg_names): + if not are_args_compatible(left_arg, right_arg, ignore_pos_arg_names, use_proper_subtype): return False done_with_positional = False @@ -414,11 +422,11 @@ def is_callable_subtype(left: CallableType, right: CallableType, # Check that *args and **kwargs types match in this loop if left_kind == ARG_STAR: - if right_star_type is not None and not is_subtype(right_star_type, left_arg.typ): + if right_star_type is not None and not is_compat(right_star_type, left_arg.typ): return False continue elif left_kind == ARG_STAR2: - if right_star2_type is not None and not is_subtype(right_star2_type, left_arg.typ): + if right_star2_type is not None and not is_compat(right_star2_type, left_arg.typ): return False continue @@ -449,7 +457,8 @@ def is_callable_subtype(left: CallableType, right: CallableType, def are_args_compatible( left: FormalArgument, right: FormalArgument, - ignore_pos_arg_names: bool) -> bool: + ignore_pos_arg_names: bool, + use_proper_subtype: bool) -> bool: # If right has a specific name it wants this argument to be, left must # have the same. if right.name is not None and left.name != right.name: @@ -460,8 +469,12 @@ def are_args_compatible( if right.pos is not None and left.pos != right.pos: return False # Left must have a more general type - if not is_subtype(right.typ, left.typ): - return False + if use_proper_subtype: + if not is_proper_subtype(right.typ, left.typ): + return False + else: + if not is_subtype(right.typ, left.typ): + return False # If right's argument is optional, left's must also be. if not right.required and left.required: return False @@ -496,52 +509,192 @@ def unify_generic_callable(type: CallableType, target: CallableType, def restrict_subtype_away(t: Type, s: Type) -> Type: - """Return a supertype of (t intersect not s) + """Return t minus s. - Currently just remove elements of a union type. + If we can't determine a precise result, return a supertype of the + ideal result (just t is a valid result). + + This is used for type inference of runtime type checks such as + isinstance. + + Currently this just removes elements of a union type. """ if isinstance(t, UnionType): - new_items = [item for item in t.items if (not is_subtype(item, s) - or isinstance(item, AnyType))] + # Since runtime type checks will ignore type arguments, erase the types. + erased_s = erase_type(s) + new_items = [item for item in t.items + if (not is_proper_subtype(erase_type(item), erased_s) + or isinstance(item, AnyType))] return UnionType.make_union(new_items) else: return t -def is_proper_subtype(t: Type, s: Type) -> bool: - """Check if t is a proper subtype of s? +def is_proper_subtype(left: Type, right: Type) -> bool: + """Is left a proper subtype of right? For proper subtypes, there's no need to rely on compatibility due to - Any types. Any instance type t is also a proper subtype of t. + Any types. Every usable type is a proper subtype of itself. """ - # FIX tuple types - if isinstance(s, UnionType): - if isinstance(t, UnionType): - return all([is_proper_subtype(item, s) for item in t.items]) - else: - return any([is_proper_subtype(t, item) for item in s.items]) + if isinstance(right, UnionType) and not isinstance(left, UnionType): + return any([is_proper_subtype(left, item) + for item in right.items]) + return left.accept(ProperSubtypeVisitor(right)) + + +class ProperSubtypeVisitor(TypeVisitor[bool]): + def __init__(self, right: Type) -> None: + self.right = right - if isinstance(t, Instance): - if isinstance(s, Instance): - if not t.type.has_base(s.type.fullname()): + def visit_unbound_type(self, left: UnboundType) -> bool: + # This can be called if there is a bad type annotation. The result probably + # doesn't matter much but by returning True we simplify these bad types away + # from unions, which could filter out some bogus messages. + return True + + def visit_error_type(self, left: ErrorType) -> bool: + # This isn't a real type. + return False + + def visit_type_list(self, left: TypeList) -> bool: + assert False, 'Should not happen' + + def visit_any(self, left: AnyType) -> bool: + return isinstance(self.right, AnyType) + + def visit_none_type(self, left: NoneTyp) -> bool: + if experiments.STRICT_OPTIONAL: + return (isinstance(self.right, NoneTyp) or + is_named_instance(self.right, 'builtins.object')) + return True + + def visit_uninhabited_type(self, left: UninhabitedType) -> bool: + return True + + def visit_erased_type(self, left: ErasedType) -> bool: + # This may be encountered during type inference. The result probably doesn't + # matter much. + return True + + def visit_deleted_type(self, left: DeletedType) -> bool: + return True + + def visit_instance(self, left: Instance) -> bool: + right = self.right + if isinstance(right, Instance): + for base in left.type.mro: + if base._promote and is_proper_subtype(base._promote, right): + return True + + if not left.type.has_base(right.type.fullname()): return False - def check_argument(left: Type, right: Type, variance: int) -> bool: + def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: if variance == COVARIANT: - return is_proper_subtype(left, right) + return is_proper_subtype(leftarg, rightarg) elif variance == CONTRAVARIANT: - return is_proper_subtype(right, left) + return is_proper_subtype(rightarg, leftarg) else: - return sametypes.is_same_type(left, right) + return sametypes.is_same_type(leftarg, rightarg) # Map left type to corresponding right instances. - t = map_instance_to_supertype(t, s.type) + left = map_instance_to_supertype(left, right.type) return all(check_argument(ta, ra, tvar.variance) for ta, ra, tvar in - zip(t.args, s.args, s.type.defn.type_vars)) + zip(left.args, right.args, right.type.defn.type_vars)) + return False + + def visit_type_var(self, left: TypeVarType) -> bool: + if isinstance(self.right, TypeVarType) and left.id == self.right.id: + return True + # TODO: Value restrictions + return is_proper_subtype(left.upper_bound, self.right) + + def visit_callable_type(self, left: CallableType) -> bool: + right = self.right + if isinstance(right, CallableType): + return is_callable_subtype( + left, right, + ignore_pos_arg_names=False, + use_proper_subtype=True) + elif isinstance(right, Overloaded): + return all(is_proper_subtype(left, item) + for item in right.items()) + elif isinstance(right, Instance): + return is_proper_subtype(left.fallback, right) + elif isinstance(right, TypeType): + # This is unsound, we don't check the __init__ signature. + return left.is_type_obj() and is_proper_subtype(left.ret_type, right.item) + return False + + def visit_tuple_type(self, left: TupleType) -> bool: + right = self.right + if isinstance(right, Instance): + if (is_named_instance(right, 'builtins.tuple') or + is_named_instance(right, 'typing.Iterable') or + is_named_instance(right, 'typing.Container') or + is_named_instance(right, 'typing.Sequence') or + is_named_instance(right, 'typing.Reversible')): + if not right.args: + return False + iter_type = right.args[0] + if is_named_instance(right, 'builtins.tuple') and isinstance(iter_type, AnyType): + # TODO: We shouldn't need this special case. This is currently needed + # for isinstance(x, tuple), though it's unclear why. + return True + return all(is_proper_subtype(li, iter_type) for li in left.items) + return is_proper_subtype(left.fallback, right) + elif isinstance(right, TupleType): + if len(left.items) != len(right.items): + return False + for l, r in zip(left.items, right.items): + if not is_proper_subtype(l, r): + return False + return is_proper_subtype(left.fallback, right.fallback) + return False + + def visit_typeddict_type(self, left: TypedDictType) -> bool: + right = self.right + if isinstance(right, TypedDictType): + for name, typ in left.items.items(): + if name in right.items and not is_same_type(typ, right.items[name]): + return False + for name, typ in right.items.items(): + if name not in left.items: + return False + return True + return is_proper_subtype(left.fallback, right) + + def visit_overloaded(self, left: Overloaded) -> bool: + # TODO: What's the right thing to do here? + return False + + def visit_union_type(self, left: UnionType) -> bool: + return all([is_proper_subtype(item, self.right) for item in left.items]) + + def visit_partial_type(self, left: PartialType) -> bool: + # TODO: What's the right thing to do here? + return False + + def visit_type_type(self, left: TypeType) -> bool: + # TODO: Handle metaclasses? + right = self.right + if isinstance(right, TypeType): + # This is unsound, we don't check the __init__ signature. + return is_proper_subtype(left.item, right.item) + if isinstance(right, CallableType): + # This is also unsound because of __init__. + return right.is_type_obj() and is_proper_subtype(left.item, right.ret_type) + if isinstance(right, Instance): + if right.type.fullname() == 'builtins.type': + # TODO: Strictly speaking, the type builtins.type is considered equivalent to + # Type[Any]. However, this would break the is_proper_subtype check in + # conditional_type_map for cases like isinstance(x, type) when the type + # of x is Type[int]. It's unclear what's the right way to address this. + return True + if right.type.fullname() == 'builtins.object': + return True return False - else: - return sametypes.is_same_type(t, s) def is_more_precise(t: Type, s: Type) -> bool: diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 9fc6a71b5594..3c1c31fd6c90 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -658,7 +658,7 @@ def test_tuples(self) -> None: self.assert_meet(self.tuple(self.fx.a, self.fx.a), self.fx.std_tuple, - NoneTyp()) + self.tuple(self.fx.a, self.fx.a)) self.assert_meet(self.tuple(self.fx.a), self.tuple(self.fx.a, self.fx.a), NoneTyp()) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index dc1e22a32969..5ef5de7f8a68 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -7,7 +7,7 @@ Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType, CallableType, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args, - get_type_vars, + get_type_vars, union_items ) from mypy.nodes import ( BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -109,7 +109,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: t.optional = False # We don't need to worry about double-wrapping Optionals or # wrapping Anys: Union simplification will take care of that. - return UnionType.make_simplified_union([self.visit_unbound_type(t), NoneTyp()]) + return make_optional_type(self.visit_unbound_type(t)) sym = self.lookup(t.name, t) if sym is not None: if sym.node is None: @@ -151,7 +151,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: self.fail('Optional[...] must have exactly one type argument', t) return AnyType() item = self.anal_type(t.args[0]) - return UnionType.make_simplified_union([item, NoneTyp()]) + return make_optional_type(item) elif fullname == 'typing.Callable': return self.analyze_callable_type(t) elif fullname == 'typing.Type': @@ -557,3 +557,21 @@ def visit_partial_type(self, t: PartialType) -> None: def visit_type_type(self, t: TypeType) -> None: pass + + +def make_optional_type(t: Type) -> Type: + """Return the type corresponding to Optional[t]. + + Note that we can't use normal union simplification, since this function + is called during semantic analysis and simplification only works during + type checking. + """ + if not experiments.STRICT_OPTIONAL: + return t + if isinstance(t, NoneTyp): + return t + if isinstance(t, UnionType): + items = [item for item in union_items(t) + if not isinstance(item, NoneTyp)] + return UnionType(items + [NoneTyp()], t.line, t.column) + return UnionType([t, NoneTyp()], t.line, t.column) diff --git a/mypy/types.py b/mypy/types.py index 41ef8ec238f5..2603e26bb427 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -997,6 +997,23 @@ def make_union(items: List[Type], line: int = -1, column: int = -1) -> Type: @staticmethod def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) -> Type: + """Build union type with redundant union items removed. + + If only a single item remains, this may return a non-union type. + + Examples: + + * [int, str] -> Union[int, str] + * [int, object] -> object + * [int, int] -> int + * [int, Any] -> Union[int, Any] (Any types are not simplified away!) + * [Any, Any] -> Any + + Note: This must NOT be used during semantic analysis, since TypeInfos may not + be fully initialized. + """ + # TODO: Make this a function living somewhere outside mypy.types. Most other non-trivial + # type operations are not static methods, so this is inconsistent. while any(isinstance(typ, UnionType) for typ in items): all_items = [] # type: List[Type] for typ in items: @@ -1006,11 +1023,7 @@ def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) - all_items.append(typ) items = all_items - if any(isinstance(typ, AnyType) for typ in items): - return AnyType() - - from mypy.subtypes import is_subtype - from mypy.sametypes import is_same_type + from mypy.subtypes import is_proper_subtype removed = set() # type: Set[int] for i, ti in enumerate(items): @@ -1018,10 +1031,8 @@ def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) - # Keep track of the truishness info for deleted subtypes which can be relevant cbt = cbf = False for j, tj in enumerate(items): - if (i != j - and is_subtype(tj, ti) - and (not (isinstance(tj, Instance) and tj.type.fallback_to_any) - or is_same_type(ti, tj))): + if (i != j and is_proper_subtype(tj, ti)): + # We found a redundant item in the union. removed.add(j) cbt = cbt or tj.can_be_true cbf = cbf or tj.can_be_false @@ -1728,7 +1739,7 @@ def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = - if isinstance(tp, TupleType): return tp.copy_modified(items=new_args) if isinstance(tp, UnionType): - return UnionType.make_simplified_union(new_args, line, column) + return UnionType(new_args, line, column) if isinstance(tp, CallableType): return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1], line=line, column=column) @@ -1756,6 +1767,20 @@ def get_type_vars(typ: Type) -> List[TypeVarType]: return tvars +def union_items(typ: Type) -> List[Type]: + """Return the flattened items of a union type. + + For non-union types, return a list containing just the argument. + """ + if isinstance(typ, UnionType): + items = [] + for item in typ.items: + items.extend(union_items(item)) + return items + else: + return [typ] + + deserialize_map = { key: obj.deserialize # type: ignore for key, obj in globals().items() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e401738c4dbf..56893383b4fd 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2438,9 +2438,9 @@ class A: pass class B(A): pass @overload -def f(a: Type[A]) -> int: pass # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def f(a: Type[B]) -> int: pass # E: Overloaded function signatures 1 and 2 overlap with incompatible return types @overload -def f(a: Type[B]) -> str: pass +def f(a: Type[A]) -> str: pass [builtins fixtures/classmethod.pyi] [out] diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index 9529a421977a..70d6d32aa475 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -79,8 +79,8 @@ n = 0 d in a # E: Unsupported right operand type for in ("A") d and a d or a -c = d and b # Unintuitive type inference? -c = d or b # Unintuitive type inference? +c = d and b # E: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "C") +c = d or b # E: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "C") c = d + a c = d - a @@ -123,8 +123,8 @@ n = 0 a and d a or d c = a in d -c = b and d # Unintuitive type inference? -c = b or d # Unintuitive type inference? +c = b and d # E: Incompatible types in assignment (expression has type "Union[bool, Any]", variable has type "C") +c = b or d # E: Incompatible types in assignment (expression has type "Union[bool, Any]", variable has type "C") b = a + d b = a / d diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 7d035d0ed992..64d010e313fd 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -779,7 +779,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # Any simplifies Union to Any now. This test should be updated after #2197 +z.foo() # E: Some element of union has no attribute "foo" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 973d463d6085..1490dc0bb089 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -399,6 +399,7 @@ def f(x: Union[List[int], List[str], int]) -> None: a + 'x' # E: Unsupported operand types for + ("int" and "str") # type of a? + reveal_type(x) # E: Revealed type is 'Union[builtins.list[builtins.int], builtins.list[builtins.str]]' x + 1 # E: Unsupported operand types for + (likely involving Union) else: x[0] # E: Value of type "int" is not indexable @@ -910,7 +911,8 @@ def foo() -> Union[int, str, A]: pass def bar() -> None: x = foo() - x + 1 # E: Unsupported left operand type for + (some union) + x + 1 # E: Unsupported left operand type for + (some union) \ + # E: Unsupported operand types for + (likely involving Union) if isinstance(x, A): x.a else: @@ -1275,6 +1277,7 @@ if isinstance(x, A): reveal_type(x) # unreachable else: reveal_type(x) # E: Revealed type is '__main__.B' +reveal_type(x) # E: Revealed type is '__main__.B' [builtins fixtures/isinstance.pyi] [case testAssertIsinstance] @@ -1305,6 +1308,9 @@ from typing import List, Union def f(x: Union[List[int], str]) -> None: if isinstance(x, list): x[0]() + else: + reveal_type(x) # E: Revealed type is 'builtins.str' + reveal_type(x) # E: Revealed type is 'Union[builtins.list[builtins.int], builtins.str]' [builtins fixtures/isinstancelist.pyi] [out] main:4: error: "int" not callable @@ -1322,6 +1328,7 @@ if isinstance(x1, B) or isinstance(x1, C): else: reveal_type(x1) # E: Revealed type is '__main__.A' f = 0 +reveal_type(x1) # E: Revealed type is '__main__.A' x2 = A() if isinstance(x2, A) or isinstance(x2, C): reveal_type(x2) # E: Revealed type is '__main__.A' @@ -1329,6 +1336,7 @@ if isinstance(x2, A) or isinstance(x2, C): else: # unreachable 1() +reveal_type(x2) # E: Revealed type is '__main__.A' [builtins fixtures/isinstance.pyi] [out] [case testComprehensionIsInstance] @@ -1370,6 +1378,7 @@ def f(x: Union[int, str], typ: type) -> None: reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' else: reveal_type(x) # E: Revealed type is 'builtins.str' + reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' [builtins fixtures/isinstancelist.pyi] @@ -1382,6 +1391,7 @@ def f(x: Union[int, A], a: Type[A]) -> None: reveal_type(x) # E: Revealed type is 'Union[builtins.int, __main__.A]' else: reveal_type(x) # E: Revealed type is '__main__.A' + reveal_type(x) # E: Revealed type is 'Union[builtins.int, __main__.A]' [builtins fixtures/isinstancelist.pyi] @@ -1412,3 +1422,29 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] + +[case testIsinstanceAndNarrowTypeVariable] +from typing import TypeVar + +class A: pass +class B(A): pass + +T = TypeVar('T', bound=A) + +def f(x: T) -> None: + if isinstance(x, B): + reveal_type(x) # E: Revealed type is '__main__.B' + else: + reveal_type(x) # E: Revealed type is 'T`-1' + reveal_type(x) # E: Revealed type is 'T`-1' +[builtins fixtures/isinstance.pyi] + +[case testIsinstanceAndTypeType] +from typing import Type +def f(x: Type[int]) -> None: + if isinstance(x, type): + reveal_type(x) # E: Revealed type is 'Type[builtins.int]' + else: + reveal_type(x) # Unreachable + reveal_type(x) # E: Revealed type is 'Type[builtins.int]' +[builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 71a058b24ac6..51260733064a 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -427,3 +427,25 @@ from typing import NamedTuple def f() -> None: A = NamedTuple('A', [('x', int)]) A # E: Name 'A' is not defined + +[case testNamedTupleWithImportCycle] +import a +[file a.py] +from collections import namedtuple +from b import f + +N = namedtuple('N', 'a') + +class X(N): pass +[file b.py] +import a + +def f(x: a.X) -> None: + # The type of x is broken (https://github.com/python/mypy/issues/3016) but we need to + # do something reasonable here to avoid a regression. + reveal_type(x) + x = a.X(1) + reveal_type(x) +[out] +tmp/b.py:6: error: Revealed type is 'a.X' +tmp/b.py:8: error: Revealed type is 'Tuple[Any, fallback=a.X]' diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 4286aab48bfd..05a9c93529f9 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -75,6 +75,7 @@ if x is None: reveal_type(x) # E: Revealed type is 'builtins.None' else: reveal_type(x) # E: Revealed type is 'builtins.int' +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]' [builtins fixtures/bool.pyi] [case testAnyCanBeNone] @@ -537,9 +538,10 @@ f = lambda x: None f(0) [builtins fixtures/function.pyi] -[case testUnionSimplificationWithStrictOptional] +[case testDontSimplifyNoneUnionsWithStrictOptional] from typing import Any, TypeVar, Union -class C(Any): pass +A = None # type: Any +class C(A): pass T = TypeVar('T') S = TypeVar('S') def u(x: T, y: S) -> Union[S, T]: pass @@ -549,9 +551,39 @@ a = None # type: Any reveal_type(u(C(), None)) # E: Revealed type is 'Union[builtins.None, __main__.C*]' reveal_type(u(None, C())) # E: Revealed type is 'Union[__main__.C*, builtins.None]' -# This will be fixed later -reveal_type(u(a, None)) # E: Revealed type is 'Any' -reveal_type(u(None, a)) # E: Revealed type is 'Any' +reveal_type(u(a, None)) # E: Revealed type is 'Union[builtins.None, Any]' +reveal_type(u(None, a)) # E: Revealed type is 'Union[Any, builtins.None]' reveal_type(u(1, None)) # E: Revealed type is 'Union[builtins.None, builtins.int*]' reveal_type(u(None, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.None]' + +[case testOptionalAndAnyBaseClass] +from typing import Any, Optional +A = None # type: Any +class C(A): + pass +x = None # type: Optional[C] +x.foo() # E: Some element of union has no attribute "foo" + +[case testIsinstanceAndOptionalAndAnyBase] +from typing import Any, Optional + +B = None # type: Any +class A(B): pass + +def f(a: Optional[A]): + reveal_type(a) # E: Revealed type is 'Union[__main__.A, builtins.None]' + if a is not None: + reveal_type(a) # E: Revealed type is '__main__.A' + else: + reveal_type(a) # E: Revealed type is 'builtins.None' + reveal_type(a) # E: Revealed type is 'Union[__main__.A, builtins.None]' +[builtins fixtures/isinstance.pyi] + +[case testFlattenOptionalUnion] +from typing import Optional, Union + +x: Optional[Union[int, str]] +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.None]' +y: Optional[Union[int, None]] +reveal_type(y) # E: Revealed type is 'Union[builtins.int, builtins.None]' diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 2ac3619c438c..e33f3cc0a411 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -723,11 +723,11 @@ try: except BaseException as e1: reveal_type(e1) # E: Revealed type is 'builtins.BaseException' except (E1, BaseException) as e2: - reveal_type(e2) # E: Revealed type is 'Any' + reveal_type(e2) # E: Revealed type is 'Union[Any, builtins.BaseException]' except (E1, E2) as e3: - reveal_type(e3) # E: Revealed type is 'Any' + reveal_type(e3) # E: Revealed type is 'Union[Any, __main__.E2]' except (E1, E2, BaseException) as e4: - reveal_type(e4) # E: Revealed type is 'Any' + reveal_type(e4) # E: Revealed type is 'Union[Any, builtins.BaseException]' try: pass except E1 as e1: diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index c836fe8cd41e..08345706f0d9 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -826,7 +826,7 @@ def g(x: Union[str, Tuple[str, str]]) -> None: [builtins fixtures/tuple.pyi] [out] -[case testTupleMeetTUpleAnyComplex] +[case testTupleMeetTupleAnyComplex] from typing import Tuple, Union Pair = Tuple[int, int] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 78fade8c8258..74d958dee3df 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -610,3 +610,60 @@ def f() -> None: A = TypedDict('A', {'x': int}) A # E: Name 'A' is not defined [builtins fixtures/dict.pyi] + + +-- Union simplification / proper subtype checks + +[case testTypedDictUnionSimplification] +from typing import TypeVar, Union, Any, cast +from mypy_extensions import TypedDict + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +C = TypedDict('C', {'a': int}) +D = TypedDict('D', {'a': int, 'b': int}) +E = TypedDict('E', {'a': str}) +F = TypedDict('F', {'x': int}) +G = TypedDict('G', {'a': Any}) + +c = C(a=1) +d = D(a=1, b=1) +e = E(a='') +f = F(x=1) +g = G(a=cast(Any, 1)) # Work around #2610 + +reveal_type(u(d, d)) # E: Revealed type is 'TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(u(c, d)) # E: Revealed type is 'TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(u(d, c)) # E: Revealed type is 'TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(u(c, e)) # E: Revealed type is 'Union[TypedDict(a=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.str]), TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +reveal_type(u(e, c)) # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(a=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.str])]' +reveal_type(u(c, f)) # E: Revealed type is 'Union[TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +reveal_type(u(f, c)) # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +reveal_type(u(c, g)) # E: Revealed type is 'Union[TypedDict(a=Any, _fallback=typing.Mapping[builtins.str, Any]), TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +reveal_type(u(g, c)) # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(a=Any, _fallback=typing.Mapping[builtins.str, Any])]' +[builtins fixtures/dict.pyi] + +[case testTypedDictUnionSimplification2] +from typing import TypeVar, Union, Mapping, Any +from mypy_extensions import TypedDict + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +C = TypedDict('C', {'a': int, 'b': int}) + +c = C(a=1, b=1) +m_s_i: Mapping[str, int] +m_s_s: Mapping[str, str] +m_i_i: Mapping[int, int] +m_s_a: Mapping[str, Any] + +reveal_type(u(c, m_s_i)) # E: Revealed type is 'typing.Mapping*[builtins.str, builtins.int]' +reveal_type(u(m_s_i, c)) # E: Revealed type is 'typing.Mapping*[builtins.str, builtins.int]' +reveal_type(u(c, m_s_s)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, builtins.str], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +reveal_type(u(c, m_i_i)) # E: Revealed type is 'Union[typing.Mapping*[builtins.int, builtins.int], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +reveal_type(u(c, m_s_a)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, Any], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index f6a149e354b8..4f2539f10a43 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -37,7 +37,7 @@ def f(x: Union[int, str]) -> None: [case testUnionAnyIsInstance] from typing import Any, Union -def func(v:Union[int, Any]) -> None: +def func(v: Union[int, Any]) -> None: if isinstance(v, int): reveal_type(v) # E: Revealed type is 'builtins.int' else: @@ -61,7 +61,8 @@ y = w.y z = w.y # E: Incompatible types in assignment (expression has type "int", variable has type "str") w.y = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") y = x.y # E: Some element of union has no attribute "y" -z = x.y # E: Some element of union has no attribute "y" +zz = x.y # E: Some element of union has no attribute "y" +z = zz # E: Incompatible types in assignment (expression has type "Union[int, Any]", variable has type "str") [builtins fixtures/isinstance.pyi] @@ -186,9 +187,8 @@ a = None # type: Any reveal_type(u(C(), None)) # E: Revealed type is '__main__.C*' reveal_type(u(None, C())) # E: Revealed type is '__main__.C*' -# This will be fixed later -reveal_type(u(C(), a)) # E: Revealed type is 'Any' -reveal_type(u(a, C())) # E: Revealed type is 'Any' +reveal_type(u(C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]' +reveal_type(u(a, C())) # E: Revealed type is 'Union[__main__.C*, Any]' reveal_type(u(C(), C())) # E: Revealed type is '__main__.C*' reveal_type(u(a, a)) # E: Revealed type is 'Any' @@ -220,3 +220,219 @@ class M(Generic[V]): def f(x: M[C]) -> None: y = x.get(None) reveal_type(y) # E: Revealed type is '__main__.C' + +[case testUnionSimplificationSpecialCases] +from typing import Any, TypeVar, Union + +class C(Any): pass + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +a = None # type: Any + +# Base-class-Any and None, simplify +reveal_type(u(C(), None)) # E: Revealed type is '__main__.C*' +reveal_type(u(None, C())) # E: Revealed type is '__main__.C*' + +# Normal instance type and None, simplify +reveal_type(u(1, None)) # E: Revealed type is 'builtins.int*' +reveal_type(u(None, 1)) # E: Revealed type is 'builtins.int*' + +# Normal instance type and base-class-Any, no simplification +reveal_type(u(C(), 1)) # E: Revealed type is 'Union[builtins.int*, __main__.C*]' +reveal_type(u(1, C())) # E: Revealed type is 'Union[__main__.C*, builtins.int*]' + +# Normal instance type and Any, no simplification +reveal_type(u(1, a)) # E: Revealed type is 'Union[Any, builtins.int*]' +reveal_type(u(a, 1)) # E: Revealed type is 'Union[builtins.int*, Any]' + +# Any and base-class-Any, no simplificaiton +reveal_type(u(C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]' +reveal_type(u(a, C())) # E: Revealed type is 'Union[__main__.C*, Any]' + +# Two normal instance types, simplify +reveal_type(u(1, object())) # E: Revealed type is 'builtins.object*' +reveal_type(u(object(), 1)) # E: Revealed type is 'builtins.object*' + +# Two normal instance types, no simplification +reveal_type(u(1, '')) # E: Revealed type is 'Union[builtins.str*, builtins.int*]' +reveal_type(u('', 1)) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' + +[case testUnionSimplificationWithDuplicateItems] +from typing import Any, TypeVar, Union + +class C(Any): pass + +T = TypeVar('T') +S = TypeVar('S') +R = TypeVar('R') +def u(x: T, y: S, z: R) -> Union[R, S, T]: pass + +a = None # type: Any + +reveal_type(u(1, 1, 1)) # E: Revealed type is 'builtins.int*' +reveal_type(u(C(), C(), None)) # E: Revealed type is '__main__.C*' +reveal_type(u(a, a, 1)) # E: Revealed type is 'Union[builtins.int*, Any]' +reveal_type(u(a, C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]' +reveal_type(u('', 1, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' + +[case testUnionAndBinaryOperation] +from typing import Union +class A: pass +def f(x: Union[int, str, A]): + x + object() # E: Unsupported left operand type for + (some union) \ + # E: Unsupported operand types for + (likely involving Union) + +[case testNarrowingDownNamedTupleUnion] +from typing import NamedTuple, Union + +A = NamedTuple('A', [('y', int)]) +B = NamedTuple('B', [('x', int)]) +C = NamedTuple('C', [('x', int)]) + +def foo(a: Union[A, B, C]): + if isinstance(a, (B, C)): + reveal_type(a) # E: Revealed type is 'Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]]' + a.x + a.y # E: Some element of union has no attribute "y" + b = a # type: Union[B, C] +[builtins fixtures/isinstance.pyi] + +[case testSimplifyingUnionAndTypePromotions] +from typing import TypeVar, Union +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +reveal_type(u(1, 2.3)) # E: Revealed type is 'builtins.float*' +reveal_type(u(2.3, 1)) # E: Revealed type is 'builtins.float*' +reveal_type(u(False, 2.2)) # E: Revealed type is 'builtins.float*' +reveal_type(u(2.2, False)) # E: Revealed type is 'builtins.float*' +[builtins fixtures/primitives.pyi] + +[case testSimplifyingUnionWithTypeTypes1] +from typing import TypeVar, Union, Type, Any + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +t_o = None # type: Type[object] +t_s = None # type: Type[str] +t_a = None # type: Type[Any] + +# Two identical items +reveal_type(u(t_o, t_o)) # E: Revealed type is 'Type[builtins.object]' +reveal_type(u(t_s, t_s)) # E: Revealed type is 'Type[builtins.str]' +reveal_type(u(t_a, t_a)) # E: Revealed type is 'Type[Any]' +reveal_type(u(type, type)) # E: Revealed type is 'def (x: builtins.Any) -> builtins.type' + +# One type, other non-type +reveal_type(u(t_s, 1)) # E: Revealed type is 'Union[builtins.int*, Type[builtins.str]]' +reveal_type(u(1, t_s)) # E: Revealed type is 'Union[Type[builtins.str], builtins.int*]' +reveal_type(u(type, 1)) # E: Revealed type is 'Union[builtins.int*, def (x: builtins.Any) -> builtins.type]' +reveal_type(u(1, type)) # E: Revealed type is 'Union[def (x: builtins.Any) -> builtins.type, builtins.int*]' +reveal_type(u(t_a, 1)) # E: Revealed type is 'Union[builtins.int*, Type[Any]]' +reveal_type(u(1, t_a)) # E: Revealed type is 'Union[Type[Any], builtins.int*]' +reveal_type(u(t_o, 1)) # E: Revealed type is 'Union[builtins.int*, Type[builtins.object]]' +reveal_type(u(1, t_o)) # E: Revealed type is 'Union[Type[builtins.object], builtins.int*]' + +[case testSimplifyingUnionWithTypeTypes2] +from typing import TypeVar, Union, Type, Any + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +t_o = None # type: Type[object] +t_s = None # type: Type[str] +t_a = None # type: Type[Any] +t = None # type: type + +# Union with object +reveal_type(u(t_o, object())) # E: Revealed type is 'builtins.object*' +reveal_type(u(object(), t_o)) # E: Revealed type is 'builtins.object*' +reveal_type(u(t_s, object())) # E: Revealed type is 'builtins.object*' +reveal_type(u(object(), t_s)) # E: Revealed type is 'builtins.object*' +reveal_type(u(t_a, object())) # E: Revealed type is 'builtins.object*' +reveal_type(u(object(), t_a)) # E: Revealed type is 'builtins.object*' + +# Union between type objects +reveal_type(u(t_o, t_a)) # E: Revealed type is 'Union[Type[Any], Type[builtins.object]]' +reveal_type(u(t_a, t_o)) # E: Revealed type is 'Union[Type[builtins.object], Type[Any]]' +reveal_type(u(t_s, t_o)) # E: Revealed type is 'Type[builtins.object]' +reveal_type(u(t_o, t_s)) # E: Revealed type is 'Type[builtins.object]' +reveal_type(u(t_o, type)) # E: Revealed type is 'Type[builtins.object]' +reveal_type(u(type, t_o)) # E: Revealed type is 'Type[builtins.object]' +reveal_type(u(t_a, t)) # E: Revealed type is 'builtins.type*' +reveal_type(u(t, t_a)) # E: Revealed type is 'builtins.type*' +# The following should arguably not be simplified, but it's unclear how to fix then +# without causing regressions elsewhere. +reveal_type(u(t_o, t)) # E: Revealed type is 'builtins.type*' +reveal_type(u(t, t_o)) # E: Revealed type is 'builtins.type*' + +[case testNotSimplifyingUnionWithMetaclass] +from typing import TypeVar, Union, Type, Any + +class M(type): pass +class M2(M): pass +class A(metaclass=M): pass + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +a: Any +t_a: Type[A] + +reveal_type(u(M(*a), t_a)) # E: Revealed type is 'Union[Type[__main__.A], __main__.M*]' +reveal_type(u(t_a, M(*a))) # E: Revealed type is 'Union[__main__.M*, Type[__main__.A]]' + +reveal_type(u(M2(*a), t_a)) # E: Revealed type is 'Union[Type[__main__.A], __main__.M2*]' +reveal_type(u(t_a, M2(*a))) # E: Revealed type is 'Union[__main__.M2*, Type[__main__.A]]' + +[case testSimplifyUnionWithCallable] +from typing import TypeVar, Union, Any, Callable + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +class C: pass +class D(C): pass + +D_C: Callable[[D], C] +A_C: Callable[[Any], C] +D_A: Callable[[D], Any] +C_C: Callable[[C], C] +D_D: Callable[[D], D] +i_C: Callable[[int], C] + +# TODO: Test argument names and kinds once we have flexible callable types. + +reveal_type(u(D_C, D_C)) # E: Revealed type is 'def (__main__.D) -> __main__.C' + +reveal_type(u(A_C, D_C)) # E: Revealed type is 'Union[def (__main__.D) -> __main__.C, def (Any) -> __main__.C]' +reveal_type(u(D_C, A_C)) # E: Revealed type is 'Union[def (Any) -> __main__.C, def (__main__.D) -> __main__.C]' + +reveal_type(u(D_A, D_C)) # E: Revealed type is 'Union[def (__main__.D) -> __main__.C, def (__main__.D) -> Any]' +reveal_type(u(D_C, D_A)) # E: Revealed type is 'Union[def (__main__.D) -> Any, def (__main__.D) -> __main__.C]' + +reveal_type(u(D_C, C_C)) # E: Revealed type is 'def (__main__.D) -> __main__.C' +reveal_type(u(C_C, D_C)) # E: Revealed type is 'def (__main__.D) -> __main__.C' + +reveal_type(u(D_C, D_D)) # E: Revealed type is 'def (__main__.D) -> __main__.C' +reveal_type(u(D_D, D_C)) # E: Revealed type is 'def (__main__.D) -> __main__.C' + +reveal_type(u(D_C, i_C)) # E: Revealed type is 'Union[def (builtins.int) -> __main__.C, def (__main__.D) -> __main__.C]' + +[case testUnionOperatorMethodSpecialCase] +from typing import Union +class C: + def __le__(self, x: 'C') -> int: ... +class D: + def __le__(self, other) -> int: ... +class E: + def __ge__(self, other: Union[C, D]) -> int: ... diff --git a/test-data/unit/fixtures/primitives.pyi b/test-data/unit/fixtures/primitives.pyi index 3b0ba0ce3719..4b5611b0bace 100644 --- a/test-data/unit/fixtures/primitives.pyi +++ b/test-data/unit/fixtures/primitives.pyi @@ -11,7 +11,7 @@ class int: def __add__(self, i: int) -> int: pass class float: pass class complex: pass -class bool: pass +class bool(int): pass class str: def __add__(self, s: str) -> str: pass def format(self, *args) -> str: pass From 603d371558f6cd083e8df7ce3bd5fa5b2d39b2db Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 30 Mar 2017 14:17:02 +0100 Subject: [PATCH 02/10] Fix strict optional and partial type special case and fix test --- mypy/binder.py | 8 ++++++-- test-data/unit/check-optional.test | 9 +++++++++ test-data/unit/check-unions.test | 6 +++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 55f02179cf65..34c80e544e94 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -1,7 +1,7 @@ from typing import (Dict, List, Set, Iterator, Union) from contextlib import contextmanager -from mypy.types import Type, AnyType, PartialType +from mypy.types import Type, AnyType, PartialType, UnionType, NoneTyp from mypy.nodes import (Key, Node, Expression, Var, RefExpr, SymbolTableNode) from mypy.subtypes import is_subtype @@ -229,7 +229,11 @@ def assign_type(self, expr: Expression, if (isinstance(self.most_recent_enclosing_type(expr, type), AnyType) and not restrict_any): pass - elif isinstance(type, AnyType): + elif (isinstance(type, AnyType) + and (not isinstance(declared_type, UnionType) + or not any(isinstance(item, NoneTyp) for item in declared_type.items))): + # Assigning an Any value doesn't affect the type to avoid false negatives, unless + # there is an Any item in a declared union type. self.put(expr, declared_type) else: self.put(expr, type) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 05a9c93529f9..af0f28d74157 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -163,6 +163,15 @@ x = None x = 1 reveal_type(x) # E: Revealed type is 'builtins.int' +[case testInferOptionalAnyType] +from typing import Any +x = None +a = None # type: Any +if bool(): + x = a + reveal_type(x) # E: Revealed type is 'Any' +reveal_type(x) # E: Revealed type is 'Union[Any, builtins.None]' +[builtins fixtures/bool.pyi] [case testInferOptionalTypeFromOptional] from typing import Optional diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 4f2539f10a43..8c597bef9ac4 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -327,13 +327,13 @@ t_a = None # type: Type[Any] reveal_type(u(t_o, t_o)) # E: Revealed type is 'Type[builtins.object]' reveal_type(u(t_s, t_s)) # E: Revealed type is 'Type[builtins.str]' reveal_type(u(t_a, t_a)) # E: Revealed type is 'Type[Any]' -reveal_type(u(type, type)) # E: Revealed type is 'def (x: builtins.Any) -> builtins.type' +reveal_type(u(type, type)) # E: Revealed type is 'def (x: Any) -> builtins.type' # One type, other non-type reveal_type(u(t_s, 1)) # E: Revealed type is 'Union[builtins.int*, Type[builtins.str]]' reveal_type(u(1, t_s)) # E: Revealed type is 'Union[Type[builtins.str], builtins.int*]' -reveal_type(u(type, 1)) # E: Revealed type is 'Union[builtins.int*, def (x: builtins.Any) -> builtins.type]' -reveal_type(u(1, type)) # E: Revealed type is 'Union[def (x: builtins.Any) -> builtins.type, builtins.int*]' +reveal_type(u(type, 1)) # E: Revealed type is 'Union[builtins.int*, def (x: Any) -> builtins.type]' +reveal_type(u(1, type)) # E: Revealed type is 'Union[def (x: Any) -> builtins.type, builtins.int*]' reveal_type(u(t_a, 1)) # E: Revealed type is 'Union[builtins.int*, Type[Any]]' reveal_type(u(1, t_a)) # E: Revealed type is 'Union[Type[Any], builtins.int*]' reveal_type(u(t_o, 1)) # E: Revealed type is 'Union[builtins.int*, Type[builtins.object]]' From 29a74f35a3d5a16299d771be9c1473306122b8a2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 30 Mar 2017 18:21:32 +0100 Subject: [PATCH 03/10] Fix lint --- mypy/binder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 34c80e544e94..7ba556f8797c 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -230,8 +230,8 @@ def assign_type(self, expr: Expression, and not restrict_any): pass elif (isinstance(type, AnyType) - and (not isinstance(declared_type, UnionType) - or not any(isinstance(item, NoneTyp) for item in declared_type.items))): + and (not isinstance(declared_type, UnionType) + or not any(isinstance(item, NoneTyp) for item in declared_type.items))): # Assigning an Any value doesn't affect the type to avoid false negatives, unless # there is an Any item in a declared union type. self.put(expr, declared_type) From 6f9ff36af05142ec48ec263d3ad828a874652803 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 31 Mar 2017 13:03:51 +0100 Subject: [PATCH 04/10] Make is_subtype and is_proper_subtype do promotion the same way Fixes #1850. --- mypy/subtypes.py | 9 +++++---- test-data/unit/check-unions.test | 8 ++++++++ test-data/unit/fixtures/list.pyi | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b96618da28eb..3169864fe85c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -128,10 +128,11 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(right, TupleType) and right.fallback.type.is_enum: return is_subtype(left, right.fallback) if isinstance(right, Instance): - if left.type._promote and is_subtype( - left.type._promote, self.right, self.check_type_parameter, - ignore_pos_arg_names=self.ignore_pos_arg_names): - return True + for base in left.type.mro: + if base._promote and is_subtype( + base._promote, self.right, self.check_type_parameter, + ignore_pos_arg_names=self.ignore_pos_arg_names): + return True rname = right.type.fullname() if not left.type.has_base(rname) and rname != 'builtins.object': return False diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 8c597bef9ac4..5b1363876932 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -436,3 +436,11 @@ class D: def __le__(self, other) -> int: ... class E: def __ge__(self, other: Union[C, D]) -> int: ... + +[case testUnionSimplificationWithBoolIntAndFloat] +from typing import List, Union +l = reveal_type([]) # type: List[Union[bool, int, float]] \ + # E: Revealed type is 'builtins.list[builtins.float]' +reveal_type(l) \ + # E: Revealed type is 'builtins.list[Union[builtins.bool, builtins.int, builtins.float]]' +[builtins fixtures/list.pyi] diff --git a/test-data/unit/fixtures/list.pyi b/test-data/unit/fixtures/list.pyi index ce9978a38c4c..4d811813776d 100644 --- a/test-data/unit/fixtures/list.pyi +++ b/test-data/unit/fixtures/list.pyi @@ -26,7 +26,8 @@ class list(Iterable[T], Generic[T]): class tuple: pass class function: pass class int: pass +class float: pass class str: pass -class bool: pass +class bool(int): pass property = object() # Dummy definition. From d9b6e8192c10b3b8c3e1e175ee513420574a9d95 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 3 Apr 2017 12:54:25 -0700 Subject: [PATCH 05/10] Drop reference to ErrorType (recently expunged) --- mypy/subtypes.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 6bfff35b3307..2642f603cc96 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -563,10 +563,6 @@ def visit_unbound_type(self, left: UnboundType) -> bool: # from unions, which could filter out some bogus messages. return True - def visit_error_type(self, left: ErrorType) -> bool: - # This isn't a real type. - return False - def visit_type_list(self, left: TypeList) -> bool: assert False, 'Should not happen' From f4aebadb52f0f4fbc6980914ed328ee24bde7ffd Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 3 Apr 2017 13:15:24 -0700 Subject: [PATCH 06/10] Fix merge mistake --- test-data/unit/check-typeddict.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 8c762137d7ca..f8e4f721ad7e 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -704,6 +704,7 @@ reveal_type(u(m_s_i, c)) # E: Revealed type is 'typing.Mapping*[builtins.str, bu reveal_type(u(c, m_s_s)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, builtins.str], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' reveal_type(u(c, m_i_i)) # E: Revealed type is 'Union[typing.Mapping*[builtins.int, builtins.int], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' reveal_type(u(c, m_s_a)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, Any], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]' +[builtins fixtures/dict.pyi] -- Use dict literals From 69990cebe69357223b78f29c58e128d23e6811ee Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 3 Apr 2017 16:43:57 -0700 Subject: [PATCH 07/10] Fix another merge mistake --- test-data/unit/check-isinstance.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 87cb1f535aa2..fb6f88871a30 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1448,6 +1448,7 @@ def f(x: Type[int]) -> None: else: reveal_type(x) # Unreachable reveal_type(x) # E: Revealed type is 'Type[builtins.int]' +[builtins fixtures/isinstance.pyi] [case testIsinstanceVariableSubstitution] From d3726187faeb40015b5df61a48654a7f42731487 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 4 Apr 2017 15:34:56 +0100 Subject: [PATCH 08/10] Update based on review --- mypy/binder.py | 4 ++-- test-data/unit/check-unions.test | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 38f5123da9cf..3ae2952a5169 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -228,8 +228,8 @@ def assign_type(self, expr: Expression, and not restrict_any): pass elif (isinstance(type, AnyType) - and (not isinstance(declared_type, UnionType) - or not any(isinstance(item, NoneTyp) for item in declared_type.items))): + and not (isinstance(declared_type, UnionType) + and any(isinstance(item, AnyType) for item in declared_type.items))): # Assigning an Any value doesn't affect the type to avoid false negatives, unless # there is an Any item in a declared union type. self.put(expr, declared_type) diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 5b1363876932..effdc9987efd 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -444,3 +444,22 @@ l = reveal_type([]) # type: List[Union[bool, int, float]] \ reveal_type(l) \ # E: Revealed type is 'builtins.list[Union[builtins.bool, builtins.int, builtins.float]]' [builtins fixtures/list.pyi] + +[case testUnionSimplificationWithBoolIntAndFloat2] +from typing import List, Union +l = reveal_type([]) # type: List[Union[bool, int, float, str]] \ + # E: Revealed type is 'builtins.list[Union[builtins.float, builtins.str]]' +reveal_type(l) \ + # E: Revealed type is 'builtins.list[Union[builtins.bool, builtins.int, builtins.float, builtins.str]]' +[builtins fixtures/list.pyi] + +[case testAssignAnyToUnion] +from typing import Union, Any +x: Union[int, str] +a: Any +if bool(): + x = a + # TODO: Maybe we should infer Any as the type instead. + reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' +[builtins fixtures/bool.pyi] From 2f348d2c75355d819218bc6b3319b3f296fb3e2f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 4 Apr 2017 15:37:21 +0100 Subject: [PATCH 09/10] Add test case --- test-data/unit/check-unions.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index effdc9987efd..c163b4f77fff 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -463,3 +463,14 @@ if bool(): reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' [builtins fixtures/bool.pyi] + +[case testAssignAnyToUnionWithAny] +from typing import Union, Any +x: Union[int, Any] +a: Any +if bool(): + x = a + # TODO: Maybe we should infer Any as the type instead. + reveal_type(x) # E: Revealed type is 'Any' +reveal_type(x) # E: Revealed type is 'Union[builtins.int, Any]' +[builtins fixtures/bool.pyi] From e1d97a2ecaa6cec670e8361d500462f70c4d7778 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 4 Apr 2017 15:57:42 +0100 Subject: [PATCH 10/10] Fix redundant TODO due to copy paste --- test-data/unit/check-unions.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index c163b4f77fff..8a89dabad82b 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -470,7 +470,6 @@ x: Union[int, Any] a: Any if bool(): x = a - # TODO: Maybe we should infer Any as the type instead. reveal_type(x) # E: Revealed type is 'Any' reveal_type(x) # E: Revealed type is 'Union[builtins.int, Any]' [builtins fixtures/bool.pyi]