229 lines
10 KiB
Python
229 lines
10 KiB
Python
"""Utilities for mapping between actual and formal arguments (and their types)."""
|
|
|
|
from typing import TYPE_CHECKING, List, Optional, Sequence, Callable, Set
|
|
|
|
from mypy.maptype import map_instance_to_supertype
|
|
from mypy.types import (
|
|
Type, Instance, TupleType, AnyType, TypeOfAny, TypedDictType, ParamSpecType, get_proper_type
|
|
)
|
|
from mypy import nodes
|
|
|
|
if TYPE_CHECKING:
|
|
from mypy.infer import ArgumentInferContext
|
|
|
|
|
|
def map_actuals_to_formals(actual_kinds: List[nodes.ArgKind],
|
|
actual_names: Optional[Sequence[Optional[str]]],
|
|
formal_kinds: List[nodes.ArgKind],
|
|
formal_names: Sequence[Optional[str]],
|
|
actual_arg_type: Callable[[int],
|
|
Type]) -> List[List[int]]:
|
|
"""Calculate mapping between actual (caller) args and formals.
|
|
|
|
The result contains a list of caller argument indexes mapping to each
|
|
callee argument index, indexed by callee index.
|
|
|
|
The caller_arg_type argument should evaluate to the type of the actual
|
|
argument type with the given index.
|
|
"""
|
|
nformals = len(formal_kinds)
|
|
formal_to_actual: List[List[int]] = [[] for i in range(nformals)]
|
|
ambiguous_actual_kwargs: List[int] = []
|
|
fi = 0
|
|
for ai, actual_kind in enumerate(actual_kinds):
|
|
if actual_kind == nodes.ARG_POS:
|
|
if fi < nformals:
|
|
if not formal_kinds[fi].is_star():
|
|
formal_to_actual[fi].append(ai)
|
|
fi += 1
|
|
elif formal_kinds[fi] == nodes.ARG_STAR:
|
|
formal_to_actual[fi].append(ai)
|
|
elif actual_kind == nodes.ARG_STAR:
|
|
# We need to know the actual type to map varargs.
|
|
actualt = get_proper_type(actual_arg_type(ai))
|
|
if isinstance(actualt, TupleType):
|
|
# A tuple actual maps to a fixed number of formals.
|
|
for _ in range(len(actualt.items)):
|
|
if fi < nformals:
|
|
if formal_kinds[fi] != nodes.ARG_STAR2:
|
|
formal_to_actual[fi].append(ai)
|
|
else:
|
|
break
|
|
if formal_kinds[fi] != nodes.ARG_STAR:
|
|
fi += 1
|
|
else:
|
|
# Assume that it is an iterable (if it isn't, there will be
|
|
# an error later).
|
|
while fi < nformals:
|
|
if formal_kinds[fi].is_named(star=True):
|
|
break
|
|
else:
|
|
formal_to_actual[fi].append(ai)
|
|
if formal_kinds[fi] == nodes.ARG_STAR:
|
|
break
|
|
fi += 1
|
|
elif actual_kind.is_named():
|
|
assert actual_names is not None, "Internal error: named kinds without names given"
|
|
name = actual_names[ai]
|
|
if name in formal_names:
|
|
formal_to_actual[formal_names.index(name)].append(ai)
|
|
elif nodes.ARG_STAR2 in formal_kinds:
|
|
formal_to_actual[formal_kinds.index(nodes.ARG_STAR2)].append(ai)
|
|
else:
|
|
assert actual_kind == nodes.ARG_STAR2
|
|
actualt = get_proper_type(actual_arg_type(ai))
|
|
if isinstance(actualt, TypedDictType):
|
|
for name in actualt.items:
|
|
if name in formal_names:
|
|
formal_to_actual[formal_names.index(name)].append(ai)
|
|
elif nodes.ARG_STAR2 in formal_kinds:
|
|
formal_to_actual[formal_kinds.index(nodes.ARG_STAR2)].append(ai)
|
|
else:
|
|
# We don't exactly know which **kwargs are provided by the
|
|
# caller, so we'll defer until all the other unambiguous
|
|
# actuals have been processed
|
|
ambiguous_actual_kwargs.append(ai)
|
|
|
|
if ambiguous_actual_kwargs:
|
|
# Assume the ambiguous kwargs will fill the remaining arguments.
|
|
#
|
|
# TODO: If there are also tuple varargs, we might be missing some potential
|
|
# matches if the tuple was short enough to not match everything.
|
|
unmatched_formals = [fi for fi in range(nformals)
|
|
if (formal_names[fi]
|
|
and (not formal_to_actual[fi]
|
|
or actual_kinds[formal_to_actual[fi][0]] == nodes.ARG_STAR)
|
|
and formal_kinds[fi] != nodes.ARG_STAR)
|
|
or formal_kinds[fi] == nodes.ARG_STAR2]
|
|
for ai in ambiguous_actual_kwargs:
|
|
for fi in unmatched_formals:
|
|
formal_to_actual[fi].append(ai)
|
|
|
|
return formal_to_actual
|
|
|
|
|
|
def map_formals_to_actuals(actual_kinds: List[nodes.ArgKind],
|
|
actual_names: Optional[Sequence[Optional[str]]],
|
|
formal_kinds: List[nodes.ArgKind],
|
|
formal_names: List[Optional[str]],
|
|
actual_arg_type: Callable[[int],
|
|
Type]) -> List[List[int]]:
|
|
"""Calculate the reverse mapping of map_actuals_to_formals."""
|
|
formal_to_actual = map_actuals_to_formals(actual_kinds,
|
|
actual_names,
|
|
formal_kinds,
|
|
formal_names,
|
|
actual_arg_type)
|
|
# Now reverse the mapping.
|
|
actual_to_formal: List[List[int]] = [[] for _ in actual_kinds]
|
|
for formal, actuals in enumerate(formal_to_actual):
|
|
for actual in actuals:
|
|
actual_to_formal[actual].append(formal)
|
|
return actual_to_formal
|
|
|
|
|
|
class ArgTypeExpander:
|
|
"""Utility class for mapping actual argument types to formal arguments.
|
|
|
|
One of the main responsibilities is to expand caller tuple *args and TypedDict
|
|
**kwargs, and to keep track of which tuple/TypedDict items have already been
|
|
consumed.
|
|
|
|
Example:
|
|
|
|
def f(x: int, *args: str) -> None: ...
|
|
f(*(1, 'x', 1.1))
|
|
|
|
We'd call expand_actual_type three times:
|
|
|
|
1. The first call would provide 'int' as the actual type of 'x' (from '1').
|
|
2. The second call would provide 'str' as one of the actual types for '*args'.
|
|
2. The third call would provide 'float' as one of the actual types for '*args'.
|
|
|
|
A single instance can process all the arguments for a single call. Each call
|
|
needs a separate instance since instances have per-call state.
|
|
"""
|
|
|
|
def __init__(self, context: 'ArgumentInferContext') -> None:
|
|
# Next tuple *args index to use.
|
|
self.tuple_index = 0
|
|
# Keyword arguments in TypedDict **kwargs used.
|
|
self.kwargs_used: Set[str] = set()
|
|
# Type context for `*` and `**` arg kinds.
|
|
self.context = context
|
|
|
|
def expand_actual_type(self,
|
|
actual_type: Type,
|
|
actual_kind: nodes.ArgKind,
|
|
formal_name: Optional[str],
|
|
formal_kind: nodes.ArgKind) -> Type:
|
|
"""Return the actual (caller) type(s) of a formal argument with the given kinds.
|
|
|
|
If the actual argument is a tuple *args, return the next individual tuple item that
|
|
maps to the formal arg.
|
|
|
|
If the actual argument is a TypedDict **kwargs, return the next matching typed dict
|
|
value type based on formal argument name and kind.
|
|
|
|
This is supposed to be called for each formal, in order. Call multiple times per
|
|
formal if multiple actuals map to a formal.
|
|
"""
|
|
actual_type = get_proper_type(actual_type)
|
|
if actual_kind == nodes.ARG_STAR:
|
|
if isinstance(actual_type, Instance) and actual_type.args:
|
|
from mypy.subtypes import is_subtype
|
|
if is_subtype(actual_type, self.context.iterable_type):
|
|
return map_instance_to_supertype(
|
|
actual_type,
|
|
self.context.iterable_type.type,
|
|
).args[0]
|
|
else:
|
|
# We cannot properly unpack anything other
|
|
# than `Iterable` type with `*`.
|
|
# Just return `Any`, other parts of code would raise
|
|
# a different error for improper use.
|
|
return AnyType(TypeOfAny.from_error)
|
|
elif isinstance(actual_type, TupleType):
|
|
# Get the next tuple item of a tuple *arg.
|
|
if self.tuple_index >= len(actual_type.items):
|
|
# Exhausted a tuple -- continue to the next *args.
|
|
self.tuple_index = 1
|
|
else:
|
|
self.tuple_index += 1
|
|
return actual_type.items[self.tuple_index - 1]
|
|
elif isinstance(actual_type, ParamSpecType):
|
|
# ParamSpec is valid in *args but it can't be unpacked.
|
|
return actual_type
|
|
else:
|
|
return AnyType(TypeOfAny.from_error)
|
|
elif actual_kind == nodes.ARG_STAR2:
|
|
from mypy.subtypes import is_subtype
|
|
if isinstance(actual_type, TypedDictType):
|
|
if formal_kind != nodes.ARG_STAR2 and formal_name in actual_type.items:
|
|
# Lookup type based on keyword argument name.
|
|
assert formal_name is not None
|
|
else:
|
|
# Pick an arbitrary item if no specified keyword is expected.
|
|
formal_name = (set(actual_type.items.keys()) - self.kwargs_used).pop()
|
|
self.kwargs_used.add(formal_name)
|
|
return actual_type.items[formal_name]
|
|
elif (
|
|
isinstance(actual_type, Instance) and
|
|
len(actual_type.args) > 1 and
|
|
is_subtype(actual_type, self.context.mapping_type)
|
|
):
|
|
# Only `Mapping` type can be unpacked with `**`.
|
|
# Other types will produce an error somewhere else.
|
|
return map_instance_to_supertype(
|
|
actual_type,
|
|
self.context.mapping_type.type,
|
|
).args[1]
|
|
elif isinstance(actual_type, ParamSpecType):
|
|
# ParamSpec is valid in **kwargs but it can't be unpacked.
|
|
return actual_type
|
|
else:
|
|
return AnyType(TypeOfAny.from_error)
|
|
else:
|
|
# No translation for other kinds -- 1:1 mapping.
|
|
return actual_type
|