Skip to content

mypy fails to consider the case where *args or **kwds contains zero elements #4001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
yutak opened this issue Sep 25, 2017 · 8 comments · Fixed by #9629
Closed

mypy fails to consider the case where *args or **kwds contains zero elements #4001

yutak opened this issue Sep 25, 2017 · 8 comments · Fixed by #9629
Labels

Comments

@yutak
Copy link

yutak commented Sep 25, 2017

Minimum repro:

object(*[])
object(**{})

This gives the following errors:

Too many arguments for "object"
Too many arguments for "object"

I think this is valid code and should pass the type check.

Context: I encountered this when I tried to write a multiple inheritance model where each class is passing arguments to the next parent (in terms of MRO):

class A(object):
    def __init__(self, a_arg: int, **kwds: Any) -> None:
        super().__init__(**kwds)

class B(object):
    def __init__(self, b_arg: int, **kwds: Any) -> None:
        super().__init__(**kwds)

class C(A, B):
    def __init__(self, c_arg: int, **kwds: Any) -> None:
        super().__init__(**kwds)

C(a_arg=1, b_arg=2, c_arg=3)

# This code runs fine but gives three type errors saying:
#     Too many arguments for "__init__" of "object"

You cannot expect a static derivation order due to the dynamic nature of MRO, so it's hard to avoid this error from happening.

@gvanrossum
Copy link
Member

While your minimal repro indeed gives those errors (and probably shouldn't), in your actual example things are not so simple -- mypy only looks at your source code, it doesn't execute your code, so when it type-checks the line super().__init__(**kwds) in your full example it can't actually assume that the argument list for object() is empty -- this depends on the call site, and while you show a call that would indeed result in an empty argument list, there may be other calls to C() that end up passing a non-empty kwds to object(). Mypy type-checks calls purely on the basis of the given signature, and the signature for C() says that any keyword arguments are accepted.

@astrojuanlu
Copy link
Contributor

Is this related to #4335?

@gvanrossum
Copy link
Member

@Juanlu001

Is this related to #4335?

I don't see how. Maybe you mistyped the issue number? #4335 is about multiple inheritance. This issue is about varargs function calls.

@astrojuanlu
Copy link
Contributor

I was confused because the original example in this issue used multiple inheritance as well.

@ilevkivskyi
Copy link
Member

The first example object(*[]) now works correctly, second one still fails. Should be probably not hard to fix.

@momohatt
Copy link
Contributor

momohatt commented Mar 22, 2020

Hi, I'd like to work on this as my first issue and I have a question.
Current implementation requires the type of kwargs in **kwargs to be a subtype of dict[str, <any>].

mypy/mypy/checkexpr.py

Lines 3790 to 3795 in 1677efd

def is_valid_keyword_var_arg(self, typ: Type) -> bool:
"""Is a type valid as a **kwargs argument?"""
if self.chk.options.python_version[0] >= 3:
return is_subtype(typ, self.chk.named_generic_type(
'typing.Mapping', [self.named_type('builtins.str'),
AnyType(TypeOfAny.special_form)]))

However, an empty dictionary {} is currently typed as dict[<nothing>, <nothing>] and fails this check, presumably because dictionary types are contravariant at the key position. Would it be better to type {} as dict[<any>, <nothing>] ?

EDIT: I think #5580 is the same problem as this

@NeilGirdhar
Copy link
Contributor

This issue is explained with __init__, but the same problem happens when calling super in other methods like __init_subclass__.

@momohatt
Copy link
Contributor

I think it's difficult to judge whether a given dictionary is empty or not, so we cannot help but give up the "Too many arguments" check whenever there are arguments like **kwds (i.e. the third line of the following should not raise error)

def f(): pass
f(**{}) # ok
f(**{'a':0}) # this should also be okay (false-negative)

This is consistent with the case of lists and variable-length tuples:

def f(): pass
f(*[]) # ok
f(*[1]) # also ok
x: Tuple[int, ...] = ()
f(*x) # ok

At first I thought we can take advantage of the fact that an empty dictionary is typed as dict[<nothing>, <nothing>], but that will contradict with this solution (#5580 (comment)) on fixing the incorrect type checking on ** arguments...

JelleZijlstra pushed a commit that referenced this issue Aug 26, 2021
### Description

Closes #5580
Previously, the type of empty dicts are inferred as `dict[<nothing>, <nothing>]`, which is not a subtype of `Mapping[str, Any]`, and this has caused a false-positive error saying `Keywords must be strings`. This PR fixes it by inferring the types of double-starred arguments with a context of `Mapping[str, Any]`. 

Closes #4001 and closes #9007 (duplicate)
Do not check for "too many arguments" error when there are any double-starred arguments. This will lead to some false-negavites, see my comment here: #4001 (comment)

### Test Plan

Added a simple test `testPassingEmptyDictWithStars`. This single test can cover both of the issues above.

I also modified some existing tests that were added in #9573 and #6213.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants