77
88from mypy import nodes
99from 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
1012from mypy .types import (
1113 AnyType ,
1214 Instance ,
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
0 commit comments