diff --git a/mypy/join.py b/mypy/join.py index 4cd0da163e130..68a74d0cdebaf 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -14,9 +14,10 @@ is_subtype, is_equivalent, is_subtype_ignoring_tvars, is_proper_subtype, is_protocol_implementation, find_member ) -from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT +from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT, INVARIANT, COVARIANT, CONTRAVARIANT import mypy.typeops from mypy import state +from mypy import meet def join_simple(declaration: Optional[Type], s: Type, t: Type) -> ProperType: @@ -357,17 +358,36 @@ def join_instances(t: Instance, s: Instance) -> ProperType: if t.type == s.type: # Simplest case: join two types with the same base type (but # potentially different arguments). - if is_subtype(t, s) or is_subtype(s, t): - # Compatible; combine type arguments. - args = [] # type: List[Type] - # N.B: We use zip instead of indexing because the lengths might have - # mismatches during daemon reprocessing. - for ta, sa in zip(t.args, s.args): - args.append(join_types(ta, sa)) - return Instance(t.type, args) - else: - # Incompatible; return trivial result object. - return object_from_instance(t) + + # Combine type arguments. + args = [] # type: List[Type] + # N.B: We use zip instead of indexing because the lengths might have + # mismatches during daemon reprocessing. + for ta, sa, type_var in zip(t.args, s.args, t.type.defn.type_vars): + ta_proper = get_proper_type(ta) + sa_proper = get_proper_type(sa) + new_type = NoneType() # type: Type + if isinstance(ta_proper, AnyType): + new_type = AnyType(TypeOfAny.from_another_any, ta_proper) + elif isinstance(sa_proper, AnyType): + new_type = AnyType(TypeOfAny.from_another_any, sa_proper) + elif type_var.variance == COVARIANT: + new_type = join_types(ta, sa) + if len(type_var.values) != 0 and new_type not in type_var.values: + return object_from_instance(t) + if not is_subtype(new_type, type_var.upper_bound): + return object_from_instance(t) + elif type_var.variance == CONTRAVARIANT: + new_type = meet.meet_types(ta, sa) + if len(type_var.values) != 0 and new_type not in type_var.values: + return object_from_instance(t) + # No need to check subtype, as ta and sa already have to be subtypes of upper_bound + elif type_var.variance == INVARIANT: + new_type = join_types(ta, sa) + if not is_equivalent(ta, sa): + return object_from_instance(t) + args.append(new_type) + return Instance(t.type, args) elif t.type.bases and is_subtype_ignoring_tvars(t, s): return join_instances_via_supertype(t, s) else: diff --git a/mypy/meet.py b/mypy/meet.py index 2e01116e6d739..4218b55f1700e 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -1,9 +1,7 @@ +from mypy.nodes import COVARIANT, CONTRAVARIANT, INVARIANT from mypy.ordered_dict import OrderedDict from typing import List, Optional, Tuple, Callable -from mypy.join import ( - is_similar_callables, combine_similar_callables, join_type_list, unpack_callback_protocol -) from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, @@ -15,6 +13,7 @@ from mypy.maptype import map_instance_to_supertype from mypy.typeops import tuple_fallback, make_simplified_union, is_recursive_pair from mypy import state +from mypy import join # TODO Describe this module. @@ -484,23 +483,45 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: return self.default(self.s) def visit_instance(self, t: Instance) -> ProperType: + def get_uninhabited_type() -> ProperType: + if state.strict_optional: + return UninhabitedType() + else: + return NoneType() + if isinstance(self.s, Instance): si = self.s if t.type == si.type: - if is_subtype(t, self.s) or is_subtype(self.s, t): - # Combine type arguments. We could have used join below - # equivalently. - args = [] # type: List[Type] - # N.B: We use zip instead of indexing because the lengths might have - # mismatches during daemon reprocessing. - for ta, sia in zip(t.args, si.args): - args.append(self.meet(ta, sia)) - return Instance(t.type, args) - else: - if state.strict_optional: - return UninhabitedType() - else: - return NoneType() + # Combine type arguments. + args = [] # type: List[Type] + # N.B: We use zip instead of indexing because the lengths might have + # mismatches during daemon reprocessing. + for ta, sa, type_var in zip(t.args, si.args, t.type.defn.type_vars): + ta_proper = get_proper_type(ta) + sa_proper = get_proper_type(sa) + new_type = NoneType() # type: Type + if isinstance(ta_proper, AnyType): + new_type = sa + elif isinstance(sa_proper, AnyType): + new_type = ta + elif type_var.variance == COVARIANT: + new_type = meet_types(ta, sa) + if len(type_var.values) != 0 and new_type not in type_var.values: + return get_uninhabited_type() + # No need to check subtype, as ta and sa already have to be + # subtypes of upper_bound + elif type_var.variance == CONTRAVARIANT: + new_type = join.join_types(ta, sa) + if len(type_var.values) != 0 and new_type not in type_var.values: + return NoneType() + if not is_subtype(new_type, type_var.upper_bound): + return get_uninhabited_type() + elif type_var.variance == INVARIANT: + new_type = ta + if not is_equivalent(ta, sa): + return get_uninhabited_type() + args.append(new_type) + return Instance(t.type, args) else: if is_subtype(t, self.s): return t @@ -508,12 +529,9 @@ def visit_instance(self, t: Instance) -> ProperType: # See also above comment. return self.s else: - if state.strict_optional: - return UninhabitedType() - else: - return NoneType() + return get_uninhabited_type() elif isinstance(self.s, FunctionLike) and t.type.is_protocol: - call = unpack_callback_protocol(t) + call = join.unpack_callback_protocol(t) if call: return meet_types(call, self.s) elif isinstance(self.s, FunctionLike) and self.s.is_type_obj() and t.type.is_metaclass(): @@ -531,9 +549,9 @@ def visit_instance(self, t: Instance) -> ProperType: return self.default(self.s) def visit_callable_type(self, t: CallableType) -> ProperType: - if isinstance(self.s, CallableType) and is_similar_callables(t, self.s): + if isinstance(self.s, CallableType) and join.is_similar_callables(t, self.s): if is_equivalent(t, self.s): - return combine_similar_callables(t, self.s) + return join.combine_similar_callables(t, self.s) result = meet_similar_callables(t, self.s) # We set the from_type_type flag to suppress error when a collection of # concrete class objects gets inferred as their common abstract superclass. @@ -551,7 +569,7 @@ def visit_callable_type(self, t: CallableType) -> ProperType: return TypeType.make_normalized(res) return self.default(self.s) elif isinstance(self.s, Instance) and self.s.type.is_protocol: - call = unpack_callback_protocol(self.s) + call = join.unpack_callback_protocol(self.s) if call: return meet_types(t, call) return self.default(self.s) @@ -570,7 +588,7 @@ def visit_overloaded(self, t: Overloaded) -> ProperType: else: return meet_types(t.fallback, s.fallback) elif isinstance(self.s, Instance) and self.s.type.is_protocol: - call = unpack_callback_protocol(self.s) + call = join.unpack_callback_protocol(self.s) if call: return meet_types(t, call) return meet_types(t.fallback, s) @@ -606,7 +624,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> ProperType: assert t_item_type is not None item_list.append((item_name, t_item_type)) items = OrderedDict(item_list) - mapping_value_type = join_type_list(list(items.values())) + mapping_value_type = join.join_type_list(list(items.values())) fallback = self.s.create_anonymous_fallback(value_type=mapping_value_type) required_keys = t.required_keys | self.s.required_keys return TypedDictType(items, required_keys, fallback) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index c65bfc7b94184..89edbf63708f4 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -477,7 +477,9 @@ def callable(self, vars: List[str], *a: Type) -> CallableType: class JoinSuite(Suite): def setUp(self) -> None: - self.fx = TypeFixture() + self.fx = TypeFixture(INVARIANT) + self.fx_co = TypeFixture(COVARIANT) + self.fx_contra = TypeFixture(CONTRAVARIANT) def test_trivial_cases(self) -> None: for simple in self.fx.a, self.fx.o, self.fx.b: @@ -597,11 +599,6 @@ def test_other_mixed_types(self) -> None: self.assert_join(t1, t2, self.fx.o) def test_simple_generics(self) -> None: - self.assert_join(self.fx.ga, self.fx.ga, self.fx.ga) - self.assert_join(self.fx.ga, self.fx.gb, self.fx.ga) - self.assert_join(self.fx.ga, self.fx.gd, self.fx.o) - self.assert_join(self.fx.ga, self.fx.g2a, self.fx.o) - self.assert_join(self.fx.ga, self.fx.nonet, self.fx.ga) self.assert_join(self.fx.ga, self.fx.anyt, self.fx.anyt) @@ -609,23 +606,43 @@ def test_simple_generics(self) -> None: self.callable(self.fx.a, self.fx.b)]: self.assert_join(t, self.fx.ga, self.fx.o) + def test_generics_invariant(self) -> None: + self.assert_join(self.fx.ga, self.fx.ga, self.fx.ga) + self.assert_join(self.fx.ga, self.fx.gb, self.fx.o) + self.assert_join(self.fx.ga, self.fx.gd, self.fx.o) + self.assert_join(self.fx.ga, self.fx.g2a, self.fx.o) + + def test_generics_covariant(self) -> None: + self.assert_join(self.fx_co.ga, self.fx_co.ga, self.fx_co.ga) + self.assert_join(self.fx_co.ga, self.fx_co.gb, self.fx_co.ga) + self.assert_join(self.fx_co.ga, self.fx_co.gd, self.fx_co.go) + self.assert_join(self.fx_co.ga, self.fx_co.g2a, self.fx_co.o) + + def test_generics_contravariant(self) -> None: + self.assert_join(self.fx_contra.ga, self.fx_contra.ga, self.fx_contra.ga) + self.assert_join(self.fx_contra.ga, self.fx_contra.gb, self.fx_contra.gb) + self.assert_join(self.fx_contra.ga, self.fx_contra.gd, self.fx_contra.gn) + self.assert_join(self.fx_contra.ga, self.fx_contra.g2a, self.fx_contra.o) + def test_generics_with_multiple_args(self) -> None: - self.assert_join(self.fx.hab, self.fx.hab, self.fx.hab) - self.assert_join(self.fx.hab, self.fx.hbb, self.fx.hab) - self.assert_join(self.fx.had, self.fx.haa, self.fx.o) + self.assert_join(self.fx_co.hab, self.fx_co.hab, self.fx_co.hab) + self.assert_join(self.fx_co.hab, self.fx_co.hbb, self.fx_co.hab) + self.assert_join(self.fx_co.had, self.fx_co.haa, self.fx_co.hao) def test_generics_with_inheritance(self) -> None: - self.assert_join(self.fx.gsab, self.fx.gb, self.fx.gb) - self.assert_join(self.fx.gsba, self.fx.gb, self.fx.ga) - self.assert_join(self.fx.gsab, self.fx.gd, self.fx.o) + self.assert_join(self.fx_co.gsab, self.fx_co.gb, self.fx_co.gb) + self.assert_join(self.fx_co.gsba, self.fx_co.gb, self.fx_co.ga) + self.assert_join(self.fx_co.gsab, self.fx_co.gd, self.fx_co.go) def test_generics_with_inheritance_and_shared_supertype(self) -> None: - self.assert_join(self.fx.gsba, self.fx.gs2a, self.fx.ga) - self.assert_join(self.fx.gsab, self.fx.gs2a, self.fx.ga) - self.assert_join(self.fx.gsab, self.fx.gs2d, self.fx.o) + self.assert_join(self.fx_co.gsba, self.fx_co.gs2a, self.fx_co.ga) + self.assert_join(self.fx_co.gsab, self.fx_co.gs2a, self.fx_co.ga) + self.assert_join(self.fx_co.gsab, self.fx_co.gs2d, self.fx_co.go) def test_generic_types_and_any(self) -> None: self.assert_join(self.fx.gdyn, self.fx.ga, self.fx.gdyn) + self.assert_join(self.fx_co.gdyn, self.fx_co.ga, self.fx_co.gdyn) + self.assert_join(self.fx_contra.gdyn, self.fx_contra.ga, self.fx_contra.gdyn) def test_callables_with_any(self) -> None: self.assert_join(self.callable(self.fx.a, self.fx.a, self.fx.anyt, @@ -801,7 +818,9 @@ def type_callable(self, *a: Type) -> CallableType: class MeetSuite(Suite): def setUp(self) -> None: - self.fx = TypeFixture() + self.fx = TypeFixture(INVARIANT) + self.fx_co = TypeFixture(COVARIANT) + self.fx_contra = TypeFixture(CONTRAVARIANT) def test_trivial_cases(self) -> None: for simple in self.fx.a, self.fx.o, self.fx.b: @@ -878,12 +897,6 @@ def test_dynamic_type(self) -> None: self.assert_meet(t, self.fx.anyt, t) def test_simple_generics(self) -> None: - self.assert_meet(self.fx.ga, self.fx.ga, self.fx.ga) - self.assert_meet(self.fx.ga, self.fx.o, self.fx.ga) - self.assert_meet(self.fx.ga, self.fx.gb, self.fx.gb) - self.assert_meet(self.fx.ga, self.fx.gd, self.fx.nonet) - self.assert_meet(self.fx.ga, self.fx.g2a, self.fx.nonet) - self.assert_meet(self.fx.ga, self.fx.nonet, self.fx.nonet) self.assert_meet(self.fx.ga, self.fx.anyt, self.fx.ga) @@ -891,11 +904,32 @@ def test_simple_generics(self) -> None: self.callable(self.fx.a, self.fx.b)]: self.assert_meet(t, self.fx.ga, self.fx.nonet) + def test_generics_invariant(self) -> None: + self.assert_meet(self.fx.ga, self.fx.ga, self.fx.ga) + self.assert_meet(self.fx.ga, self.fx.o, self.fx.ga) + self.assert_meet(self.fx.ga, self.fx.gb, self.fx.nonet) + self.assert_meet(self.fx.ga, self.fx.gd, self.fx.nonet) + self.assert_meet(self.fx.ga, self.fx.g2a, self.fx.nonet) + + def test_generics_covariant(self) -> None: + self.assert_meet(self.fx_co.ga, self.fx_co.ga, self.fx_co.ga) + self.assert_meet(self.fx_co.ga, self.fx_co.o, self.fx_co.ga) + self.assert_meet(self.fx_co.ga, self.fx_co.gb, self.fx_co.gb) + self.assert_meet(self.fx_co.ga, self.fx_co.gd, self.fx_co.gn) + self.assert_meet(self.fx_co.ga, self.fx_co.g2a, self.fx_co.nonet) + + def test_generics_contravariant(self) -> None: + self.assert_meet(self.fx_contra.ga, self.fx_contra.ga, self.fx_contra.ga) + self.assert_meet(self.fx_contra.ga, self.fx_contra.o, self.fx_contra.ga) + self.assert_meet(self.fx_contra.ga, self.fx_contra.gb, self.fx_contra.ga) + self.assert_meet(self.fx_contra.ga, self.fx_contra.gd, self.fx_contra.go) + self.assert_meet(self.fx_contra.ga, self.fx_contra.g2a, self.fx_contra.nonet) + def test_generics_with_multiple_args(self) -> None: - self.assert_meet(self.fx.hab, self.fx.hab, self.fx.hab) - self.assert_meet(self.fx.hab, self.fx.haa, self.fx.hab) - self.assert_meet(self.fx.hab, self.fx.had, self.fx.nonet) - self.assert_meet(self.fx.hab, self.fx.hbb, self.fx.hbb) + self.assert_meet(self.fx_co.hab, self.fx_co.hab, self.fx_co.hab) + self.assert_meet(self.fx_co.hab, self.fx_co.haa, self.fx_co.hab) + self.assert_meet(self.fx_co.hab, self.fx_co.had, self.fx_co.han) + self.assert_meet(self.fx_co.hab, self.fx_co.hbb, self.fx_co.hbb) def test_generics_with_inheritance(self) -> None: self.assert_meet(self.fx.gsab, self.fx.gb, self.fx.gsab) diff --git a/mypy/test/typefixture.py b/mypy/test/typefixture.py index b29f7164c9119..da6a6bbbdd8a9 100644 --- a/mypy/test/typefixture.py +++ b/mypy/test/typefixture.py @@ -129,6 +129,7 @@ def make_type_var(name: str, id: int, values: List[Type], upper_bound: Type, self.gtf2 = Instance(self.gi, [self.tf2]) # G[T`-2] self.gs = Instance(self.gi, [self.s]) # G[S] self.gdyn = Instance(self.gi, [self.anyt]) # G[Any] + self.gn = Instance(self.gi, [self.nonet]) # G[None] self.g2a = Instance(self.g2i, [self.a]) # G2[A] @@ -145,6 +146,8 @@ def make_type_var(name: str, id: int, values: List[Type], upper_bound: Type, self.hbb = Instance(self.hi, [self.b, self.b]) # H[B, B] self.hts = Instance(self.hi, [self.t, self.s]) # H[T, S] self.had = Instance(self.hi, [self.a, self.d]) # H[A, D] + self.hao = Instance(self.hi, [self.a, self.o]) # H[A, object] + self.han = Instance(self.hi, [self.a, self.nonet]) # H[A, None] self.lsta = Instance(self.std_listi, [self.a]) # List[A] self.lstb = Instance(self.std_listi, [self.b]) # List[B] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 25ed014899584..cc73ef6f5ca3e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5436,7 +5436,7 @@ main:8:6: error: Type argument "builtins.int" of "G" must be a subtype of "built [case testExtremeForwardReferencing] from typing import TypeVar, Generic -T = TypeVar('T') +T = TypeVar('T', covariant=True) class B(Generic[T]): ... y: A diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 654f33c590c2d..d3146dcef77d4 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2430,3 +2430,59 @@ class C: ... def test() -> Optional[S]: reveal_type(A().gen(C())) # N: Revealed type is '__main__.C*' return None + +[case testGenericJoinCovariant] +from typing import Generic, TypeVar, List + +T = TypeVar("T", covariant=True) + +class Container(Generic[T]): ... +class Base: ... +class A(Base): ... +class B(Base): ... + +a: A +b: B + +a_c: Container[A] +b_c: Container[B] + +reveal_type([a, b]) # N: Revealed type is 'builtins.list[__main__.Base*]' +reveal_type([a_c, b_c]) # N: Revealed type is 'builtins.list[__main__.Container*[__main__.Base]]' +[builtins fixtures/list.pyi] + +[case testGenericJoinContravariant] +from typing import Generic, TypeVar + +T = TypeVar("T", contravariant=True) + +class Container(Generic[T]): ... +class A: ... +class B(A): ... + +a_c: Container[A] +b_c: Container[B] + +reveal_type([a_c, b_c]) # N: Revealed type is 'builtins.list[__main__.Container*[__main__.B]]' +[builtins fixtures/list.pyi] + +[case testGenericJoinNestedMeet] +from typing import Generic, TypeVar + +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + +class Container_co(Generic[T_co]): ... +class Container_contra(Generic[T_contra]): ... +class A: ... +class B(A): ... + +a_c: Container_co[Container_co[A]] +b_c: Container_co[Container_co[B]] + +a_c2: Container_contra[Container_co[A]] +b_c2: Container_contra[Container_co[B]] + +reveal_type([a_c, b_c]) # N: Revealed type is 'builtins.list[__main__.Container_co*[__main__.Container_co[__main__.A]]]' +reveal_type([a_c2, b_c2]) # N: Revealed type is 'builtins.list[__main__.Container_contra*[__main__.Container_co[__main__.B]]]' +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 750e00294ca35..a62f28b596207 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -791,10 +791,10 @@ AnyStr = TypeVar('AnyStr', bytes, str) def f(x: Union[AnyStr, int], *a: AnyStr) -> None: pass f('foo') f('foo', 'bar') -f('foo', b'bar') # E: Value of type variable "AnyStr" of "f" cannot be "object" +f('foo', b'bar') # E: Value of type variable "AnyStr" of "f" cannot be "Sequence[object]" f(1) f(1, 'foo') -f(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "f" cannot be "object" +f(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "f" cannot be "Sequence[object]" [builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 476345c801dac..0154487da597d 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1970,7 +1970,7 @@ class G(Generic[T]): pass t: Tuple[G[B], G[C]] # E: Type argument "__main__.B" of "G" must be a subtype of "__main__.A" \ # E: Type argument "__main__.C" of "G" must be a subtype of "__main__.A" -reveal_type(t.__iter__) # N: Revealed type is 'def () -> typing.Iterator[__main__.G*[__main__.B]]' +reveal_type(t.__iter__) # N: Revealed type is 'def () -> typing.Iterator[builtins.object*]' [builtins fixtures/tuple.pyi] [case testNewAnalyzerClassKeywordsForward] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 05db459d78b1b..e5c38f1bd8185 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1315,10 +1315,10 @@ def g(x: int, *a: AnyStr) -> None: pass g('foo') g('foo', 'bar') -g('foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "object" +g('foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "Sequence[object]" g(1) g(1, 'foo') -g(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "object" +g(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "Sequence[object]" [builtins fixtures/primitives.pyi] [case testOverloadOverlapWithTypeVarsWithValuesOrdering] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 6876744cbbb52..425892d8e4b09 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1005,7 +1005,7 @@ re.subn(bpat, b'', b'')[0] + b'' re.subn(bre, lambda m: b'', b'')[0] + b'' re.subn(bpat, lambda m: b'', b'')[0] + b'' [out] -_program.py:7: error: Value of type variable "AnyStr" of "search" cannot be "object" +_program.py:7: error: Value of type variable "AnyStr" of "search" cannot be "Sequence[object]" _program.py:9: error: Cannot infer type argument 1 of "search" [case testReModuleString] @@ -1029,7 +1029,7 @@ re.subn(spat, '', '')[0] + '' re.subn(sre, lambda m: '', '')[0] + '' re.subn(spat, lambda m: '', '')[0] + '' [out] -_program.py:7: error: Value of type variable "AnyStr" of "search" cannot be "object" +_program.py:7: error: Value of type variable "AnyStr" of "search" cannot be "Sequence[object]" _program.py:9: error: Cannot infer type argument 1 of "search" [case testListSetitemTuple]