mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Support `expand_type` with `ParamSpec.{args,kwargs}`

Open sterliakov opened this issue 2 months ago • 4 comments

Fixes #19839.

Looks like it was relatively easy to do the right way, let me try! When splitting a callable/parameters into args and kwargs, we have the following options:

  • posonly - only goes to *args, required unless has a default. If we encounter such required arg, all previously collected optional args become required (this only happens due to faulty TVT expansion somewhere; probably I should look into that too)
  • kwonly - only goes to **kwargs, required unless has a default
  • pos-or-kw - goes to both
  • vararg - only goes to *args as an Unpack (possibly normalized by tuple constructor)
  • kwargs - only goes to **kwargs and is only used if there are no kwargs with known names, because PEP 728 is not yet implemented, so we have to choose between dict and TypedDict. (thoughts? Maybe it is better to prefer dict with union(kwarg, *kwargs.values()) as value type? Either way I do not consider this question important as PEP728 will be eventually implemented, and we'll have extra_items for our TypedDicts)

Applying these steps to every argument in order, we collect required and optional args and kwargs candidates. Now, the type of **kwargs is a TypedDict if we know any keys, dict[str, KwargType] if we only have something like **kw: str, and dict[str, Never] if no kwargs were found.

The type of *args is union of all prefixes of optional_args concatenated with required_args: all required args must be there, and optional args can only be passed in order. Since it is uncommon to have a function with more than 10-20 args, I think this union is a reasonable solution.

sterliakov avatar Oct 25 '25 22:10 sterliakov

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

github-actions[bot] avatar Oct 25 '25 22:10 github-actions[bot]

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

github-actions[bot] avatar Oct 30 '25 16:10 github-actions[bot]

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

github-actions[bot] avatar Nov 04 '25 13:11 github-actions[bot]

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

github-actions[bot] avatar Nov 25 '25 22:11 github-actions[bot]

@sterliakov Btw I just noticed that many of the unions in revealed types can be simplified by mypy.binder.collapse_variadic_union(), is it just a coincidence? If it is not a coincidence, then maybe you can re-use that helper here? (This will probably require moving it to typevartuples.py)

ilevkivskyi avatar Dec 14 '25 23:12 ilevkivskyi

Hm, looking at this again this is likely just a coincidence, because of

def f5(x: int, y: int = 0) -> int:
    return 0
def f6(x: int, *args: int) -> int:
    return 0

if the types would be different (not both ints), then there would be no compact representation.

ilevkivskyi avatar Dec 14 '25 23:12 ilevkivskyi

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

github-actions[bot] avatar Dec 14 '25 23:12 github-actions[bot]