def is_overlapping_types(left: Type, right: Type, ignore_promotions: bool = False, prohibit_none_typevar_overlap: bool = False) -> bool: """Can a value of type 'left' also be of type 'right' or vice-versa? If 'ignore_promotions' is True, we ignore promotions while checking for overlaps. If 'prohibit_none_typevar_overlap' is True, we disallow None from overlapping with TypeVars (in both strict-optional and non-strict-optional mode). """ left = get_proper_type(left) right = get_proper_type(right) def _is_overlapping_types(left: Type, right: Type) -> bool: '''Encode the kind of overlapping check to perform. This function mostly exists so we don't have to repeat keyword arguments everywhere.''' return is_overlapping_types( left, right, ignore_promotions=ignore_promotions, prohibit_none_typevar_overlap=prohibit_none_typevar_overlap) # We should never encounter this type. if isinstance(left, PartialType) or isinstance(right, PartialType): assert False, "Unexpectedly encountered partial type" # We should also never encounter these types, but it's possible a few # have snuck through due to unrelated bugs. For now, we handle these # in the same way we handle 'Any'. # # TODO: Replace these with an 'assert False' once we are more confident. illegal_types = (UnboundType, ErasedType, DeletedType) if isinstance(left, illegal_types) or isinstance(right, illegal_types): return True # 'Any' may or may not be overlapping with the other type if isinstance(left, AnyType) or isinstance(right, AnyType): return True # When running under non-strict optional mode, simplify away types of # the form 'Union[A, B, C, None]' into just 'Union[A, B, C]'. if not state.strict_optional: if isinstance(left, UnionType): left = UnionType.make_union(left.relevant_items()) if isinstance(right, UnionType): right = UnionType.make_union(right.relevant_items()) # We check for complete overlaps next as a general-purpose failsafe. # If this check fails, we start checking to see if there exists a # *partial* overlap between types. # # These checks will also handle the NoneType and UninhabitedType cases for us. if (is_proper_subtype(left, right, ignore_promotions=ignore_promotions) or is_proper_subtype( right, left, ignore_promotions=ignore_promotions)): return True # See the docstring for 'get_possible_variants' for more info on what the # following lines are doing. left_possible = get_possible_variants(left) right_possible = get_possible_variants(right) # We start by checking multi-variant types like Unions first. We also perform # the same logic if either type happens to be a TypeVar. # # Handling the TypeVars now lets us simulate having them bind to the corresponding # type -- if we deferred these checks, the "return-early" logic of the other # checks will prevent us from detecting certain overlaps. # # If both types are singleton variants (and are not TypeVars), we've hit the base case: # we skip these checks to avoid infinitely recursing. def is_none_typevar_overlap(t1: ProperType, t2: ProperType) -> bool: return isinstance(t1, NoneType) and isinstance(t2, TypeVarType) if prohibit_none_typevar_overlap: if is_none_typevar_overlap(left, right) or is_none_typevar_overlap( right, left): return False if (len(left_possible) > 1 or len(right_possible) > 1 or isinstance(left, TypeVarType) or isinstance(right, TypeVarType)): for l in left_possible: for r in right_possible: if _is_overlapping_types(l, r): return True return False # Now that we've finished handling TypeVars, we're free to end early # if one one of the types is None and we're running in strict-optional mode. # (None only overlaps with None in strict-optional mode). # # We must perform this check after the TypeVar checks because # a TypeVar could be bound to None, for example. if state.strict_optional and isinstance(left, NoneType) != isinstance( right, NoneType): return False # Next, we handle single-variant types that may be inherently partially overlapping: # # - TypedDicts # - Tuples # # If we cannot identify a partial overlap and end early, we degrade these two types # into their 'Instance' fallbacks. if isinstance(left, TypedDictType) and isinstance(right, TypedDictType): return are_typed_dicts_overlapping(left, right, ignore_promotions=ignore_promotions) elif typed_dict_mapping_pair(left, right): # Overlaps between TypedDicts and Mappings require dedicated logic. return typed_dict_mapping_overlap(left, right, overlapping=_is_overlapping_types) elif isinstance(left, TypedDictType): left = left.fallback elif isinstance(right, TypedDictType): right = right.fallback if is_tuple(left) and is_tuple(right): return are_tuples_overlapping(left, right, ignore_promotions=ignore_promotions) elif isinstance(left, TupleType): left = tuple_fallback(left) elif isinstance(right, TupleType): right = tuple_fallback(right) # Next, we handle single-variant types that cannot be inherently partially overlapping, # but do require custom logic to inspect. # # As before, we degrade into 'Instance' whenever possible. if isinstance(left, TypeType) and isinstance(right, TypeType): return _is_overlapping_types(left.item, right.item) def _type_object_overlap(left: ProperType, right: ProperType) -> bool: """Special cases for type object types overlaps.""" # TODO: these checks are a bit in gray area, adjust if they cause problems. # 1. Type[C] vs Callable[..., C], where the latter is class object. if isinstance(left, TypeType) and isinstance( right, CallableType) and right.is_type_obj(): return _is_overlapping_types(left.item, right.ret_type) # 2. Type[C] vs Meta, where Meta is a metaclass for C. if isinstance(left, TypeType) and isinstance(right, Instance): if isinstance(left.item, Instance): left_meta = left.item.type.metaclass_type if left_meta is not None: return _is_overlapping_types(left_meta, right) # builtins.type (default metaclass) overlaps with all metaclasses return right.type.has_base('builtins.type') elif isinstance(left.item, AnyType): return right.type.has_base('builtins.type') # 3. Callable[..., C] vs Meta is considered below, when we switch to fallbacks. return False if isinstance(left, TypeType) or isinstance(right, TypeType): return _type_object_overlap(left, right) or _type_object_overlap( right, left) if isinstance(left, CallableType) and isinstance(right, CallableType): return is_callable_compatible(left, right, is_compat=_is_overlapping_types, ignore_pos_arg_names=True, allow_partial_overlap=True) elif isinstance(left, CallableType): left = left.fallback elif isinstance(right, CallableType): right = right.fallback if isinstance(left, LiteralType) and isinstance(right, LiteralType): if left.value == right.value: # If values are the same, we still need to check if fallbacks are overlapping, # this is done below. left = left.fallback right = right.fallback else: return False elif isinstance(left, LiteralType): left = left.fallback elif isinstance(right, LiteralType): right = right.fallback # Finally, we handle the case where left and right are instances. if isinstance(left, Instance) and isinstance(right, Instance): # First we need to handle promotions and structural compatibility for instances # that came as fallbacks, so simply call is_subtype() to avoid code duplication. if (is_subtype(left, right, ignore_promotions=ignore_promotions) or is_subtype(right, left, ignore_promotions=ignore_promotions)): return True # Two unrelated types cannot be partially overlapping: they're disjoint. if left.type.has_base(right.type.fullname()): left = map_instance_to_supertype(left, right.type) elif right.type.has_base(left.type.fullname()): right = map_instance_to_supertype(right, left.type) else: return False if len(left.args) == len(right.args): # Note: we don't really care about variance here, since the overlapping check # is symmetric and since we want to return 'True' even for partial overlaps. # # For example, suppose we have two types Wrapper[Parent] and Wrapper[Child]. # It doesn't matter whether Wrapper is covariant or contravariant since # either way, one of the two types will overlap with the other. # # Similarly, if Wrapper was invariant, the two types could still be partially # overlapping -- what if Wrapper[Parent] happened to contain only instances of # specifically Child? # # Or, to use a more concrete example, List[Union[A, B]] and List[Union[B, C]] # would be considered partially overlapping since it's possible for both lists # to contain only instances of B at runtime. for left_arg, right_arg in zip(left.args, right.args): if _is_overlapping_types(left_arg, right_arg): return True return False # We ought to have handled every case by now: we conclude the # two types are not overlapping, either completely or partially. # # Note: it's unclear however, whether returning False is the right thing # to do when inferring reachability -- see https://github.com/python/mypy/issues/5529 assert type(left) != type(right) return False
def is_overlapping_types(left: Type, right: Type, ignore_promotions: bool = False, prohibit_none_typevar_overlap: bool = False) -> bool: """Can a value of type 'left' also be of type 'right' or vice-versa? If 'ignore_promotions' is True, we ignore promotions while checking for overlaps. If 'prohibit_none_typevar_overlap' is True, we disallow None from overlapping with TypeVars (in both strict-optional and non-strict-optional mode). """ def _is_overlapping_types(left: Type, right: Type) -> bool: '''Encode the kind of overlapping check to perform. This function mostly exists so we don't have to repeat keyword arguments everywhere.''' return is_overlapping_types( left, right, ignore_promotions=ignore_promotions, prohibit_none_typevar_overlap=prohibit_none_typevar_overlap) # We should never encounter this type. if isinstance(left, PartialType) or isinstance(right, PartialType): assert False, "Unexpectedly encountered partial type" # We should also never encounter these types, but it's possible a few # have snuck through due to unrelated bugs. For now, we handle these # in the same way we handle 'Any'. # # TODO: Replace these with an 'assert False' once we are more confident. illegal_types = (UnboundType, ErasedType, DeletedType) if isinstance(left, illegal_types) or isinstance(right, illegal_types): return True # 'Any' may or may not be overlapping with the other type if isinstance(left, AnyType) or isinstance(right, AnyType): return True # When running under non-strict optional mode, simplify away types of # the form 'Union[A, B, C, None]' into just 'Union[A, B, C]'. if not state.strict_optional: if isinstance(left, UnionType): left = UnionType.make_union(left.relevant_items()) if isinstance(right, UnionType): right = UnionType.make_union(right.relevant_items()) # We check for complete overlaps next as a general-purpose failsafe. # If this check fails, we start checking to see if there exists a # *partial* overlap between types. # # These checks will also handle the NoneTyp and UninhabitedType cases for us. if (is_proper_subtype(left, right, ignore_promotions=ignore_promotions) or is_proper_subtype(right, left, ignore_promotions=ignore_promotions)): return True # See the docstring for 'get_possible_variants' for more info on what the # following lines are doing. left_possible = get_possible_variants(left) right_possible = get_possible_variants(right) # We start by checking multi-variant types like Unions first. We also perform # the same logic if either type happens to be a TypeVar. # # Handling the TypeVars now lets us simulate having them bind to the corresponding # type -- if we deferred these checks, the "return-early" logic of the other # checks will prevent us from detecting certain overlaps. # # If both types are singleton variants (and are not TypeVars), we've hit the base case: # we skip these checks to avoid infinitely recursing. def is_none_typevar_overlap(t1: Type, t2: Type) -> bool: return isinstance(t1, NoneTyp) and isinstance(t2, TypeVarType) if prohibit_none_typevar_overlap: if is_none_typevar_overlap(left, right) or is_none_typevar_overlap(right, left): return False if (len(left_possible) > 1 or len(right_possible) > 1 or isinstance(left, TypeVarType) or isinstance(right, TypeVarType)): for l in left_possible: for r in right_possible: if _is_overlapping_types(l, r): return True return False # Now that we've finished handling TypeVars, we're free to end early # if one one of the types is None and we're running in strict-optional mode. # (None only overlaps with None in strict-optional mode). # # We must perform this check after the TypeVar checks because # a TypeVar could be bound to None, for example. if state.strict_optional and isinstance(left, NoneTyp) != isinstance(right, NoneTyp): return False # Next, we handle single-variant types that may be inherently partially overlapping: # # - TypedDicts # - Tuples # # If we cannot identify a partial overlap and end early, we degrade these two types # into their 'Instance' fallbacks. if isinstance(left, TypedDictType) and isinstance(right, TypedDictType): return are_typed_dicts_overlapping(left, right, ignore_promotions=ignore_promotions) elif isinstance(left, TypedDictType): left = left.fallback elif isinstance(right, TypedDictType): right = right.fallback if is_tuple(left) and is_tuple(right): return are_tuples_overlapping(left, right, ignore_promotions=ignore_promotions) elif isinstance(left, TupleType): left = left.fallback elif isinstance(right, TupleType): right = right.fallback # Next, we handle single-variant types that cannot be inherently partially overlapping, # but do require custom logic to inspect. # # As before, we degrade into 'Instance' whenever possible. if isinstance(left, TypeType) and isinstance(right, TypeType): # TODO: Can Callable[[...], T] and Type[T] be partially overlapping? return _is_overlapping_types(left.item, right.item) if isinstance(left, CallableType) and isinstance(right, CallableType): return is_callable_compatible(left, right, is_compat=_is_overlapping_types, ignore_pos_arg_names=True, allow_partial_overlap=True) elif isinstance(left, CallableType): left = left.fallback elif isinstance(right, CallableType): right = right.fallback if isinstance(left, LiteralType) and isinstance(right, LiteralType): return left == right elif isinstance(left, LiteralType): left = left.fallback elif isinstance(right, LiteralType): right = right.fallback # Finally, we handle the case where left and right are instances. if isinstance(left, Instance) and isinstance(right, Instance): if left.type.is_protocol and is_protocol_implementation(right, left): return True if right.type.is_protocol and is_protocol_implementation(left, right): return True # Two unrelated types cannot be partially overlapping: they're disjoint. # We don't need to handle promotions because they've already been handled # by the calls to `is_subtype(...)` up above (and promotable types never # have any generic arguments we need to recurse on). if left.type.has_base(right.type.fullname()): left = map_instance_to_supertype(left, right.type) elif right.type.has_base(left.type.fullname()): right = map_instance_to_supertype(right, left.type) else: return False if len(left.args) == len(right.args): # Note: we don't really care about variance here, since the overlapping check # is symmetric and since we want to return 'True' even for partial overlaps. # # For example, suppose we have two types Wrapper[Parent] and Wrapper[Child]. # It doesn't matter whether Wrapper is covariant or contravariant since # either way, one of the two types will overlap with the other. # # Similarly, if Wrapper was invariant, the two types could still be partially # overlapping -- what if Wrapper[Parent] happened to contain only instances of # specifically Child? # # Or, to use a more concrete example, List[Union[A, B]] and List[Union[B, C]] # would be considered partially overlapping since it's possible for both lists # to contain only instances of B at runtime. for left_arg, right_arg in zip(left.args, right.args): if _is_overlapping_types(left_arg, right_arg): return True return False # We ought to have handled every case by now: we conclude the # two types are not overlapping, either completely or partially. # # Note: it's unclear however, whether returning False is the right thing # to do when inferring reachability -- see https://github.com/python/mypy/issues/5529 assert type(left) != type(right) return False