python / python/mypy

`*args` and `**kwargs` are allowed even to methods with no arguments

Open
#13,380 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-calls
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

I am working on removing dict and **kwargs hack from mypy after https://github.com/python/typeshed/pull/8517 is merged.

But, I found a very confusing thing in how *args and **kwargs are checked.

Simple repro (all of the examples below work):

def some() -> None: ...

# args
some(*[1, 2])
args = [1, 2]
some(*args)

# kwargs
some(**{'a': 1})
kw = {'a': 2}
some(**kw)

All of these would raise TypeError in runtime.
I think that the main idea was to allow calls like some(*[]) and some(**{}) which are fine in runtime.

This affects how overloads are selected in complex cases like:

class dict2(Generic[KT, VT]):
    @overload
    def __init__(self, __iterable: Iterable[Tuple[KT, VT]]) -> None: pass
    @overload
    def __init__(self: "dict2[str, VT]", __iterable: Iterable[Tuple[str, VT]], **kwargs: VT) -> None: pass

it = [(1, 'x')]
kw = {'x': 'y'}
reveal_type(dict2(it, **kw))

It looks like the second @overload will raise an error: Iterable[Tuple[str, VT]] is required, but Iterable[Tuple[int, str]] is given.

But it does not, because of how **kwargs are silently ignored. So, first @overload always matches.

This is broken, if you ask me 😢

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the *args and **kwargs call-checking and overload-selection behavior described in the reproducer. Verify how calls to a no-argument function handle nonempty unpacked arguments and keyword arguments, while preserving valid empty unpacking. Done means invalid runtime calls are rejected and the dict2 overload example selects or reports errors correctly.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.