
## Summary Closes: https://github.com/astral-sh/ty/issues/1236 This PR fixes a bug where the variadic argument wouldn't match against the variadic parameter in certain scenarios. This was happening because I didn't realize that the `all_elements` iterator wouldn't keep on returning the variable element (which is correct, I just didn't realize it back then). I don't think we can use the `resize` method here because we don't know how many parameters this variadic argument is matching against as this is where the actual parameter matching occurs. ## Test Plan Expand test cases to consider a few more combinations of arguments and parameters which are variadic.
35 KiB
Call expression
Simple
def get_int() -> int:
return 42
reveal_type(get_int()) # revealed: int
Async
async def get_int_async() -> int:
return 42
reveal_type(get_int_async()) # revealed: CoroutineType[Any, Any, int]
Generic
[environment]
python-version = "3.12"
def get_int[T]() -> int:
return 42
reveal_type(get_int()) # revealed: int
Decorated
from typing import Callable
def foo() -> int:
return 42
def decorator(func) -> Callable[[], int]:
return foo
@decorator
def bar() -> str:
return "bar"
reveal_type(bar()) # revealed: int
Invalid callable
nonsense = 123
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
Potentially unbound function
def _(flag: bool):
if flag:
def foo() -> int:
return 42
# error: [possibly-unresolved-reference]
reveal_type(foo()) # revealed: int
PEP-484 convention for positional-only parameters
PEP 570, introduced in Python 3.8, added dedicated Python syntax for denoting positional-only
parameters (the /
in a function signature). However, functions implemented in C were able to have
positional-only parameters prior to Python 3.8 (there was just no syntax for expressing this at the
Python level).
Stub files describing functions implemented in C nonetheless needed a way of expressing that certain parameters were positional-only. In the absence of dedicated Python syntax, PEP 484 described a convention that type checkers were expected to understand:
Some functions are designed to take their arguments only positionally, and expect their callers never to use the argument’s name to provide that argument by keyword. All arguments with names beginning with
__
are assumed to be positional-only, except if their names also end with__
.
While this convention is now redundant (following the implementation of PEP 570), many projects still continue to use the old convention, so it is supported by ty as well.
def f(__x: int): ...
f(1)
# error: [positional-only-parameter-as-kwarg]
f(__x=1)
But not if they follow a non-positional-only parameter:
def g(x: int, __y: str): ...
g(x=1, __y="foo")
And also not if they both start and end with __
:
def h(__x__: str): ...
h(__x__="foo")
And if any parameters use the new PEP-570 convention, the old convention does not apply:
def i(x: str, /, __y: int): ...
i("foo", __y=42) # fine
And self
/cls
are implicitly positional-only:
class C:
def method(self, __x: int): ...
@classmethod
def class_method(cls, __x: str): ...
# (the name of the first parameter is irrelevant;
# a staticmethod works the same as a free function in the global scope)
@staticmethod
def static_method(self, __x: int): ...
# error: [positional-only-parameter-as-kwarg]
C().method(__x=1)
# error: [positional-only-parameter-as-kwarg]
C.class_method(__x="1")
C.static_method("x", __x=42) # fine
Splatted arguments
Unknown argument length
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: list[int]) -> None:
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, ...]) -> None:
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
Fixed-length tuple argument
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: tuple[int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args) # error: [missing-argument]
takes_two_positional_only(*args) # error: [missing-argument]
takes_two_different(*args) # error: [missing-argument]
takes_two_different_positional_only(*args) # error: [missing-argument]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
def _(args: tuple[int, int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, str]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args) # error: [invalid-argument-type]
takes_two_positional_only(*args) # error: [invalid-argument-type]
takes_two_different(*args)
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args) # error: [invalid-argument-type]
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
Subclass of fixed-length tuple argument
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
class SingleElementTuple(tuple[int]): ...
def _(args: SingleElementTuple) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args) # error: [missing-argument]
takes_two_positional_only(*args) # error: [missing-argument]
takes_two_different(*args) # error: [missing-argument]
takes_two_different_positional_only(*args) # error: [missing-argument]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
class TwoElementIntTuple(tuple[int, int]): ...
def _(args: TwoElementIntTuple) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStrTuple(tuple[int, str]): ...
def _(args: IntStrTuple) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
takes_two_positional_only(*args)
takes_two_different(*args)
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
# error: [invalid-argument-type]
takes_at_least_two(*args)
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
Mixed tuple argument
[environment]
python-version = "3.11"
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: tuple[int, *tuple[int, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, *tuple[str, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args) # error: [invalid-argument-type]
takes_two_positional_only(*args) # error: [invalid-argument-type]
takes_two_different(*args)
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args) # error: [invalid-argument-type]
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
def _(args: tuple[int, int, *tuple[int, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, int, *tuple[str, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, *tuple[int, ...], int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, *tuple[str, ...], int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args) # error: [invalid-argument-type]
takes_two_positional_only(*args) # error: [invalid-argument-type]
takes_two_different(*args)
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args) # error: [invalid-argument-type]
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
Subclass of mixed tuple argument
[environment]
python-version = "3.11"
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
def _(args: IntStarInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
def _(args: IntStarStr) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
takes_two_positional_only(*args)
takes_two_different(*args)
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
# error: [invalid-argument-type]
takes_at_least_two(*args)
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
class IntIntStarInt(tuple[int, int, *tuple[int, ...]]): ...
def _(args: IntIntStarInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
def _(args: IntIntStarStr) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
# error: [invalid-argument-type]
takes_two_different(*args)
# error: [invalid-argument-type]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarIntInt(tuple[int, *tuple[int, ...], int]): ...
def _(args: IntStarIntInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
def _(args: IntStarStrInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
takes_two_positional_only(*args)
takes_two_different(*args)
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
# error: [invalid-argument-type]
takes_at_least_two(*args)
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
String argument
from typing import Literal
def takes_zero() -> None: ...
def takes_one(x: str) -> None: ...
def takes_two(x: str, y: str) -> None: ...
def takes_two_positional_only(x: str, y: str, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: str, *args) -> None: ...
def takes_at_least_two(x: str, y: str, *args) -> None: ...
def takes_at_least_two_positional_only(x: str, y: str, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: Literal["a"]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
takes_two(*args) # error: [missing-argument]
takes_two_positional_only(*args) # error: [missing-argument]
# error: [invalid-argument-type]
# error: [missing-argument]
takes_two_different(*args)
# error: [invalid-argument-type]
# error: [missing-argument]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
def _(args: Literal["ab"]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: Literal["abc"]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
takes_two(*args) # error: [too-many-positional-arguments]
takes_two_positional_only(*args) # error: [too-many-positional-arguments]
# error: [invalid-argument-type]
# error: [too-many-positional-arguments]
takes_two_different(*args)
# error: [invalid-argument-type]
# error: [too-many-positional-arguments]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: str) -> None:
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
Wrong argument type
Positional argument, positional-or-keyword parameter
def f(x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f("foo")) # revealed: int
Positional argument, positional-only parameter
def f(x: int, /) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f("foo")) # revealed: int
Positional argument, variadic parameter
def f(*args: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f("foo")) # revealed: int
Variadic argument, variadic parameter
[environment]
python-version = "3.11"
def f(*args: int) -> int:
return 1
def _(args: list[str]) -> None:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
reveal_type(f(*args)) # revealed: int
Considering a few different shapes of tuple for the splatted argument:
def f1(*args: str): ...
def _(
args1: tuple[str, ...],
args2: tuple[str, *tuple[str, ...]],
args3: tuple[str, *tuple[str, ...], str],
args4: tuple[int, *tuple[str, ...]],
args5: tuple[int, *tuple[str, ...], str],
args6: tuple[*tuple[str, ...], str],
args7: tuple[*tuple[str, ...], int],
args8: tuple[int, *tuple[str, ...], int],
args9: tuple[str, *tuple[str, ...], int],
args10: tuple[str, *tuple[int, ...], str],
):
f1(*args1)
f1(*args2)
f1(*args3)
f1(*args4) # error: [invalid-argument-type]
f1(*args5) # error: [invalid-argument-type]
f1(*args6)
f1(*args7) # error: [invalid-argument-type]
# The reason for two errors here is because of the two fixed elements in the tuple of `args8`
# which are both `int`
# error: [invalid-argument-type]
# error: [invalid-argument-type]
f1(*args8)
f1(*args9) # error: [invalid-argument-type]
f1(*args10) # error: [invalid-argument-type]
Mixed argument and parameter containing variadic
[environment]
python-version = "3.11"
def f(x: int, *args: str) -> int:
return 1
def _(
args1: list[int],
args2: tuple[int],
args3: tuple[int, int],
args4: tuple[int, ...],
args5: tuple[int, *tuple[str, ...]],
args6: tuple[int, int, *tuple[str, ...]],
) -> None:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
reveal_type(f(*args1)) # revealed: int
# This shouldn't raise an error because the unpacking doesn't match the variadic parameter.
reveal_type(f(*args2)) # revealed: int
# But, this should because the second tuple element is not assignable.
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
reveal_type(f(*args3)) # revealed: int
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
reveal_type(f(*args4)) # revealed: int
# The first element of the tuple matches the required argument;
# all subsequent elements match the variadic argument
reveal_type(f(*args5)) # revealed: int
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
reveal_type(f(*args6)) # revealed: int
Keyword argument, positional-or-keyword parameter
def f(x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f(x="foo")) # revealed: int
Keyword argument, keyword-only parameter
def f(*, x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f(x="foo")) # revealed: int
Keyword argument, keywords parameter
def f(**kwargs: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f(x="foo")) # revealed: int
Correctly match keyword out-of-order
def f(x: int = 1, y: str = "foo") -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `Literal[2]`"
# error: 20 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["bar"]`"
reveal_type(f(y=2, x="bar")) # revealed: int
Too many positional arguments
One too many
def f() -> int:
return 1
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 1"
reveal_type(f("foo")) # revealed: int
Two too many
def f() -> int:
return 1
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 2"
reveal_type(f("foo", "bar")) # revealed: int
No too-many-positional if variadic is taken
def f(*args: int) -> int:
return 1
reveal_type(f(1, 2, 3)) # revealed: int
Multiple keyword arguments map to keyword variadic parameter
def f(**kwargs: int) -> int:
return 1
reveal_type(f(foo=1, bar=2)) # revealed: int
Missing arguments
No defaults or variadic
def f(x: int) -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
With default
def f(x: int, y: str = "foo") -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
Defaulted argument is not required
def f(x: int = 1) -> int:
return 1
reveal_type(f()) # revealed: int
With variadic
def f(x: int, *y: str) -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
Variadic argument is not required
def f(*args: int) -> int:
return 1
reveal_type(f()) # revealed: int
Keywords argument is not required
def f(**kwargs: int) -> int:
return 1
reveal_type(f()) # revealed: int
Multiple
def f(x: int, y: int) -> int:
return 1
# error: 13 [missing-argument] "No arguments provided for required parameters `x`, `y` of function `f`"
reveal_type(f()) # revealed: int
Unknown argument
def f(x: int) -> int:
return 1
# error: 20 [unknown-argument] "Argument `y` does not match any known parameter of function `f`"
reveal_type(f(x=1, y=2)) # revealed: int
Parameter already assigned
def f(x: int) -> int:
return 1
# error: 18 [parameter-already-assigned] "Multiple values provided for parameter `x` of function `f`"
reveal_type(f(1, x=2)) # revealed: int
Special functions
Some functions require special handling in type inference. Here, we make sure that we still emit proper diagnostics in case of missing or superfluous arguments.
reveal_type
from typing_extensions import reveal_type
# error: [missing-argument] "No argument provided for required parameter `obj` of function `reveal_type`"
reveal_type()
# error: [too-many-positional-arguments] "Too many positional arguments to function `reveal_type`: expected 1, got 2"
reveal_type(1, 2)
static_assert
from ty_extensions import static_assert
# error: [missing-argument] "No argument provided for required parameter `condition` of function `static_assert`"
static_assert()
# error: [too-many-positional-arguments] "Too many positional arguments to function `static_assert`: expected 2, got 3"
static_assert(True, 2, 3)
len
# error: [missing-argument] "No argument provided for required parameter `obj` of function `len`"
len()
# error: [too-many-positional-arguments] "Too many positional arguments to function `len`: expected 1, got 2"
len([], 1)
Type property predicates
from ty_extensions import is_subtype_of
# error: [missing-argument]
is_subtype_of()
# error: [missing-argument]
is_subtype_of(int)
# error: [too-many-positional-arguments]
is_subtype_of(int, int, int)
# error: [too-many-positional-arguments]
is_subtype_of(int, int, int, int)
Keywords argument
A double-starred argument (**kwargs
) can be used to pass an argument that implements the mapping
protocol. This is matched against any of the unmatched standard (positional or keyword),
keyword-only, and keywords (**kwargs
) parameters.
Empty
def empty() -> None: ...
def _(kwargs: dict[str, int]) -> None:
empty(**kwargs)
empty(**{})
empty(**dict())
Single parameter
from typing_extensions import TypedDict
def f(**kwargs: int) -> None: ...
class Foo(TypedDict):
a: int
b: int
def _(kwargs: dict[str, int]) -> None:
f(**kwargs)
f(**{"foo": 1})
f(**dict(foo=1))
f(**Foo(a=1, b=2))
Positional-only and variadic parameters
def f1(a: int, b: int, /) -> None: ...
def f2(*args: int) -> None: ...
def _(kwargs: dict[str, int]) -> None:
# error: [missing-argument] "No arguments provided for required parameters `a`, `b` of function `f1`"
f1(**kwargs)
# This doesn't raise an error because `*args` is an optional parameter and `**kwargs` can be empty.
f2(**kwargs)
Standard parameters
from typing_extensions import TypedDict
class Foo(TypedDict):
a: int
b: int
def f(a: int, b: int) -> None: ...
def _(kwargs: dict[str, int]) -> None:
f(**kwargs)
f(**{"a": 1, "b": 2})
f(**dict(a=1, b=2))
f(**Foo(a=1, b=2))
Keyword-only parameters
from typing_extensions import TypedDict
class Foo(TypedDict):
a: int
b: int
def f(*, a: int, b: int) -> None: ...
def _(kwargs: dict[str, int]) -> None:
f(**kwargs)
f(**{"a": 1, "b": 2})
f(**dict(a=1, b=2))
f(**Foo(a=1, b=2))
Multiple keywords argument
def f(**kwargs: int) -> None: ...
def _(kwargs1: dict[str, int], kwargs2: dict[str, int], kwargs3: dict[str, str], kwargs4: dict[int, list]) -> None:
f(**kwargs1, **kwargs2)
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
f(**kwargs1, **kwargs3)
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `list[Unknown]`"
f(**kwargs3, **kwargs4)
Keyword-only after keywords
class B: ...
def f(*, a: int, b: B, **kwargs: int) -> None: ...
def _(kwargs: dict[str, int]):
# Make sure that the `b` argument is not being matched against `kwargs` by passing an integer
# instead of the annotated type which should raise an
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `B`, found `Literal[2]`"
f(a=1, **kwargs, b=2)
Mixed parameter kind
def f1(*, a: int, b: int, **kwargs: int) -> None: ...
def f2(a: int, *, b: int, **kwargs: int) -> None: ...
def f3(a: int, /, *args: int, b: int, **kwargs: int) -> None: ...
def _(kwargs1: dict[str, int], kwargs2: dict[str, str]):
f1(**kwargs1)
f2(**kwargs1)
f3(1, **kwargs1)
TypedDict
from typing_extensions import NotRequired, TypedDict
class Foo1(TypedDict):
a: int
b: str
class Foo2(TypedDict):
a: int
b: NotRequired[str]
def f(**kwargs: int) -> None: ...
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
f(**Foo1(a=1, b="b"))
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
f(**Foo2(a=1))
Keys must be strings
The keys of the mapping passed to a double-starred argument must be strings.
from collections.abc import Mapping
def f(**kwargs: int) -> None: ...
class DictSubclass(dict[int, int]): ...
class MappingSubclass(Mapping[int, int]): ...
class MappingProtocol:
def keys(self) -> list[int]:
return [1]
def __getitem__(self, key: int) -> int:
return 1
def _(kwargs: dict[int, int]) -> None:
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
f(**kwargs)
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
f(**DictSubclass())
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
f(**MappingSubclass())
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
f(**MappingProtocol())
The key can also be a custom type that inherits from str
.
class SubStr(str): ...
class SubInt(int): ...
def _(kwargs1: dict[SubStr, int], kwargs2: dict[SubInt, int]) -> None:
f(**kwargs1)
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `SubInt`"
f(**kwargs2)
Or, it can be a type that is assignable to str
.
from typing import Any
from ty_extensions import Unknown
def _(kwargs1: dict[Any, int], kwargs2: dict[Unknown, int]) -> None:
f(**kwargs1)
f(**kwargs2)
Invalid value type
from collections.abc import Mapping
def f(**kwargs: str) -> None: ...
class DictSubclass(dict[str, int]): ...
class MappingSubclass(Mapping[str, int]): ...
class MappingProtocol:
def keys(self) -> list[str]:
return ["foo"]
def __getitem__(self, key: str) -> int:
return 1
def _(kwargs: dict[str, int]) -> None:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
f(**kwargs)
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
f(**DictSubclass())
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
f(**MappingSubclass())
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
f(**MappingProtocol())
Unknown
type
from ty_extensions import Unknown
def f(**kwargs: int) -> None: ...
def _(kwargs: Unknown):
f(**kwargs)
Not a mapping
def f(**kwargs: int) -> None: ...
class A: ...
class InvalidMapping:
def keys(self) -> A:
return A()
def __getitem__(self, key: str) -> int:
return 1
def _(kwargs: dict[str, int] | int):
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `dict[str, int] | int`"
f(**kwargs)
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `InvalidMapping`"
f(**InvalidMapping())
Generic
For a generic keywords parameter, the type variable should be specialized to the value type of the mapping.
from typing import TypeVar
_T = TypeVar("_T")
def f(**kwargs: _T) -> _T:
return kwargs["a"]
def _(kwargs: dict[str, int]) -> None:
reveal_type(f(**kwargs)) # revealed: int
For a TypedDict
, the type variable should be specialized to the union of all value types.
from typing import TypeVar
from typing_extensions import TypedDict
_T = TypeVar("_T")
class Foo(TypedDict):
a: int
b: str
def f(**kwargs: _T) -> _T:
return kwargs["a"]
reveal_type(f(**Foo(a=1, b="b"))) # revealed: int | str