from __future__ import annotations from mypy.expandtype import expand_type_by_instance from mypy.nodes import TypeInfo from mypy.types import AnyType, Instance, TupleType, TypeOfAny, has_type_vars def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Instance: """Produce a supertype of `instance` that is an Instance of `superclass`, mapping type arguments up the chain of bases. If `superclass` is not a nominal superclass of `instance.type`, then all type arguments are mapped to 'Any'. """ if instance.type == superclass: # Fast path: `instance` already belongs to `superclass`. return instance if superclass.fullname == "builtins.tuple" and instance.type.tuple_type: if has_type_vars(instance.type.tuple_type): # We special case mapping generic tuple types to tuple base, because for # such tuples fallback can't be calculated before applying type arguments. alias = instance.type.special_alias assert alias is not None if not alias._is_recursive: # Unfortunately we can't support this for generic recursive tuples. # If we skip this special casing we will fall back to tuple[Any, ...]. tuple_type = expand_type_by_instance(instance.type.tuple_type, instance) if isinstance(tuple_type, TupleType): # Make the import here to avoid cyclic imports. import mypy.typeops return mypy.typeops.tuple_fallback(tuple_type) elif isinstance(tuple_type, Instance): # This can happen after normalizing variadic tuples. return tuple_type if not superclass.type_vars: # Fast path: `superclass` has no type variables to map to. return Instance(superclass, []) return map_instance_to_supertypes(instance, superclass)[0] def map_instance_to_supertypes(instance: Instance, supertype: TypeInfo) -> list[Instance]: # FIX: Currently we should only have one supertype per interface, so no # need to return an array result: list[Instance] = [] for path in class_derivation_paths(instance.type, supertype): types = [instance] for sup in path: a: list[Instance] = [] for t in types: a.extend(map_instance_to_direct_supertypes(t, sup)) types = a result.extend(types) if result: return result else: # Nothing. Presumably due to an error. Construct a dummy using Any. any_type = AnyType(TypeOfAny.from_error) return [Instance(supertype, [any_type] * len(supertype.type_vars))] def class_derivation_paths(typ: TypeInfo, supertype: TypeInfo) -> list[list[TypeInfo]]: """Return an array of non-empty paths of direct base classes from type to supertype. Return [] if no such path could be found. InterfaceImplementationPaths(A, B) == [[B]] if A inherits B InterfaceImplementationPaths(A, C) == [[B, C]] if A inherits B and B inherits C """ # FIX: Currently we might only ever have a single path, so this could be # simplified result: list[list[TypeInfo]] = [] for base in typ.bases: btype = base.type if btype == supertype: result.append([btype]) else: # Try constructing a longer path via the base class. for path in class_derivation_paths(btype, supertype): result.append([btype] + path) return result def map_instance_to_direct_supertypes(instance: Instance, supertype: TypeInfo) -> list[Instance]: # FIX: There should only be one supertypes, always. typ = instance.type result: list[Instance] = [] for b in typ.bases: if b.type == supertype: t = expand_type_by_instance(b, instance) assert isinstance(t, Instance) result.append(t) if result: return result else: # Relationship with the supertype not specified explicitly. Use dynamic # type arguments implicitly. any_type = AnyType(TypeOfAny.unannotated) return [Instance(supertype, [any_type] * len(supertype.type_vars))]