@@ -69,6 +69,246 @@ def _(flag: bool):
6969 reveal_type(foo()) # revealed: int
7070```
7171
72+ ## Splatted arguments
73+
74+ ### Unknown argument length
75+
76+ ``` py
77+ def takes_zero () -> None : ...
78+ def takes_one (x : int ) -> None : ...
79+ def takes_two (x : int , y : int ) -> None : ...
80+ def takes_two_positional_only (x : int , y : int , / ) -> None : ...
81+ def takes_two_different (x : int , y : str ) -> None : ...
82+ def takes_two_different_positional_only (x : int , y : str , / ) -> None : ...
83+ def takes_at_least_zero (* args ) -> None : ...
84+ def takes_at_least_one (x : int , * args ) -> None : ...
85+ def takes_at_least_two (x : int , y : int , * args ) -> None : ...
86+ def takes_at_least_two_positional_only (x : int , y : int , / , * args ) -> None : ...
87+
88+ # Test all of the above with a number of different splatted argument types
89+
90+ def _ (args : list[int ]) -> None :
91+ takes_zero(* args)
92+ takes_one(* args)
93+ takes_two(* args)
94+ takes_two_positional_only(* args)
95+ takes_two_different(* args) # error: [invalid-argument-type]
96+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
97+ takes_at_least_zero(* args)
98+ takes_at_least_one(* args)
99+ takes_at_least_two(* args)
100+ takes_at_least_two_positional_only(* args)
101+
102+ def _ (args : tuple[int , ... ]) -> None :
103+ takes_zero(* args)
104+ takes_one(* args)
105+ takes_two(* args)
106+ takes_two_positional_only(* args)
107+ takes_two_different(* args) # error: [invalid-argument-type]
108+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
109+ takes_at_least_zero(* args)
110+ takes_at_least_one(* args)
111+ takes_at_least_two(* args)
112+ takes_at_least_two_positional_only(* args)
113+ ```
114+
115+ ### Fixed-length tuple argument
116+
117+ ``` py
118+ def takes_zero () -> None : ...
119+ def takes_one (x : int ) -> None : ...
120+ def takes_two (x : int , y : int ) -> None : ...
121+ def takes_two_positional_only (x : int , y : int , / ) -> None : ...
122+ def takes_two_different (x : int , y : str ) -> None : ...
123+ def takes_two_different_positional_only (x : int , y : str , / ) -> None : ...
124+ def takes_at_least_zero (* args ) -> None : ...
125+ def takes_at_least_one (x : int , * args ) -> None : ...
126+ def takes_at_least_two (x : int , y : int , * args ) -> None : ...
127+ def takes_at_least_two_positional_only (x : int , y : int , / , * args ) -> None : ...
128+
129+ # Test all of the above with a number of different splatted argument types
130+
131+ def _ (args : tuple[int ]) -> None :
132+ takes_zero(* args) # error: [too-many-positional-arguments]
133+ takes_one(* args)
134+ takes_two(* args) # error: [missing-argument]
135+ takes_two_positional_only(* args) # error: [missing-argument]
136+ takes_two_different(* args) # error: [missing-argument]
137+ takes_two_different_positional_only(* args) # error: [missing-argument]
138+ takes_at_least_zero(* args)
139+ takes_at_least_one(* args)
140+ takes_at_least_two(* args) # error: [missing-argument]
141+ takes_at_least_two_positional_only(* args) # error: [missing-argument]
142+
143+ def _ (args : tuple[int , int ]) -> None :
144+ takes_zero(* args) # error: [too-many-positional-arguments]
145+ takes_one(* args) # error: [too-many-positional-arguments]
146+ takes_two(* args)
147+ takes_two_positional_only(* args)
148+ takes_two_different(* args) # error: [invalid-argument-type]
149+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
150+ takes_at_least_zero(* args)
151+ takes_at_least_one(* args)
152+ takes_at_least_two(* args)
153+ takes_at_least_two_positional_only(* args)
154+
155+ def _ (args : tuple[int , str ]) -> None :
156+ takes_zero(* args) # error: [too-many-positional-arguments]
157+ takes_one(* args) # error: [too-many-positional-arguments]
158+ takes_two(* args) # error: [invalid-argument-type]
159+ takes_two_positional_only(* args) # error: [invalid-argument-type]
160+ takes_two_different(* args)
161+ takes_two_different_positional_only(* args)
162+ takes_at_least_zero(* args)
163+ takes_at_least_one(* args)
164+ takes_at_least_two(* args) # error: [invalid-argument-type]
165+ takes_at_least_two_positional_only(* args) # error: [invalid-argument-type]
166+ ```
167+
168+ ### Mixed tuple argument
169+
170+ ``` toml
171+ [environment ]
172+ python-version = " 3.11"
173+ ```
174+
175+ ``` py
176+ def takes_zero () -> None : ...
177+ def takes_one (x : int ) -> None : ...
178+ def takes_two (x : int , y : int ) -> None : ...
179+ def takes_two_positional_only (x : int , y : int , / ) -> None : ...
180+ def takes_two_different (x : int , y : str ) -> None : ...
181+ def takes_two_different_positional_only (x : int , y : str , / ) -> None : ...
182+ def takes_at_least_zero (* args ) -> None : ...
183+ def takes_at_least_one (x : int , * args ) -> None : ...
184+ def takes_at_least_two (x : int , y : int , * args ) -> None : ...
185+ def takes_at_least_two_positional_only (x : int , y : int , / , * args ) -> None : ...
186+
187+ # Test all of the above with a number of different splatted argument types
188+
189+ def _ (args : tuple[int , * tuple[int , ... ]]) -> None :
190+ takes_zero(* args) # error: [too-many-positional-arguments]
191+ takes_one(* args)
192+ takes_two(* args)
193+ takes_two_positional_only(* args)
194+ takes_two_different(* args) # error: [invalid-argument-type]
195+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
196+ takes_at_least_zero(* args)
197+ takes_at_least_one(* args)
198+ takes_at_least_two(* args)
199+ takes_at_least_two_positional_only(* args)
200+
201+ def _ (args : tuple[int , * tuple[str , ... ]]) -> None :
202+ takes_zero(* args) # error: [too-many-positional-arguments]
203+ takes_one(* args)
204+ takes_two(* args) # error: [invalid-argument-type]
205+ takes_two_positional_only(* args) # error: [invalid-argument-type]
206+ takes_two_different(* args)
207+ takes_two_different_positional_only(* args)
208+ takes_at_least_zero(* args)
209+ takes_at_least_one(* args)
210+ takes_at_least_two(* args) # error: [invalid-argument-type]
211+ takes_at_least_two_positional_only(* args) # error: [invalid-argument-type]
212+
213+ def _ (args : tuple[int , int , * tuple[int , ... ]]) -> None :
214+ takes_zero(* args) # error: [too-many-positional-arguments]
215+ takes_one(* args) # error: [too-many-positional-arguments]
216+ takes_two(* args)
217+ takes_two_positional_only(* args)
218+ takes_two_different(* args) # error: [invalid-argument-type]
219+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
220+ takes_at_least_zero(* args)
221+ takes_at_least_one(* args)
222+ takes_at_least_two(* args)
223+ takes_at_least_two_positional_only(* args)
224+
225+ def _ (args : tuple[int , int , * tuple[str , ... ]]) -> None :
226+ takes_zero(* args) # error: [too-many-positional-arguments]
227+ takes_one(* args) # error: [too-many-positional-arguments]
228+ takes_two(* args)
229+ takes_two_positional_only(* args)
230+ takes_two_different(* args) # error: [invalid-argument-type]
231+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
232+ takes_at_least_zero(* args)
233+ takes_at_least_one(* args)
234+ takes_at_least_two(* args)
235+ takes_at_least_two_positional_only(* args)
236+
237+ def _ (args : tuple[int , * tuple[int , ... ], int ]) -> None :
238+ takes_zero(* args) # error: [too-many-positional-arguments]
239+ takes_one(* args) # error: [too-many-positional-arguments]
240+ takes_two(* args)
241+ takes_two_positional_only(* args)
242+ takes_two_different(* args) # error: [invalid-argument-type]
243+ takes_two_different_positional_only(* args) # error: [invalid-argument-type]
244+ takes_at_least_zero(* args)
245+ takes_at_least_one(* args)
246+ takes_at_least_two(* args)
247+ takes_at_least_two_positional_only(* args)
248+
249+ def _ (args : tuple[int , * tuple[str , ... ], int ]) -> None :
250+ takes_zero(* args) # error: [too-many-positional-arguments]
251+ takes_one(* args) # error: [too-many-positional-arguments]
252+ takes_two(* args) # error: [invalid-argument-type]
253+ takes_two_positional_only(* args) # error: [invalid-argument-type]
254+ takes_two_different(* args)
255+ takes_two_different_positional_only(* args)
256+ takes_at_least_zero(* args)
257+ takes_at_least_one(* args)
258+ takes_at_least_two(* args) # error: [invalid-argument-type]
259+ takes_at_least_two_positional_only(* args) # error: [invalid-argument-type]
260+ ```
261+
262+ ### Argument expansion regression
263+
264+ This is a regression that was highlighted by the ecosystem check, which shows that we might need to
265+ rethink how we perform argument expansion during overload resolution. In particular, we might need
266+ to retry both ` match_parameters ` _ and_ ` check_types ` for each expansion. Currently we only retry
267+ ` check_types ` .
268+
269+ The issue is that argument expansion might produce a splatted value with a different arity than what
270+ we originally inferred for the unexpanded value, and that in turn can affect which parameters the
271+ splatted value is matched with.
272+
273+ The first example correctly produces an error. The ` tuple[int, str] ` union element has a precise
274+ arity of two, and so parameter matching chooses the first overload. The second element of the tuple
275+ does not match the second parameter type, which yielding an ` invalid-argument-type ` error.
276+
277+ The third example should produce the same error. However, because we have a union, we do not see the
278+ precise arity of each union element during parameter matching. Instead, we infer an arity of "zero
279+ or more" for the union as a whole, and use that less precise arity when matching parameters. We
280+ therefore consider the second overload to still be a potential candidate for the ` tuple[int, str] `
281+ union element. During type checking, we have to force the arity of each union element to match the
282+ inferred arity of the union as a whole (turning ` tuple[int, str] ` into ` tuple[int | str, ...] ` ).
283+ That less precise tuple type-checks successfully against the second overload, making us incorrectly
284+ think that ` tuple[int, str] ` is a valid splatted call.
285+
286+ If we update argument expansion to retry parameter matching with the precise arity of each union
287+ element, we will correctly rule out the second overload for ` tuple[int, str] ` , just like we do when
288+ splatting that tuple directly (instead of as part of a union).
289+
290+ ``` py
291+ from typing import overload
292+
293+ @overload
294+ def f (x : int , y : int ) -> None : ...
295+ @overload
296+ def f (x : int , y : str , z : int ) -> None : ...
297+ def f (* args ): ...
298+
299+ # Test all of the above with a number of different splatted argument types
300+
301+ def _ (t : tuple[int , str ]) -> None :
302+ f(* t) # error: [invalid-argument-type]
303+
304+ def _ (t : tuple[int , str , int ]) -> None :
305+ f(* t)
306+
307+ def _ (t : tuple[int , str ] | tuple[int , str , int ]) -> None :
308+ # TODO : error: [invalid-argument-type]
309+ f(* t)
310+ ```
311+
72312## Wrong argument type
73313
74314### Positional argument, positional-or-keyword parameter
0 commit comments