blob: cae904469fedf8bd480e2bf27a05e351ce0fddac [file] [log] [blame]
from __future__ import annotations
from mypy.expandtype import expand_type
from mypy.nodes import TypeInfo
from mypy.types import AnyType, Instance, TupleType, Type, TypeOfAny, TypeVarId, 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, ...].
env = instance_to_type_environment(instance)
tuple_type = expand_type(instance.type.tuple_type, env)
if isinstance(tuple_type, TupleType):
# Make the import here to avoid cyclic imports.
import mypy.typeops
return mypy.typeops.tuple_fallback(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:
env = instance_to_type_environment(instance)
t = expand_type(b, env)
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))]
def instance_to_type_environment(instance: Instance) -> dict[TypeVarId, Type]:
"""Given an Instance, produce the resulting type environment for type
variables bound by the Instance's class definition.
An Instance is a type application of a class (a TypeInfo) to its
required number of type arguments. So this environment consists
of the class's type variables mapped to the Instance's actual
arguments. The type variables are mapped by their `id`.
"""
return {binder.id: arg for binder, arg in zip(instance.type.defn.type_vars, instance.args)}