python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.26k stars 2.79k forks source link

[stubtest] Stubtest reports incorrect argument name errors for overloads with positional only arguments #16956

Open Daverball opened 7 months ago

Daverball commented 7 months ago

Bug Report

Sometimes functions are overloaded in a way where the same positional argument (usually the very first one) is reused for different kinds of objects, affecting the outcome. So for documentation purposes it can be nice to give that argument a different name depending on the overload. As long as that argument is positional-only and that particular overload doesn't also take **kwargs, the name of the argument should be ignored when matching against the original function signature, as long as the argument matches positionally, that should be good enough, there's only a safety issue if you can also pass the same argument by name in that overload.

To Reproduce

Source:

def foo(name_or_number): ...

Stub:

@overload
def foo(number: int, /): ...
@overload
def foo(name_or_number: str): ...

Expected Behavior

stubtest is fine with accepting the first overload as a possibility

Actual Behavior

It looks like when you do that, stubtest will complain on the first overload, since there's no number argument, and then also complain on the second overload, because it will now infer that the original function must look like def foo(number: int = ..., number_or_number: str = ...) for some reason

error: [...].foo is inconsistent, stub argument "number" differs from runtime argument "text_or_number"
Stub: in file [...]
Overload(def (number: int) -> Any, def(name_or_number: str) -> Any)
Inferred signature: def (number: int = ..., name_or_number: str = ...)
Runtime: in file [...]
def (name_or_number) -> Any

error: [...].foo is inconsistent, stub argument "number" has a default value but runtime argument does not
Stub: in file [...]
Overload(def (number: int) -> Any, def(name_or_number: str) -> Any)
Inferred signature: def (number: int = ..., name_or_number: str = ...)
Runtime: in file [...]
def (name_or_number) -> Any

error: [...].foo is inconsistent, runtime does not have argument "name_or_number"
Stub: in file [...]
Overload(def (number: int) -> Any, def(name_or_number: str) -> Any)
Inferred signature: def (number: int = ..., name_or_number: str = ...)
Runtime: in file [...]
def (name_or_number) -> Any

Found 3 errors (checked 1 modules)

Your Environment

Daverball commented 7 months ago

For addtional context, this was originally discovered in this typeshed PR for the wsgify.__call__ overloads: https://github.com/python/typeshed/pull/11460/files#diff-d3561ae2db2a1abf8fe4b07affa2cd97d72b2f4a257ce9f59fec9ac761c2dce4R99-R106