diff --git a/mypy/applytype.py b/mypy/applytype.py index 847c399a2e8a..ffadeab0dbde 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -1,6 +1,5 @@ from typing import Callable, Dict, Optional, Sequence -import mypy.sametypes import mypy.subtypes from mypy.expandtype import expand_type from mypy.nodes import Context @@ -41,7 +40,7 @@ def get_target_type( if isinstance(type, TypeVarType) and type.values: # Allow substituting T1 for T if every allowed value of T1 # is also a legal value of T. - if all(any(mypy.sametypes.is_same_type(v, v1) for v in values) for v1 in type.values): + if all(any(mypy.subtypes.is_same_type(v, v1) for v in values) for v1 in type.values): return type matching = [] for value in values: diff --git a/mypy/binder.py b/mypy/binder.py index df2f9d8b4c01..3996cb55584b 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -8,8 +8,7 @@ from mypy.join import join_simple from mypy.literals import Key, literal, literal_hash, subkeys from mypy.nodes import AssignmentExpr, Expression, IndexExpr, MemberExpr, NameExpr, RefExpr, Var -from mypy.sametypes import is_same_type -from mypy.subtypes import is_subtype +from mypy.subtypes import is_same_type, is_subtype from mypy.types import AnyType, NoneType, PartialType, Type, TypeOfAny, UnionType, get_proper_type BindableExpression: _TypeAlias = Union[IndexExpr, MemberExpr, AssignmentExpr, NameExpr] diff --git a/mypy/checker.py b/mypy/checker.py index cb9d38cf9b37..671478118da6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -133,7 +133,6 @@ ) from mypy.options import Options from mypy.plugin import CheckerPluginInterface, Plugin -from mypy.sametypes import is_same_type from mypy.scope import Scope from mypy.semanal import is_trivial_body, refers_to_fullname, set_callable_name from mypy.semanal_enum import ENUM_BASES, ENUM_SPECIAL_PROPS @@ -145,6 +144,7 @@ is_equivalent, is_more_precise, is_proper_subtype, + is_same_type, is_subtype, restrict_subtype_away, unify_generic_callable, diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d6243856ab07..9c31cc5d3f7e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -95,10 +95,9 @@ MethodSigContext, Plugin, ) -from mypy.sametypes import is_same_type from mypy.semanal_enum import ENUM_BASES from mypy.state import state -from mypy.subtypes import is_equivalent, is_subtype, non_method_protocol_members +from mypy.subtypes import is_equivalent, is_same_type, is_subtype, non_method_protocol_members from mypy.traverser import has_await_expression from mypy.typeanal import ( check_for_explicit_any, @@ -3574,7 +3573,7 @@ def visit_cast_expr(self, expr: CastExpr) -> Type: if ( options.warn_redundant_casts and not isinstance(get_proper_type(target_type), AnyType) - and is_same_type(source_type, target_type) + and source_type == target_type ): self.msg.redundant_cast(target_type, expr) if options.disallow_any_unimported and has_any_from_unimported_type(target_type): diff --git a/mypy/constraints.py b/mypy/constraints.py index 00309462db27..0ca6a3e085f0 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -4,13 +4,12 @@ from typing_extensions import Final -import mypy.sametypes import mypy.subtypes import mypy.typeops from mypy.argmap import ArgTypeExpander from mypy.erasetype import erase_typevars from mypy.maptype import map_instance_to_supertype -from mypy.nodes import CONTRAVARIANT, COVARIANT, ArgKind +from mypy.nodes import ARG_OPT, ARG_POS, CONTRAVARIANT, COVARIANT, ArgKind from mypy.types import ( TUPLE_LIKE_INSTANCE_NAMES, AnyType, @@ -141,7 +140,9 @@ def infer_constraints(template: Type, actual: Type, direction: int) -> List[Cons The constraints are represented as Constraint objects. """ - if any(get_proper_type(template) == get_proper_type(t) for t in TypeState._inferring): + if any( + get_proper_type(template) == get_proper_type(t) for t in reversed(TypeState._inferring) + ): return [] if isinstance(template, TypeAliasType) and template.is_recursive: # This case requires special care because it may cause infinite recursion. @@ -341,7 +342,7 @@ def is_same_constraint(c1: Constraint, c2: Constraint) -> bool: return ( c1.type_var == c2.type_var and (c1.op == c2.op or skip_op_check) - and mypy.sametypes.is_same_type(c1.target, c2.target) + and mypy.subtypes.is_same_type(c1.target, c2.target) ) @@ -474,9 +475,7 @@ def visit_instance(self, template: Instance) -> List[Constraint]: if isinstance(actual, (CallableType, Overloaded)) and template.type.is_protocol: if template.type.protocol_members == ["__call__"]: # Special case: a generic callback protocol - if not any( - mypy.sametypes.is_same_type(template, t) for t in template.type.inferring - ): + if not any(template == t for t in template.type.inferring): template.type.inferring.append(template) call = mypy.subtypes.find_member( "__call__", template, actual, is_operator=True @@ -635,7 +634,7 @@ def visit_instance(self, template: Instance) -> List[Constraint]: # Note that we use is_protocol_implementation instead of is_subtype # because some type may be considered a subtype of a protocol # due to _promote, but still not implement the protocol. - not any(mypy.sametypes.is_same_type(template, t) for t in template.type.inferring) + not any(template == t for t in reversed(template.type.inferring)) and mypy.subtypes.is_protocol_implementation(instance, erased) ): template.type.inferring.append(template) @@ -651,7 +650,7 @@ def visit_instance(self, template: Instance) -> List[Constraint]: and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. - not any(mypy.sametypes.is_same_type(instance, i) for i in instance.type.inferring) + not any(instance == i for i in reversed(instance.type.inferring)) and mypy.subtypes.is_protocol_implementation(erased, instance) ): instance.type.inferring.append(instance) @@ -734,6 +733,8 @@ def visit_callable_type(self, template: CallableType) -> List[Constraint]: cactual_ps = cactual.param_spec() if not cactual_ps: + max_prefix_len = len([k for k in cactual.arg_kinds if k in (ARG_POS, ARG_OPT)]) + prefix_len = min(prefix_len, max_prefix_len) res.append( Constraint( param_spec.id, diff --git a/mypy/messages.py b/mypy/messages.py index 0b5d09c575b4..629d04c85dc5 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -62,13 +62,13 @@ reverse_builtin_aliases, ) from mypy.operators import op_methods, op_methods_to_symbols -from mypy.sametypes import is_same_type from mypy.subtypes import ( IS_CLASS_OR_STATIC, IS_CLASSVAR, IS_SETTABLE, find_member, get_member_flags, + is_same_type, is_subtype, ) from mypy.typeops import separate_union_literals diff --git a/mypy/sametypes.py b/mypy/sametypes.py deleted file mode 100644 index 33f2cdf7aa16..000000000000 --- a/mypy/sametypes.py +++ /dev/null @@ -1,245 +0,0 @@ -from typing import List, Sequence, Set, Tuple - -from mypy.typeops import is_simple_literal, make_simplified_union, tuple_fallback -from mypy.types import ( - AnyType, - CallableType, - DeletedType, - ErasedType, - Instance, - LiteralType, - NoneType, - Overloaded, - Parameters, - ParamSpecType, - PartialType, - ProperType, - TupleType, - Type, - TypeAliasType, - TypedDictType, - TypeType, - TypeVarTupleType, - TypeVarType, - TypeVisitor, - UnboundType, - UninhabitedType, - UnionType, - UnpackType, - get_proper_type, -) - - -def is_same_type(left: Type, right: Type) -> bool: - """Is 'left' the same type as 'right'?""" - - if isinstance(left, TypeAliasType) and isinstance(right, TypeAliasType): - if left.is_recursive and right.is_recursive: - return left.alias == right.alias and left.args == right.args - - left = get_proper_type(left) - right = get_proper_type(right) - - if isinstance(right, UnboundType): - # Make unbound types same as anything else to reduce the number of - # generated spurious error messages. - return True - else: - # Simplify types to canonical forms. - # - # There are multiple possible union types that represent the same type, - # such as Union[int, bool, str] and Union[int, str]. Also, some union - # types can be simplified to non-union types such as Union[int, bool] - # -> int. It would be nice if we always had simplified union types but - # this is currently not the case, though it often is. - left = simplify_union(left) - right = simplify_union(right) - - return left.accept(SameTypeVisitor(right)) - - -def simplify_union(t: Type) -> ProperType: - t = get_proper_type(t) - if isinstance(t, UnionType): - return make_simplified_union(t.items) - return t - - -def is_same_types(a1: Sequence[Type], a2: Sequence[Type]) -> bool: - if len(a1) != len(a2): - return False - for i in range(len(a1)): - if not is_same_type(a1[i], a2[i]): - return False - return True - - -def _extract_literals(u: UnionType) -> Tuple[Set[Type], List[Type]]: - """Given a UnionType, separate out its items into a set of simple literals and a remainder list - This is a useful helper to avoid O(n**2) behavior when comparing large unions, which can often - result from large enums in contexts where type narrowing removes a small subset of entries. - """ - lit: Set[Type] = set() - rem: List[Type] = [] - for i in u.relevant_items(): - i = get_proper_type(i) - if is_simple_literal(i): - lit.add(i) - else: - rem.append(i) - return lit, rem - - -class SameTypeVisitor(TypeVisitor[bool]): - """Visitor for checking whether two types are the 'same' type.""" - - def __init__(self, right: ProperType) -> None: - self.right = right - - # visit_x(left) means: is left (which is an instance of X) the same type as - # right? - - def visit_unbound_type(self, left: UnboundType) -> bool: - return True - - def visit_any(self, left: AnyType) -> bool: - return isinstance(self.right, AnyType) - - def visit_none_type(self, left: NoneType) -> bool: - return isinstance(self.right, NoneType) - - def visit_uninhabited_type(self, t: UninhabitedType) -> bool: - return isinstance(self.right, UninhabitedType) - - def visit_erased_type(self, left: ErasedType) -> bool: - # We can get here when isinstance is used inside a lambda - # whose type is being inferred. In any event, we have no reason - # to think that an ErasedType will end up being the same as - # any other type, except another ErasedType (for protocols). - return isinstance(self.right, ErasedType) - - def visit_deleted_type(self, left: DeletedType) -> bool: - return isinstance(self.right, DeletedType) - - def visit_instance(self, left: Instance) -> bool: - return ( - isinstance(self.right, Instance) - and left.type == self.right.type - and is_same_types(left.args, self.right.args) - and left.last_known_value == self.right.last_known_value - ) - - def visit_type_alias_type(self, left: TypeAliasType) -> bool: - # Similar to protocols, two aliases with the same targets return False here, - # but both is_subtype(t, s) and is_subtype(s, t) return True. - return ( - isinstance(self.right, TypeAliasType) - and left.alias == self.right.alias - and is_same_types(left.args, self.right.args) - ) - - def visit_type_var(self, left: TypeVarType) -> bool: - return isinstance(self.right, TypeVarType) and left.id == self.right.id - - def visit_param_spec(self, left: ParamSpecType) -> bool: - # Ignore upper bound since it's derived from flavor. - return ( - isinstance(self.right, ParamSpecType) - and left.id == self.right.id - and left.flavor == self.right.flavor - ) - - def visit_type_var_tuple(self, left: TypeVarTupleType) -> bool: - return isinstance(self.right, TypeVarTupleType) and left.id == self.right.id - - def visit_unpack_type(self, left: UnpackType) -> bool: - return isinstance(self.right, UnpackType) and is_same_type(left.type, self.right.type) - - def visit_parameters(self, left: Parameters) -> bool: - return ( - isinstance(self.right, Parameters) - and left.arg_names == self.right.arg_names - and is_same_types(left.arg_types, self.right.arg_types) - and left.arg_kinds == self.right.arg_kinds - ) - - def visit_callable_type(self, left: CallableType) -> bool: - # FIX generics - if isinstance(self.right, CallableType): - cright = self.right - return ( - is_same_type(left.ret_type, cright.ret_type) - and is_same_types(left.arg_types, cright.arg_types) - and left.arg_names == cright.arg_names - and left.arg_kinds == cright.arg_kinds - and left.is_type_obj() == cright.is_type_obj() - and left.is_ellipsis_args == cright.is_ellipsis_args - ) - else: - return False - - def visit_tuple_type(self, left: TupleType) -> bool: - if isinstance(self.right, TupleType): - return is_same_type( - tuple_fallback(left), tuple_fallback(self.right) - ) and is_same_types(left.items, self.right.items) - else: - return False - - def visit_typeddict_type(self, left: TypedDictType) -> bool: - if isinstance(self.right, TypedDictType): - if left.items.keys() != self.right.items.keys(): - return False - for (_, left_item_type, right_item_type) in left.zip(self.right): - if not is_same_type(left_item_type, right_item_type): - return False - return True - else: - return False - - def visit_literal_type(self, left: LiteralType) -> bool: - if isinstance(self.right, LiteralType): - if left.value != self.right.value: - return False - return is_same_type(left.fallback, self.right.fallback) - else: - return False - - def visit_union_type(self, left: UnionType) -> bool: - if isinstance(self.right, UnionType): - left_lit, left_rem = _extract_literals(left) - right_lit, right_rem = _extract_literals(self.right) - - if left_lit != right_lit: - return False - - # Check that everything in left is in right - for left_item in left_rem: - if not any(is_same_type(left_item, right_item) for right_item in right_rem): - return False - - # Check that everything in right is in left - for right_item in right_rem: - if not any(is_same_type(right_item, left_item) for left_item in left_rem): - return False - - return True - else: - return False - - def visit_overloaded(self, left: Overloaded) -> bool: - if isinstance(self.right, Overloaded): - return is_same_types(left.items, self.right.items) - else: - return False - - def visit_partial_type(self, left: PartialType) -> bool: - # A partial type is not fully defined, so the result is indeterminate. We shouldn't - # get here. - raise RuntimeError - - def visit_type_type(self, left: TypeType) -> bool: - if isinstance(self.right, TypeType): - return is_same_type(left.item, self.right.item) - else: - return False diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index 8e1cae3717df..e6334f9e8c0a 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -14,9 +14,8 @@ from mypy.mixedtraverser import MixedTraverserVisitor from mypy.nodes import Block, ClassDef, Context, FakeInfo, FuncItem, MypyFile, TypeInfo from mypy.options import Options -from mypy.sametypes import is_same_type from mypy.scope import Scope -from mypy.subtypes import is_subtype +from mypy.subtypes import is_same_type, is_subtype from mypy.types import ( AnyType, Instance, @@ -27,6 +26,7 @@ TypeOfAny, TypeVarTupleType, TypeVarType, + UnboundType, UnpackType, get_proper_type, get_proper_types, @@ -136,7 +136,9 @@ def check_type_var_values( context: Context, ) -> None: for actual in get_proper_types(actuals): - if not isinstance(actual, AnyType) and not any( + # TODO: bind type variables in class bases/alias targets + # so we can safely check this, currently we miss some errors. + if not isinstance(actual, (AnyType, UnboundType)) and not any( is_same_type(actual, value) for value in valids ): if len(actuals) > 1 or not isinstance(actual, Instance): diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 1c639172ffa4..11f517d4602c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -5,7 +5,6 @@ import mypy.applytype import mypy.constraints -import mypy.sametypes import mypy.typeops from mypy.erasetype import erase_type from mypy.expandtype import expand_type_by_instance @@ -241,6 +240,21 @@ def is_equivalent( ) +def is_same_type(a: Type, b: Type, ignore_promotions: bool = True) -> bool: + """Are these types proper subtypes of each other? + + This means types may have different representation (e.g. an alias, or + a non-simplified union) but are semantically exchangeable in all contexts. + """ + # Note that using ignore_promotions=True (default) makes types like int and int64 + # considered not the same type (which is the case at runtime). + # Also Union[bool, int] (if it wasn't simplified before) will be different + # from plain int, etc. + return is_proper_subtype(a, b, ignore_promotions=ignore_promotions) and is_proper_subtype( + b, a, ignore_promotions=ignore_promotions + ) + + # This is a common entry point for subtyping checks (both proper and non-proper). # Never call this private function directly, use the public versions. def _is_subtype( @@ -301,6 +315,8 @@ def check_item(left: Type, right: Type, subtype_context: SubtypeContext) -> bool return left.accept(SubtypeVisitor(orig_right, subtype_context, proper_subtype)) +# TODO: should we pass on the original flags here and in couple other places? +# This seems logical but was never done in the past for some reasons. def check_type_parameter(lefta: Type, righta: Type, variance: int, proper_subtype: bool) -> bool: def check(left: Type, right: Type) -> bool: return is_proper_subtype(left, right) if proper_subtype else is_subtype(left, right) @@ -311,7 +327,7 @@ def check(left: Type, right: Type) -> bool: return check(righta, lefta) else: if proper_subtype: - return mypy.sametypes.is_same_type(lefta, righta) + return is_same_type(lefta, righta) return is_equivalent(lefta, righta) @@ -690,7 +706,7 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool: return False for name, l, r in left.zip(right): if self.proper_subtype: - check = mypy.sametypes.is_same_type(l, r) + check = is_same_type(l, r) else: check = is_equivalent( l, diff --git a/mypy/suggestions.py b/mypy/suggestions.py index a5ff07da3ba4..ad0657d69aa5 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -64,7 +64,6 @@ reverse_builtin_aliases, ) from mypy.plugin import FunctionContext, MethodContext, Plugin -from mypy.sametypes import is_same_type from mypy.server.update import FineGrainedBuildManager from mypy.state import state from mypy.traverser import TraverserVisitor @@ -929,7 +928,7 @@ def generate_type_combinations(types: List[Type]) -> List[Type]: """ joined_type = join_type_list(types) union_type = make_simplified_union(types) - if is_same_type(joined_type, union_type): + if joined_type == union_type: return [joined_type] else: return [joined_type, union_type] diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 173d80b85426..2cc1b9c024bf 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -8,9 +8,8 @@ from mypy.join import join_simple, join_types from mypy.meet import meet_types, narrow_declared_type from mypy.nodes import ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2, CONTRAVARIANT, COVARIANT, INVARIANT -from mypy.sametypes import is_same_type from mypy.state import state -from mypy.subtypes import is_more_precise, is_proper_subtype, is_subtype +from mypy.subtypes import is_more_precise, is_proper_subtype, is_same_type, is_subtype from mypy.test.helpers import Suite, assert_equal, assert_type, skip from mypy.test.typefixture import InterfaceTypeFixture, TypeFixture from mypy.typeops import false_only, make_simplified_union, true_only diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 3454e2cce948..33ab1a8602be 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -6464,6 +6464,6 @@ eggs = lambda: 'eggs' reveal_type(func(eggs)) # N: Revealed type is "def (builtins.str) -> builtins.str" spam: Callable[..., str] = lambda x, y: 'baz' -reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> Any" +reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> builtins.str" [builtins fixtures/paramspec.pyi] diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index ae9b8e6d84a0..18192b38dc6c 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -533,10 +533,10 @@ def expects_int_first(x: Callable[Concatenate[int, P], int]) -> None: ... # N: This may be because "one" has arguments named: "x" def one(x: str) -> int: ... -@expects_int_first # E: Argument 1 to "expects_int_first" has incompatible type "Callable[[NamedArg(int, 'x')], int]"; expected "Callable[[int], int]" +@expects_int_first # E: Argument 1 to "expects_int_first" has incompatible type "Callable[[NamedArg(int, 'x')], int]"; expected "Callable[[int, NamedArg(int, 'x')], int]" def two(*, x: int) -> int: ... -@expects_int_first # E: Argument 1 to "expects_int_first" has incompatible type "Callable[[KwArg(int)], int]"; expected "Callable[[int], int]" +@expects_int_first # E: Argument 1 to "expects_int_first" has incompatible type "Callable[[KwArg(int)], int]"; expected "Callable[[int, KwArg(int)], int]" def three(**kwargs: int) -> int: ... @expects_int_first # Accepted