Skip to content
44 changes: 32 additions & 12 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
72 changes: 45 additions & 27 deletions mypy/meet.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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.

Expand Down Expand Up @@ -484,36 +483,55 @@ 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
elif is_subtype(self.s, t):
# 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():
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
86 changes: 60 additions & 26 deletions mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -597,35 +599,50 @@ 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)

for t in [self.fx.a, self.fx.o, self.fx.t, self.tuple(),
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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -878,24 +897,39 @@ 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)

for t in [self.fx.a, self.fx.t, self.tuple(),
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)
Expand Down
3 changes: 3 additions & 0 deletions mypy/test/typefixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading