Skip to content

Commit 7db048d

Browse files
more cleanup and documentation
cleanup fix param spec tuple interaction fix self check more cleanup use Unpack for 3.9 / 3.10 tests fixed crash in overload with multiple rvalues fixed basic tuple expression round trip fix bad paramspec interaction with typevartuple reworked star argument checking
1 parent 30b2a2d commit 7db048d

106 files changed

Lines changed: 3831 additions & 904 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

mypy/argmap.py

Lines changed: 143 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from mypy import nodes
99
from mypy.maptype import map_instance_to_supertype
10+
from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT, ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2
11+
from mypy.tuple_normal_form import TupleHelper, TupleNormalForm
1012
from mypy.types import (
1113
AnyType,
1214
Instance,
@@ -16,6 +18,7 @@
1618
TypedDictType,
1719
TypeOfAny,
1820
TypeVarTupleType,
21+
UninhabitedType,
1922
UnpackType,
2023
get_proper_type,
2124
)
@@ -44,44 +47,35 @@ def map_actuals_to_formals(
4447
ambiguous_actual_kwargs: list[int] = []
4548
fi = 0
4649
for ai, actual_kind in enumerate(actual_kinds):
47-
if actual_kind == nodes.ARG_POS:
50+
if actual_kind == ARG_POS:
4851
if fi < nformals:
49-
if not formal_kinds[fi].is_star():
52+
if formal_kinds[fi] in (ARG_POS, ARG_OPT):
5053
formal_to_actual[fi].append(ai)
5154
fi += 1
52-
elif formal_kinds[fi] == nodes.ARG_STAR:
55+
elif formal_kinds[fi] == ARG_STAR:
5356
formal_to_actual[fi].append(ai)
54-
elif actual_kind == nodes.ARG_STAR:
55-
# We need to know the actual type to map varargs.
56-
actualt = get_proper_type(actual_arg_type(ai))
57-
if isinstance(actualt, TupleType):
58-
# A tuple actual maps to a fixed number of formals.
59-
for _ in range(len(actualt.items)):
60-
if fi < nformals:
61-
if formal_kinds[fi] != nodes.ARG_STAR2:
62-
formal_to_actual[fi].append(ai)
63-
else:
64-
break
65-
if formal_kinds[fi] != nodes.ARG_STAR:
66-
fi += 1
67-
else:
68-
# Assume that it is an iterable (if it isn't, there will be
69-
# an error later).
70-
while fi < nformals:
71-
if formal_kinds[fi].is_named(star=True):
72-
break
73-
else:
74-
formal_to_actual[fi].append(ai)
75-
if formal_kinds[fi] == nodes.ARG_STAR:
76-
break
77-
fi += 1
57+
elif actual_kind == ARG_STAR:
58+
# convert the actual argument type to a tuple-like type
59+
star_arg_type = TupleNormalForm.from_star_argument(actual_arg_type(ai))
60+
61+
# for a variadic argument use a negative value, so it remains truthy when decremented
62+
# otherwise, use the length of the prefix.
63+
num_actual_items = -1 if star_arg_type.is_variadic else len(star_arg_type.prefix)
64+
# note: empty tuple star-args will not get mapped to anything
65+
while fi < nformals and num_actual_items:
66+
if formal_kinds[fi] in (ARG_POS, ARG_OPT, ARG_STAR):
67+
formal_to_actual[fi].append(ai)
68+
num_actual_items -= 1
69+
if formal_kinds[fi] in (ARG_STAR, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR2):
70+
break
71+
fi += 1
7872
elif actual_kind.is_named():
7973
assert actual_names is not None, "Internal error: named kinds without names given"
8074
name = actual_names[ai]
8175
if name in formal_names and formal_kinds[formal_names.index(name)] != nodes.ARG_STAR:
8276
formal_to_actual[formal_names.index(name)].append(ai)
83-
elif nodes.ARG_STAR2 in formal_kinds:
84-
formal_to_actual[formal_kinds.index(nodes.ARG_STAR2)].append(ai)
77+
elif ARG_STAR2 in formal_kinds:
78+
formal_to_actual[formal_kinds.index(ARG_STAR2)].append(ai)
8579
else:
8680
assert actual_kind == nodes.ARG_STAR2
8781
actualt = get_proper_type(actual_arg_type(ai))
@@ -171,18 +165,86 @@ def __init__(self, context: ArgumentInferContext) -> None:
171165
# Type context for `*` and `**` arg kinds.
172166
self.context = context
173167

168+
def parse_star_argument(self, star_arg: Type, /) -> TupleType:
169+
r"""Parse the type of ``*args`` argument into a tuple type.
170+
171+
Note: For star parameters, use `parse_star_parameter` instead.
172+
"""
173+
tnf = TupleNormalForm.from_star_argument(star_arg)
174+
return tnf.materialize(self.context)
175+
176+
def parse_star_parameter(self, star_param: Type, /) -> TupleType:
177+
r"""Parse the type of a ``*args: T`` parameter into a tuple type.
178+
179+
This is different from `parse_star_argument` since mypy does some translation
180+
for certain annotations. Below are some examples of how this works.
181+
182+
| annotation | semanal result | parsed result |
183+
|-----------------------|-----------------------|-------------------------|
184+
| *args: int | int | tuple[*tuple[int, ...]] |
185+
| *args: *tuple[T, ...] | Unpack[tuple[T, ...]] | tuple[*tuple[T, ...]] |
186+
| *args: *tuple[A, B] | Unpack[tuple[A, B]] | tuple[A, B] |
187+
| *args: *Ts | Unpack[Ts] | tuple[*Ts] |
188+
| *args: P.args | P.args | tuple[*P.args] |
189+
"""
190+
p_t = get_proper_type(star_param)
191+
if isinstance(p_t, UnpackType):
192+
unpacked = get_proper_type(p_t.type)
193+
if isinstance(unpacked, TupleType):
194+
return unpacked
195+
return TupleType([p_t], fallback=self.context.fallback_tuple)
196+
197+
elif isinstance(p_t, ParamSpecType):
198+
# We put the ParamSpec inside an UnpackType.
199+
parsed = UnpackType(p_t)
200+
return TupleType([parsed], fallback=self.context.fallback_tuple)
201+
202+
else: # e.g. *args: int --> *args: *tuple[int, ...]
203+
parsed = UnpackType(self.context.make_tuple_instance_type(p_t))
204+
return TupleType([parsed], fallback=self.context.fallback_tuple)
205+
206+
@staticmethod
207+
def unparse_star_parameter(t: Type, /) -> Type:
208+
r"""Reverse normalizations done by parse_star_parameter.
209+
210+
tuple[*tuple[T, ...]] -> T
211+
tuple[A, B] -> *tuple[A, B]
212+
tuple[*Ts] -> *Ts
213+
tuple[*P.args] -> P.args
214+
"""
215+
p_t = get_proper_type(t)
216+
assert isinstance(p_t, TupleType), f"Expected a parsed star argument, got {t}"
217+
simplified_type = p_t.simplify()
218+
219+
# convert tuple[T, ...] to plain T.
220+
if isinstance(simplified_type, Instance):
221+
assert simplified_type.type.fullname == "builtins.tuple"
222+
return simplified_type.args[0]
223+
# wrap tuple and Ts in UnpackType
224+
elif isinstance(simplified_type, (TupleType, TypeVarTupleType)):
225+
return UnpackType(simplified_type)
226+
# return ParamSpec as is.
227+
elif isinstance(simplified_type, ParamSpecType):
228+
return simplified_type
229+
else:
230+
assert False, f"Unexpected unpack content {simplified_type!r}"
231+
174232
def expand_actual_type(
175233
self,
176234
actual_type: Type,
177235
actual_kind: nodes.ArgKind,
178236
formal_name: str | None,
179237
formal_kind: nodes.ArgKind,
180-
allow_unpack: bool = False,
181238
) -> Type:
182239
"""Return the actual (caller) type(s) of a formal argument with the given kinds.
183240
184-
If the actual argument is a tuple *args, return the next individual tuple item that
185-
maps to the formal arg.
241+
If the actual argument is a star argument *args, then:
242+
1. If the formal argument is positional, return the next individual tuple item that
243+
maps to the formal arg.
244+
If the tuple is exhausted, returns UninhabitedType.
245+
2. If the formal argument is a star parameter, returns a tuple type with the items
246+
that map to the formal arg by slicing.
247+
If the tuple is exhausted, returns an empty tuple type.
186248
187249
If the actual argument is a TypedDict **kwargs, return the next matching typed dict
188250
value type based on formal argument name and kind.
@@ -192,51 +254,56 @@ def expand_actual_type(
192254
"""
193255
original_actual = actual_type
194256
actual_type = get_proper_type(actual_type)
195-
if actual_kind == nodes.ARG_STAR:
196-
if isinstance(actual_type, TypeVarTupleType):
197-
# This code path is hit when *Ts is passed to a callable and various
198-
# special-handling didn't catch this. The best thing we can do is to use
199-
# the upper bound.
200-
actual_type = get_proper_type(actual_type.upper_bound)
201-
if isinstance(actual_type, Instance) and actual_type.args:
202-
from mypy.subtypes import is_subtype
203-
204-
if is_subtype(actual_type, self.context.iterable_type):
205-
return map_instance_to_supertype(
206-
actual_type, self.context.iterable_type.type
207-
).args[0]
208-
else:
209-
# We cannot properly unpack anything other
210-
# than `Iterable` type with `*`.
211-
# Just return `Any`, other parts of code would raise
212-
# a different error for improper use.
213-
return AnyType(TypeOfAny.from_error)
214-
elif isinstance(actual_type, TupleType):
215-
# Get the next tuple item of a tuple *arg.
216-
if self.tuple_index >= len(actual_type.items):
217-
# Exhausted a tuple -- continue to the next *args.
218-
self.tuple_index = 1
219-
else:
220-
self.tuple_index += 1
221-
item = actual_type.items[self.tuple_index - 1]
222-
if isinstance(item, UnpackType) and not allow_unpack:
223-
# An unpack item that doesn't have special handling, use upper bound as above.
224-
unpacked = get_proper_type(item.type)
225-
if isinstance(unpacked, TypeVarTupleType):
226-
fallback = get_proper_type(unpacked.upper_bound)
227-
else:
228-
fallback = unpacked
229-
assert (
230-
isinstance(fallback, Instance)
231-
and fallback.type.fullname == "builtins.tuple"
232-
)
233-
item = fallback.args[0]
234-
return item
235-
elif isinstance(actual_type, ParamSpecType):
236-
# ParamSpec is valid in *args but it can't be unpacked.
237-
return actual_type
257+
258+
if actual_kind == ARG_STAR:
259+
assert formal_kind in (ARG_POS, ARG_OPT, ARG_STAR)
260+
# parse *args into a TupleType.
261+
tuple_helper = TupleHelper(self.context.tuple_typeinfo)
262+
star_args_type = self.parse_star_argument(actual_type)
263+
264+
# # star_args_type failed to parse. treat as if it were tuple[Any, ...]
265+
# if isinstance(star_args_type, AnyType):
266+
# any_tuple = self.context.make_tuple_instance_type(AnyType(TypeOfAny.from_error))
267+
# star_args_type = self.context.make_tuple_type([UnpackType(any_tuple)])
268+
269+
assert isinstance(star_args_type, TupleType)
270+
271+
# we are mapping an actual *args to positional arguments.
272+
if formal_kind in (ARG_POS, ARG_OPT):
273+
value = tuple_helper.get_item(star_args_type, self.tuple_index)
274+
self.tuple_index += 1
275+
276+
# FIXME: In principle, None should indicate out-of-bounds access
277+
# caused by an error in formal_to_actual mapping.
278+
# assert value is not None, "error in formal_to_actual mapping"
279+
# However, in some cases due to lack of machinery it can happen:
280+
# For example f(*[]). Then formal_to_actual is ignorant of the fact
281+
# that the list is empty, but when materializing the tuple we actually get an empty tuple.
282+
# Therefore, we currently just return UninhabitedType in this case.
283+
value = UninhabitedType() if value is None else value
284+
285+
# if the argument is exhausted, reset the index
286+
if (
287+
not star_args_type.is_variadic
288+
and self.tuple_index >= star_args_type.minimum_length
289+
):
290+
self.tuple_index = 0
291+
return value
292+
293+
# we are mapping an actual *args input to a *args formal argument.
294+
elif formal_kind == ARG_STAR:
295+
# get the slice from the current index to the end of the tuple.
296+
r = tuple_helper.get_slice(star_args_type, self.tuple_index, None)
297+
# r = star_args_type.slice(
298+
# self.tuple_index, None, None, fallback=self.context.tuple_type
299+
# )
300+
self.tuple_index = 0
301+
# assert r is not None, f"failed to slice {star_args_type} at {self.tuple_index}"
302+
return r
303+
238304
else:
239-
return AnyType(TypeOfAny.from_error)
305+
raise AssertionError(f"Unexpected formal kind {formal_kind} for *args")
306+
240307
elif actual_kind == nodes.ARG_STAR2:
241308
from mypy.subtypes import is_subtype
242309

mypy/checker.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4259,6 +4259,27 @@ def check_multi_assignment_from_tuple(
42594259
# inferred return type for an overloaded function
42604260
# to be ambiguous.
42614261
return
4262+
if (
4263+
isinstance(reinferred_rvalue_type, Instance)
4264+
and reinferred_rvalue_type.type.fullname == "builtins.tuple"
4265+
):
4266+
# the type can change into variadic tuple if the added context picks a different overload
4267+
# see testOverloadWithOverlappingItemsAndAnyArgument17 as an example
4268+
rv_type = reinferred_rvalue_type.args[0]
4269+
for lv in lvalues:
4270+
if (
4271+
isinstance(lv, NameExpr)
4272+
and isinstance(lv.node, Var)
4273+
and lv.node.type is None
4274+
):
4275+
self.check_assignment(
4276+
lv, self.temp_node(rv_type, context), infer_lvalue_type
4277+
)
4278+
elif isinstance(lv, StarExpr):
4279+
list_expr = ListExpr([StarExpr(self.temp_node(rv_type, context))])
4280+
list_expr.set_line(context)
4281+
self.check_assignment(lv.expr, list_expr, infer_lvalue_type)
4282+
return
42624283
assert isinstance(reinferred_rvalue_type, TupleType)
42634284
rvalue_type = reinferred_rvalue_type
42644285

0 commit comments

Comments
 (0)