From 1234e5f7cea9399e0e84d800fb434ce17c981d60 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 18:04:06 +0100 Subject: [PATCH 01/11] Consider variance when joining generic types --- mypy/join.py | 37 ++++++++++++++++++++---------- mypy/main.py | 4 ++++ mypy/meet.py | 16 ++++++------- test-data/unit/check-generics.test | 35 ++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 4cd0da163e130..b1d03e6a7d3d5 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,29 @@ 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): + if 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) + args.append(new_type) + 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 + args.append(new_type) + elif type_var.variance == INVARIANT: + if ta != sa: + return object_from_instance(t) + 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/main.py b/mypy/main.py index be2fab1bb0f9c..740e942633275 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -505,6 +505,10 @@ def add_invertible_flag(flag: str, platform_group.add_argument( '--always-false', metavar='NAME', action='append', default=[], help="Additional variable to be considered False (may be repeated)") + platform_group.add_argument( + '--extra-builtins', metavar='SOURCE_FILE', action='append', default=[], + help="Load additional variables from a file and treat them as builtins (may be repeated)" + ) disallow_any_group = parser.add_argument_group( title='Disallow dynamic typing', diff --git a/mypy/meet.py b/mypy/meet.py index 2e01116e6d739..55502018625af 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -1,9 +1,6 @@ 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 +12,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. @@ -513,7 +511,7 @@ def visit_instance(self, t: Instance) -> ProperType: else: return NoneType() 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 +529,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 +549,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 +568,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 +604,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/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 654f33c590c2d..7d8bec000a2b7 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2430,3 +2430,38 @@ 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, List + +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] From 0938c03129a60a0f280530fcba68eae5e3434294 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 18:34:41 +0100 Subject: [PATCH 02/11] Fix most errors in selfcheck --- mypy/join.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/join.py b/mypy/join.py index b1d03e6a7d3d5..ca63db1d6a37b 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -380,6 +380,7 @@ def join_instances(t: Instance, s: Instance) -> ProperType: elif type_var.variance == INVARIANT: if ta != sa: return object_from_instance(t) + args.append(ta) return Instance(t.type, args) elif t.type.bases and is_subtype_ignoring_tvars(t, s): return join_instances_via_supertype(t, s) From 720b24e48402869e046f961a1fb91bc0036867fa Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 18:36:37 +0100 Subject: [PATCH 03/11] Remove accidentally added, unreleated change --- mypy/main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 740e942633275..be2fab1bb0f9c 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -505,10 +505,6 @@ def add_invertible_flag(flag: str, platform_group.add_argument( '--always-false', metavar='NAME', action='append', default=[], help="Additional variable to be considered False (may be repeated)") - platform_group.add_argument( - '--extra-builtins', metavar='SOURCE_FILE', action='append', default=[], - help="Load additional variables from a file and treat them as builtins (may be repeated)" - ) disallow_any_group = parser.add_argument_group( title='Disallow dynamic typing', From 299ddd6d6c1b2082b16411eebce91af55fdc6f77 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 19:19:24 +0100 Subject: [PATCH 04/11] Fix more tests --- test-data/unit/check-inference.test | 4 ++-- test-data/unit/check-overloading.test | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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-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] From 8ddd1e20f49f9517a6fdeb0ca313e56f0cffe98c Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 20:01:23 +0100 Subject: [PATCH 05/11] Fixed more tests --- mypy/test/testtypes.py | 8 ++++---- mypy/test/typefixture.py | 1 + test-data/unit/pythoneval.test | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index c65bfc7b94184..212ca05b5c3c8 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -599,7 +599,7 @@ def test_other_mixed_types(self) -> None: 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.gd, self.fx.go) self.assert_join(self.fx.ga, self.fx.g2a, self.fx.o) self.assert_join(self.fx.ga, self.fx.nonet, self.fx.ga) @@ -612,17 +612,17 @@ def test_simple_generics(self) -> None: 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.had, self.fx.haa, self.fx.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.gsab, self.fx.gd, self.fx.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.gsab, self.fx.gs2d, self.fx.go) def test_generic_types_and_any(self) -> None: self.assert_join(self.fx.gdyn, self.fx.ga, self.fx.gdyn) diff --git a/mypy/test/typefixture.py b/mypy/test/typefixture.py index b29f7164c9119..9c4888dfe2e2e 100644 --- a/mypy/test/typefixture.py +++ b/mypy/test/typefixture.py @@ -145,6 +145,7 @@ 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.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/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] From 31525d09cee2f4834c6e46c3ace0fb6de02cacf0 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 20:39:18 +0100 Subject: [PATCH 06/11] Fixed more tests --- test-data/unit/check-classes.test | 2 +- test-data/unit/check-newsemanal.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 25ed014899584..434d2e0aac51e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5442,7 +5442,7 @@ class B(Generic[T]): ... y: A z: A[int] x = [y, z] -reveal_type(x) # N: Revealed type is 'builtins.list[__main__.B*[Any]]' +reveal_type(x) # N: Revealed type is 'builtins.list[builtins.object*]' A = B [builtins fixtures/list.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] From 340da49812a59ea087e2897b9bb7863f1ffad28a Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 20:46:52 +0100 Subject: [PATCH 07/11] Change fixed test to another solution --- test-data/unit/check-classes.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 434d2e0aac51e..cc73ef6f5ca3e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5436,13 +5436,13 @@ 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 z: A[int] x = [y, z] -reveal_type(x) # N: Revealed type is 'builtins.list[builtins.object*]' +reveal_type(x) # N: Revealed type is 'builtins.list[__main__.B*[Any]]' A = B [builtins fixtures/list.pyi] From 9af224b39ece64600e0a4592bf14b2e92a4dd2a0 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Fri, 29 Jan 2021 21:25:52 +0100 Subject: [PATCH 08/11] Fix mypy selfcheck failing --- mypy/join.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/join.py b/mypy/join.py index ca63db1d6a37b..937f24f674105 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -378,7 +378,7 @@ def join_instances(t: Instance, s: Instance) -> ProperType: # No need to check subtype, as ta and sa already have to be subtypes of upper_bound args.append(new_type) elif type_var.variance == INVARIANT: - if ta != sa: + if not is_equivalent(ta, sa): return object_from_instance(t) args.append(ta) return Instance(t.type, args) From 06eb549d39b9092c7c56d48f78e80eb7f2c25776 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Tue, 2 Feb 2021 01:48:23 +0100 Subject: [PATCH 09/11] Added tests for variance when joining generics --- mypy/join.py | 11 ++++++---- mypy/test/testtypes.py | 47 +++++++++++++++++++++++++++------------- mypy/test/typefixture.py | 1 + 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 937f24f674105..66de41bc74ea6 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -364,23 +364,26 @@ def join_instances(t: Instance, s: Instance) -> ProperType: # 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): - if type_var.variance == COVARIANT: + if isinstance(ta, AnyType): + new_type = AnyType(TypeOfAny.from_another_any, ta) + elif isinstance(sa, AnyType): + new_type = AnyType(TypeOfAny.from_another_any, sa) + 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) - args.append(new_type) 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 - args.append(new_type) elif type_var.variance == INVARIANT: + new_type = join_types(ta, sa) if not is_equivalent(ta, sa): return object_from_instance(t) - args.append(ta) + 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) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 212ca05b5c3c8..06ed8d5b0094e 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.go) - 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.hao) + 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.go) + 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.go) + 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, diff --git a/mypy/test/typefixture.py b/mypy/test/typefixture.py index 9c4888dfe2e2e..5abbb90db38ed 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, [NoneType()]) # G[None] self.g2a = Instance(self.g2i, [self.a]) # G2[A] From 9e220ee92a561ee33115c2487a8f5475c70b4405 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Tue, 2 Feb 2021 01:59:05 +0100 Subject: [PATCH 10/11] Fix linting errors in new code --- mypy/join.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 66de41bc74ea6..68a74d0cdebaf 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -364,10 +364,13 @@ def join_instances(t: Instance, s: Instance) -> ProperType: # 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): - if isinstance(ta, AnyType): - new_type = AnyType(TypeOfAny.from_another_any, ta) - elif isinstance(sa, AnyType): - new_type = AnyType(TypeOfAny.from_another_any, sa) + 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: From 648a4f73b11491a16470c2b02fb53e092b33fdf7 Mon Sep 17 00:00:00 2001 From: Adrian Freund Date: Tue, 2 Feb 2021 22:49:45 +0100 Subject: [PATCH 11/11] Consider variance when meeting generic types --- mypy/meet.py | 56 ++++++++++++++++++++---------- mypy/test/testtypes.py | 39 +++++++++++++++------ mypy/test/typefixture.py | 3 +- test-data/unit/check-generics.test | 23 +++++++++++- 4 files changed, 90 insertions(+), 31 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 55502018625af..4218b55f1700e 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -1,3 +1,4 @@ +from mypy.nodes import COVARIANT, CONTRAVARIANT, INVARIANT from mypy.ordered_dict import OrderedDict from typing import List, Optional, Tuple, Callable @@ -482,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 @@ -506,10 +529,7 @@ 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 = join.unpack_callback_protocol(t) if call: diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 06ed8d5b0094e..89edbf63708f4 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -818,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: @@ -895,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) @@ -908,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 5abbb90db38ed..da6a6bbbdd8a9 100644 --- a/mypy/test/typefixture.py +++ b/mypy/test/typefixture.py @@ -129,7 +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, [NoneType()]) # G[None] + self.gn = Instance(self.gi, [self.nonet]) # G[None] self.g2a = Instance(self.g2i, [self.a]) # G2[A] @@ -147,6 +147,7 @@ def make_type_var(name: str, id: int, values: List[Type], upper_bound: Type, 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-generics.test b/test-data/unit/check-generics.test index 7d8bec000a2b7..d3146dcef77d4 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2452,7 +2452,7 @@ reveal_type([a_c, b_c]) # N: Revealed type is 'builtins.list[__main__.Container* [builtins fixtures/list.pyi] [case testGenericJoinContravariant] -from typing import Generic, TypeVar, List +from typing import Generic, TypeVar T = TypeVar("T", contravariant=True) @@ -2465,3 +2465,24 @@ 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]