| """Calculation of the least upper bound types (joins).""" |
| |
| from __future__ import annotations |
| |
| from typing import overload |
| |
| import mypy.typeops |
| from mypy.maptype import map_instance_to_supertype |
| from mypy.nodes import CONTRAVARIANT, COVARIANT, INVARIANT |
| from mypy.state import state |
| from mypy.subtypes import ( |
| SubtypeContext, |
| find_member, |
| is_equivalent, |
| is_proper_subtype, |
| is_protocol_implementation, |
| is_subtype, |
| ) |
| from mypy.types import ( |
| AnyType, |
| CallableType, |
| DeletedType, |
| ErasedType, |
| FunctionLike, |
| Instance, |
| LiteralType, |
| NoneType, |
| Overloaded, |
| Parameters, |
| ParamSpecType, |
| PartialType, |
| ProperType, |
| TupleType, |
| Type, |
| TypeAliasType, |
| TypedDictType, |
| TypeOfAny, |
| TypeType, |
| TypeVarTupleType, |
| TypeVarType, |
| TypeVisitor, |
| UnboundType, |
| UninhabitedType, |
| UnionType, |
| UnpackType, |
| get_proper_type, |
| get_proper_types, |
| ) |
| |
| |
| class InstanceJoiner: |
| def __init__(self) -> None: |
| self.seen_instances: list[tuple[Instance, Instance]] = [] |
| |
| def join_instances(self, t: Instance, s: Instance) -> ProperType: |
| if (t, s) in self.seen_instances or (s, t) in self.seen_instances: |
| return object_from_instance(t) |
| |
| self.seen_instances.append((t, s)) |
| |
| # Calculate the join of two instance types |
| if t.type == s.type: |
| # Simplest case: join two types with the same base type (but |
| # potentially different arguments). |
| |
| # Combine type arguments. |
| args: 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: Type | None = None |
| 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 isinstance(type_var, TypeVarType): |
| if type_var.variance == COVARIANT: |
| new_type = join_types(ta, sa, self) |
| if len(type_var.values) != 0 and new_type not in type_var.values: |
| self.seen_instances.pop() |
| return object_from_instance(t) |
| if not is_subtype(new_type, type_var.upper_bound): |
| self.seen_instances.pop() |
| return object_from_instance(t) |
| # TODO: contravariant case should use meet but pass seen instances as |
| # an argument to keep track of recursive checks. |
| elif type_var.variance in (INVARIANT, CONTRAVARIANT): |
| if not is_equivalent(ta, sa): |
| self.seen_instances.pop() |
| return object_from_instance(t) |
| # If the types are different but equivalent, then an Any is involved |
| # so using a join in the contravariant case is also OK. |
| new_type = join_types(ta, sa, self) |
| else: |
| # ParamSpec type variables behave the same, independent of variance |
| if not is_equivalent(ta, sa): |
| return get_proper_type(type_var.upper_bound) |
| new_type = join_types(ta, sa, self) |
| assert new_type is not None |
| args.append(new_type) |
| result: ProperType = Instance(t.type, args) |
| elif t.type.bases and is_proper_subtype( |
| t, s, subtype_context=SubtypeContext(ignore_type_params=True) |
| ): |
| result = self.join_instances_via_supertype(t, s) |
| else: |
| # Now t is not a subtype of s, and t != s. Now s could be a subtype |
| # of t; alternatively, we need to find a common supertype. This works |
| # in of the both cases. |
| result = self.join_instances_via_supertype(s, t) |
| |
| self.seen_instances.pop() |
| return result |
| |
| def join_instances_via_supertype(self, t: Instance, s: Instance) -> ProperType: |
| # Give preference to joins via duck typing relationship, so that |
| # join(int, float) == float, for example. |
| for p in t.type._promote: |
| if is_subtype(p, s): |
| return join_types(p, s, self) |
| for p in s.type._promote: |
| if is_subtype(p, t): |
| return join_types(t, p, self) |
| |
| # Compute the "best" supertype of t when joined with s. |
| # The definition of "best" may evolve; for now it is the one with |
| # the longest MRO. Ties are broken by using the earlier base. |
| best: ProperType | None = None |
| for base in t.type.bases: |
| mapped = map_instance_to_supertype(t, base.type) |
| res = self.join_instances(mapped, s) |
| if best is None or is_better(res, best): |
| best = res |
| assert best is not None |
| for promote in t.type._promote: |
| if isinstance(promote, Instance): |
| res = self.join_instances(promote, s) |
| if is_better(res, best): |
| best = res |
| return best |
| |
| |
| def join_simple(declaration: Type | None, s: Type, t: Type) -> ProperType: |
| """Return a simple least upper bound given the declared type. |
| |
| This function should be only used by binder, and should not recurse. |
| For all other uses, use `join_types()`. |
| """ |
| declaration = get_proper_type(declaration) |
| s = get_proper_type(s) |
| t = get_proper_type(t) |
| |
| if (s.can_be_true, s.can_be_false) != (t.can_be_true, t.can_be_false): |
| # if types are restricted in different ways, use the more general versions |
| s = mypy.typeops.true_or_false(s) |
| t = mypy.typeops.true_or_false(t) |
| |
| if isinstance(s, AnyType): |
| return s |
| |
| if isinstance(s, ErasedType): |
| return t |
| |
| if is_proper_subtype(s, t, ignore_promotions=True): |
| return t |
| |
| if is_proper_subtype(t, s, ignore_promotions=True): |
| return s |
| |
| if isinstance(declaration, UnionType): |
| return mypy.typeops.make_simplified_union([s, t]) |
| |
| if isinstance(s, NoneType) and not isinstance(t, NoneType): |
| s, t = t, s |
| |
| if isinstance(s, UninhabitedType) and not isinstance(t, UninhabitedType): |
| s, t = t, s |
| |
| # Meets/joins require callable type normalization. |
| s, t = normalize_callables(s, t) |
| |
| if isinstance(s, UnionType) and not isinstance(t, UnionType): |
| s, t = t, s |
| |
| value = t.accept(TypeJoinVisitor(s)) |
| if declaration is None or is_subtype(value, declaration): |
| return value |
| |
| return declaration |
| |
| |
| def trivial_join(s: Type, t: Type) -> Type: |
| """Return one of types (expanded) if it is a supertype of other, otherwise top type.""" |
| if is_subtype(s, t): |
| return t |
| elif is_subtype(t, s): |
| return s |
| else: |
| return object_or_any_from_type(get_proper_type(t)) |
| |
| |
| @overload |
| def join_types( |
| s: ProperType, t: ProperType, instance_joiner: InstanceJoiner | None = None |
| ) -> ProperType: |
| ... |
| |
| |
| @overload |
| def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) -> Type: |
| ... |
| |
| |
| def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) -> Type: |
| """Return the least upper bound of s and t. |
| |
| For example, the join of 'int' and 'object' is 'object'. |
| """ |
| if mypy.typeops.is_recursive_pair(s, t): |
| # This case can trigger an infinite recursion, general support for this will be |
| # tricky so we use a trivial join (like for protocols). |
| return trivial_join(s, t) |
| s = get_proper_type(s) |
| t = get_proper_type(t) |
| |
| if (s.can_be_true, s.can_be_false) != (t.can_be_true, t.can_be_false): |
| # if types are restricted in different ways, use the more general versions |
| s = mypy.typeops.true_or_false(s) |
| t = mypy.typeops.true_or_false(t) |
| |
| if isinstance(s, UnionType) and not isinstance(t, UnionType): |
| s, t = t, s |
| |
| if isinstance(s, AnyType): |
| return s |
| |
| if isinstance(s, ErasedType): |
| return t |
| |
| if isinstance(s, NoneType) and not isinstance(t, NoneType): |
| s, t = t, s |
| |
| if isinstance(s, UninhabitedType) and not isinstance(t, UninhabitedType): |
| s, t = t, s |
| |
| # Meets/joins require callable type normalization. |
| s, t = normalize_callables(s, t) |
| |
| # Use a visitor to handle non-trivial cases. |
| return t.accept(TypeJoinVisitor(s, instance_joiner)) |
| |
| |
| class TypeJoinVisitor(TypeVisitor[ProperType]): |
| """Implementation of the least upper bound algorithm. |
| |
| Attributes: |
| s: The other (left) type operand. |
| """ |
| |
| def __init__(self, s: ProperType, instance_joiner: InstanceJoiner | None = None) -> None: |
| self.s = s |
| self.instance_joiner = instance_joiner |
| |
| def visit_unbound_type(self, t: UnboundType) -> ProperType: |
| return AnyType(TypeOfAny.special_form) |
| |
| def visit_union_type(self, t: UnionType) -> ProperType: |
| if is_proper_subtype(self.s, t): |
| return t |
| else: |
| return mypy.typeops.make_simplified_union([self.s, t]) |
| |
| def visit_any(self, t: AnyType) -> ProperType: |
| return t |
| |
| def visit_none_type(self, t: NoneType) -> ProperType: |
| if state.strict_optional: |
| if isinstance(self.s, (NoneType, UninhabitedType)): |
| return t |
| elif isinstance(self.s, UnboundType): |
| return AnyType(TypeOfAny.special_form) |
| else: |
| return mypy.typeops.make_simplified_union([self.s, t]) |
| else: |
| return self.s |
| |
| def visit_uninhabited_type(self, t: UninhabitedType) -> ProperType: |
| return self.s |
| |
| def visit_deleted_type(self, t: DeletedType) -> ProperType: |
| return self.s |
| |
| def visit_erased_type(self, t: ErasedType) -> ProperType: |
| return self.s |
| |
| def visit_type_var(self, t: TypeVarType) -> ProperType: |
| if isinstance(self.s, TypeVarType) and self.s.id == t.id: |
| return self.s |
| else: |
| return self.default(self.s) |
| |
| def visit_param_spec(self, t: ParamSpecType) -> ProperType: |
| if self.s == t: |
| return t |
| return self.default(self.s) |
| |
| def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType: |
| if self.s == t: |
| return t |
| return self.default(self.s) |
| |
| def visit_unpack_type(self, t: UnpackType) -> UnpackType: |
| raise NotImplementedError |
| |
| def visit_parameters(self, t: Parameters) -> ProperType: |
| if self.s == t: |
| return t |
| else: |
| return self.default(self.s) |
| |
| def visit_instance(self, t: Instance) -> ProperType: |
| if isinstance(self.s, Instance): |
| if self.instance_joiner is None: |
| self.instance_joiner = InstanceJoiner() |
| nominal = self.instance_joiner.join_instances(t, self.s) |
| structural: Instance | None = None |
| if t.type.is_protocol and is_protocol_implementation(self.s, t): |
| structural = t |
| elif self.s.type.is_protocol and is_protocol_implementation(t, self.s): |
| structural = self.s |
| # Structural join is preferred in the case where we have found both |
| # structural and nominal and they have same MRO length (see two comments |
| # in join_instances_via_supertype). Otherwise, just return the nominal join. |
| if not structural or is_better(nominal, structural): |
| return nominal |
| return structural |
| elif isinstance(self.s, FunctionLike): |
| if t.type.is_protocol: |
| call = unpack_callback_protocol(t) |
| if call: |
| return join_types(call, self.s) |
| return join_types(t, self.s.fallback) |
| elif isinstance(self.s, TypeType): |
| return join_types(t, self.s) |
| elif isinstance(self.s, TypedDictType): |
| return join_types(t, self.s) |
| elif isinstance(self.s, TupleType): |
| return join_types(t, self.s) |
| elif isinstance(self.s, LiteralType): |
| return join_types(t, self.s) |
| else: |
| 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 is_equivalent(t, self.s): |
| return combine_similar_callables(t, self.s) |
| result = join_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. |
| if not ( |
| (t.is_type_obj() and t.type_object().is_abstract) |
| or (self.s.is_type_obj() and self.s.type_object().is_abstract) |
| ): |
| result.from_type_type = True |
| if any( |
| isinstance(tp, (NoneType, UninhabitedType)) |
| for tp in get_proper_types(result.arg_types) |
| ): |
| # We don't want to return unusable Callable, attempt fallback instead. |
| return join_types(t.fallback, self.s) |
| return result |
| elif isinstance(self.s, Overloaded): |
| # Switch the order of arguments to that we'll get to visit_overloaded. |
| return join_types(t, self.s) |
| elif isinstance(self.s, Instance) and self.s.type.is_protocol: |
| call = unpack_callback_protocol(self.s) |
| if call: |
| return join_types(t, call) |
| return join_types(t.fallback, self.s) |
| |
| def visit_overloaded(self, t: Overloaded) -> ProperType: |
| # This is more complex than most other cases. Here are some |
| # examples that illustrate how this works. |
| # |
| # First let's define a concise notation: |
| # - Cn are callable types (for n in 1, 2, ...) |
| # - Ov(C1, C2, ...) is an overloaded type with items C1, C2, ... |
| # - Callable[[T, ...], S] is written as [T, ...] -> S. |
| # |
| # We want some basic properties to hold (assume Cn are all |
| # unrelated via Any-similarity): |
| # |
| # join(Ov(C1, C2), C1) == C1 |
| # join(Ov(C1, C2), Ov(C1, C2)) == Ov(C1, C2) |
| # join(Ov(C1, C2), Ov(C1, C3)) == C1 |
| # join(Ov(C2, C2), C3) == join of fallback types |
| # |
| # The presence of Any types makes things more interesting. The join is the |
| # most general type we can get with respect to Any: |
| # |
| # join(Ov([int] -> int, [str] -> str), [Any] -> str) == Any -> str |
| # |
| # We could use a simplification step that removes redundancies, but that's not |
| # implemented right now. Consider this example, where we get a redundancy: |
| # |
| # join(Ov([int, Any] -> Any, [str, Any] -> Any), [Any, int] -> Any) == |
| # Ov([Any, int] -> Any, [Any, int] -> Any) |
| # |
| # TODO: Consider more cases of callable subtyping. |
| result: list[CallableType] = [] |
| s = self.s |
| if isinstance(s, FunctionLike): |
| # The interesting case where both types are function types. |
| for t_item in t.items: |
| for s_item in s.items: |
| if is_similar_callables(t_item, s_item): |
| if is_equivalent(t_item, s_item): |
| result.append(combine_similar_callables(t_item, s_item)) |
| elif is_subtype(t_item, s_item): |
| result.append(s_item) |
| if result: |
| # TODO: Simplify redundancies from the result. |
| if len(result) == 1: |
| return result[0] |
| else: |
| return Overloaded(result) |
| return join_types(t.fallback, s.fallback) |
| elif isinstance(s, Instance) and s.type.is_protocol: |
| call = unpack_callback_protocol(s) |
| if call: |
| return join_types(t, call) |
| return join_types(t.fallback, s) |
| |
| def visit_tuple_type(self, t: TupleType) -> ProperType: |
| # When given two fixed-length tuples: |
| # * If they have the same length, join their subtypes item-wise: |
| # Tuple[int, bool] + Tuple[bool, bool] becomes Tuple[int, bool] |
| # * If lengths do not match, return a variadic tuple: |
| # Tuple[bool, int] + Tuple[bool] becomes Tuple[int, ...] |
| # |
| # Otherwise, `t` is a fixed-length tuple but `self.s` is NOT: |
| # * Joining with a variadic tuple returns variadic tuple: |
| # Tuple[int, bool] + Tuple[bool, ...] becomes Tuple[int, ...] |
| # * Joining with any Sequence also returns a Sequence: |
| # Tuple[int, bool] + List[bool] becomes Sequence[int] |
| if isinstance(self.s, TupleType) and self.s.length() == t.length(): |
| if self.instance_joiner is None: |
| self.instance_joiner = InstanceJoiner() |
| fallback = self.instance_joiner.join_instances( |
| mypy.typeops.tuple_fallback(self.s), mypy.typeops.tuple_fallback(t) |
| ) |
| assert isinstance(fallback, Instance) |
| if self.s.length() == t.length(): |
| items: list[Type] = [] |
| for i in range(t.length()): |
| items.append(join_types(t.items[i], self.s.items[i])) |
| return TupleType(items, fallback) |
| else: |
| return fallback |
| else: |
| return join_types(self.s, mypy.typeops.tuple_fallback(t)) |
| |
| def visit_typeddict_type(self, t: TypedDictType) -> ProperType: |
| if isinstance(self.s, TypedDictType): |
| items = { |
| item_name: s_item_type |
| for (item_name, s_item_type, t_item_type) in self.s.zip(t) |
| if ( |
| is_equivalent(s_item_type, t_item_type) |
| and (item_name in t.required_keys) == (item_name in self.s.required_keys) |
| ) |
| } |
| fallback = self.s.create_anonymous_fallback() |
| # We need to filter by items.keys() since some required keys present in both t and |
| # self.s might be missing from the join if the types are incompatible. |
| required_keys = set(items.keys()) & t.required_keys & self.s.required_keys |
| return TypedDictType(items, required_keys, fallback) |
| elif isinstance(self.s, Instance): |
| return join_types(self.s, t.fallback) |
| else: |
| return self.default(self.s) |
| |
| def visit_literal_type(self, t: LiteralType) -> ProperType: |
| if isinstance(self.s, LiteralType): |
| if t == self.s: |
| return t |
| if self.s.fallback.type.is_enum and t.fallback.type.is_enum: |
| return mypy.typeops.make_simplified_union([self.s, t]) |
| return join_types(self.s.fallback, t.fallback) |
| else: |
| return join_types(self.s, t.fallback) |
| |
| def visit_partial_type(self, t: PartialType) -> ProperType: |
| # We only have partial information so we can't decide the join result. We should |
| # never get here. |
| assert False, "Internal error" |
| |
| def visit_type_type(self, t: TypeType) -> ProperType: |
| if isinstance(self.s, TypeType): |
| return TypeType.make_normalized(join_types(t.item, self.s.item), line=t.line) |
| elif isinstance(self.s, Instance) and self.s.type.fullname == "builtins.type": |
| return self.s |
| else: |
| return self.default(self.s) |
| |
| def visit_type_alias_type(self, t: TypeAliasType) -> ProperType: |
| assert False, f"This should be never called, got {t}" |
| |
| def default(self, typ: Type) -> ProperType: |
| typ = get_proper_type(typ) |
| if isinstance(typ, Instance): |
| return object_from_instance(typ) |
| elif isinstance(typ, UnboundType): |
| return AnyType(TypeOfAny.special_form) |
| elif isinstance(typ, TupleType): |
| return self.default(mypy.typeops.tuple_fallback(typ)) |
| elif isinstance(typ, TypedDictType): |
| return self.default(typ.fallback) |
| elif isinstance(typ, FunctionLike): |
| return self.default(typ.fallback) |
| elif isinstance(typ, TypeVarType): |
| return self.default(typ.upper_bound) |
| elif isinstance(typ, ParamSpecType): |
| return self.default(typ.upper_bound) |
| else: |
| return AnyType(TypeOfAny.special_form) |
| |
| |
| def is_better(t: Type, s: Type) -> bool: |
| # Given two possible results from join_instances_via_supertype(), |
| # indicate whether t is the better one. |
| t = get_proper_type(t) |
| s = get_proper_type(s) |
| |
| if isinstance(t, Instance): |
| if not isinstance(s, Instance): |
| return True |
| # Use len(mro) as a proxy for the better choice. |
| if len(t.type.mro) > len(s.type.mro): |
| return True |
| return False |
| |
| |
| def normalize_callables(s: ProperType, t: ProperType) -> tuple[ProperType, ProperType]: |
| if isinstance(s, (CallableType, Overloaded)): |
| s = s.with_unpacked_kwargs() |
| if isinstance(t, (CallableType, Overloaded)): |
| t = t.with_unpacked_kwargs() |
| return s, t |
| |
| |
| def is_similar_callables(t: CallableType, s: CallableType) -> bool: |
| """Return True if t and s have identical numbers of |
| arguments, default arguments and varargs. |
| """ |
| return ( |
| len(t.arg_types) == len(s.arg_types) |
| and t.min_args == s.min_args |
| and t.is_var_arg == s.is_var_arg |
| ) |
| |
| |
| def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: |
| from mypy.meet import meet_types |
| |
| arg_types: list[Type] = [] |
| for i in range(len(t.arg_types)): |
| arg_types.append(meet_types(t.arg_types[i], s.arg_types[i])) |
| # TODO in combine_similar_callables also applies here (names and kinds; user metaclasses) |
| # The fallback type can be either 'function', 'type', or some user-provided metaclass. |
| # The result should always use 'function' as a fallback if either operands are using it. |
| if t.fallback.type.fullname == "builtins.function": |
| fallback = t.fallback |
| else: |
| fallback = s.fallback |
| return t.copy_modified( |
| arg_types=arg_types, |
| arg_names=combine_arg_names(t, s), |
| ret_type=join_types(t.ret_type, s.ret_type), |
| fallback=fallback, |
| name=None, |
| ) |
| |
| |
| def combine_similar_callables(t: CallableType, s: CallableType) -> CallableType: |
| arg_types: list[Type] = [] |
| for i in range(len(t.arg_types)): |
| arg_types.append(join_types(t.arg_types[i], s.arg_types[i])) |
| # TODO kinds and argument names |
| # TODO what should happen if one fallback is 'type' and the other is a user-provided metaclass? |
| # The fallback type can be either 'function', 'type', or some user-provided metaclass. |
| # The result should always use 'function' as a fallback if either operands are using it. |
| if t.fallback.type.fullname == "builtins.function": |
| fallback = t.fallback |
| else: |
| fallback = s.fallback |
| return t.copy_modified( |
| arg_types=arg_types, |
| arg_names=combine_arg_names(t, s), |
| ret_type=join_types(t.ret_type, s.ret_type), |
| fallback=fallback, |
| name=None, |
| ) |
| |
| |
| def combine_arg_names(t: CallableType, s: CallableType) -> list[str | None]: |
| """Produces a list of argument names compatible with both callables. |
| |
| For example, suppose 't' and 's' have the following signatures: |
| |
| - t: (a: int, b: str, X: str) -> None |
| - s: (a: int, b: str, Y: str) -> None |
| |
| This function would return ["a", "b", None]. This information |
| is then used above to compute the join of t and s, which results |
| in a signature of (a: int, b: str, str) -> None. |
| |
| Note that the third argument's name is omitted and 't' and 's' |
| are both valid subtypes of this inferred signature. |
| |
| Precondition: is_similar_types(t, s) is true. |
| """ |
| num_args = len(t.arg_types) |
| new_names = [] |
| for i in range(num_args): |
| t_name = t.arg_names[i] |
| s_name = s.arg_names[i] |
| if t_name == s_name or t.arg_kinds[i].is_named() or s.arg_kinds[i].is_named(): |
| new_names.append(t_name) |
| else: |
| new_names.append(None) |
| return new_names |
| |
| |
| def object_from_instance(instance: Instance) -> Instance: |
| """Construct the type 'builtins.object' from an instance type.""" |
| # Use the fact that 'object' is always the last class in the mro. |
| res = Instance(instance.type.mro[-1], []) |
| return res |
| |
| |
| def object_or_any_from_type(typ: ProperType) -> ProperType: |
| # Similar to object_from_instance() but tries hard for all types. |
| # TODO: find a better way to get object, or make this more reliable. |
| if isinstance(typ, Instance): |
| return object_from_instance(typ) |
| elif isinstance(typ, (CallableType, TypedDictType, LiteralType)): |
| return object_from_instance(typ.fallback) |
| elif isinstance(typ, TupleType): |
| return object_from_instance(typ.partial_fallback) |
| elif isinstance(typ, TypeType): |
| return object_or_any_from_type(typ.item) |
| elif isinstance(typ, TypeVarType) and isinstance(typ.upper_bound, ProperType): |
| return object_or_any_from_type(typ.upper_bound) |
| elif isinstance(typ, UnionType): |
| for item in typ.items: |
| if isinstance(item, ProperType): |
| candidate = object_or_any_from_type(item) |
| if isinstance(candidate, Instance): |
| return candidate |
| return AnyType(TypeOfAny.implementation_artifact) |
| |
| |
| def join_type_list(types: list[Type]) -> Type: |
| if not types: |
| # This is a little arbitrary but reasonable. Any empty tuple should be compatible |
| # with all variable length tuples, and this makes it possible. |
| return UninhabitedType() |
| joined = types[0] |
| for t in types[1:]: |
| joined = join_types(joined, t) |
| return joined |
| |
| |
| def unpack_callback_protocol(t: Instance) -> ProperType | None: |
| assert t.type.is_protocol |
| if t.type.protocol_members == ["__call__"]: |
| return get_proper_type(find_member("__call__", t, t, is_operator=True)) |
| return None |