crates/ty_python_semantic/resources/mdtest/call/function.md
def get_int() -> int:
return 42
reveal_type(get_int()) # revealed: int
from typing import Any
def accepts_anything(first: int, *args: Any, **kwargs: Any) -> None: ...
def accepts_only_gradual(*args: Any, **kwargs: Any) -> None: ...
accepts_anything(1, "one", object(), keyword=object())
accepts_anything("not an int") # error: [invalid-argument-type]
accepts_only_gradual(1, "one", keyword=object())
accepts_only_gradual(**{1: "one"}) # error: [invalid-argument-type]
async def get_int_async() -> int:
return 42
reveal_type(get_int_async()) # revealed: CoroutineType[Any, Any, int]
[environment]
python-version = "3.12"
def get_int[T]() -> int:
return 42
reveal_type(get_int()) # revealed: int
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
nonsense = 123
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
def _(flag: bool):
if flag:
def foo() -> int:
return 42
# error: [possibly-unresolved-reference]
reveal_type(foo()) # revealed: int
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. This is flagged with a different error code since (per the typing spec), this is likely a mistake from the user:
from typing import overload
# error: [invalid-legacy-positional-parameter]
def g(x: int, __y: str): ...
g(x=1, __y="foo")
# The earlier `g` definition is shadowed here,
# but we still emit a diagnostic on the earlier definition
def g(): ...
Because the lint is a syntactic check, we emit it for each overload if multiple overloads violate the lint:
import tkinter
from typing import Callable, TypeVar, Any
@overload
def g2(x: int, __y: str): ... # error: [invalid-legacy-positional-parameter]
@overload
def g2(x: str, __y: int): ... # error: [invalid-legacy-positional-parameter]
def g2(x: str | int, __y: int | str): ... # error: [invalid-legacy-positional-parameter]
T = TypeVar("T")
def copy_type(f: T) -> Callable[[Any], T]:
return lambda x: x
# Naively iterating over the overloads using `.iter_overloads_and_implementation()` and/or
# using `.signature()` would cause us to panic on this function, because the overloads
# of this function's public signature are defined in `stdlib/tkinter/__init__.pyi` due to
# the decorator.
@copy_type(tkinter.Text.__init__)
def g3(x, *args: Any, **kwargs: Any) -> None: ...
def new_signature(): ...
# The check is able to "see through" the decorators and examines the original function's
# signature:
@copy_type(new_signature)
def g4(a, __b): ... # error: [invalid-legacy-positional-parameter]
Parameters are also not understood as positional-only 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: [invalid-legacy-positional-parameter]
# `__new__` is a staticmethod, but the `cls` parameter works in the same way as the `cls`
# parameter in a classmethod, and is always passed positionally at runtime,
# We therefore understand both `cls` and `__x` here as positional-only; we do not
# emit `[invalid-legacy-positional-parameter]` on the method.
def __new__(cls, __x: int): ...
# error: [positional-only-parameter-as-kwarg]
C(42).method(__x=1)
# error: [positional-only-parameter-as-kwarg]
C.class_method(__x="1")
C.static_method("x", __x=42) # fine
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(*b"ab")
takes_two(*b"abc") # error: [too-many-positional-arguments]
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)
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]
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)
[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]
[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)
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)
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
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
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
[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]
A union of heterogeneous tuples provided to a variadic parameter:
# Test inspired by ecosystem code at:
# - <https://github.com/home-assistant/core/blob/bde4eb50111a72f9717fe73ee5929e50eb06911b/homeassistant/components/lovelace/websocket.py#L50-L59>
# - <https://github.com/pydata/xarray/blob/3572f4e70f2b12ef9935c1f8c3c1b74045d2a092/xarray/tests/test_groupby.py#L3058-L3059>
def f2(a: str, b: bool): ...
def f3(coinflip: bool):
if coinflip:
args = "foo", True
else:
args = "bar", False
# revealed: tuple[Literal["foo"], Literal[True]] | tuple[Literal["bar"], Literal[False]]
reveal_type(args)
f2(*args) # fine
if coinflip:
other_args = "foo", True
else:
other_args = "bar", (True,)
# revealed: tuple[Literal["foo"], Literal[True]] | tuple[Literal["bar"], tuple[Literal[True]]]
reveal_type(other_args)
# error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `bool`, found `Literal[True] | tuple[Literal[True]]`"
f2(*other_args)
def f4(a=None, b=None, c=None, d=None, e=None): ...
my_args = ((1, 2), (3, 4), (5, 6))
for tup in my_args:
f4(*tup, e=None) # fine
my_other_args = (
(1, 2, 3, 4, 5),
(6, 7, 8, 9, 10),
)
for tup in my_other_args:
# error: [parameter-already-assigned] "Multiple values provided for parameter `e` of function `f4`"
f4(*tup, e=None)
Regression test for https://github.com/astral-sh/ty/issues/2734.
def f5(x: int | None = None, y: str = "") -> None: ...
def f6(flag: bool) -> None:
args = () if flag else (1,)
f5(*args)
def f7(x: int | None = None, y: str = "") -> None: ...
def f8(flag: bool) -> None:
args = () if flag else ("bad",)
f7(*args) # error: [invalid-argument-type]
def f11(*args: int) -> None: ...
def f12(args: tuple[int] | int) -> None:
f11(*args) # error: [not-iterable]
def f13(a: int, b: int, c: str) -> None: ...
def f14(a: int, b: int, c: str, d: list[float], e: list[float]) -> None: ...
def f15(profile: bool, line: str) -> None:
matcher = f13
timings = []
if profile:
matcher = f14
timings = [[0.0], [1.0], [2.0], [3.0]]
matcher(1, 2, line, *timings[:2])
def f9(x: int = 0, y: str = "") -> None: ...
def f10(args: tuple[int, ...] | tuple[int, str]) -> None:
# The variable-length element `int` from `tuple[int, ...]` unions with `str`
# from `tuple[int, str]` at position 1, giving `int | str` for `y: str`.
f9(*args) # error: [invalid-argument-type]
def f18(x: int = 0, y: int = 0) -> None: ...
def f19(args: tuple[int, ...] | tuple[int, int]) -> None:
f18(*args)
# Union variadic unpacking also works when the non-defaulted parameters are covered by
# the shortest union element, even if not all remaining parameters are defaulted.
def f16(a: int, b: int = 0, c: str = "") -> None: ...
def f17(x: tuple[int] | tuple[int, int]) -> None:
f16(*x)
# Longer union elements must still be rejected when they would contribute
# extra positional arguments.
def f20(a: int, b: int) -> None: ...
def f21(x: tuple[int, int] | tuple[int, int, int]) -> None:
f20(*x) # error: [too-many-positional-arguments]
# Shorter union elements must also be rejected when they cannot provide a required
# positional argument.
def f22(a: int, b: int, c: int) -> None: ...
def f23(x: tuple[int, int] | tuple[int, int, int]) -> None:
f22(*x) # error: [missing-argument]
# Later positional arguments must not be allowed to "slide left" when a longer
# union member would still bind an incompatible tuple element. We currently
# handle this conservatively, so this still reports the broader iterator-based
# family of errors.
def f24(a: int, b: int, c: int = 0) -> None: ...
def f25(x: tuple[int] | tuple[int, str]) -> None:
# error: [invalid-argument-type]
# error: [invalid-argument-type]
# error: [too-many-positional-arguments]
f24(*x, 1) # error: [invalid-argument-type]
[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
When a variable-length iterable (like a list) is unpacked and followed by explicit keyword arguments, the variadic unpacking should not greedily consume parameters that have explicit keyword bindings. This prevents false positive "parameter already assigned" errors.
Regression test for https://github.com/astral-sh/ty/issues/1584.
from typing import TypedDict
def f(a: str, b: str, c: float) -> None: ...
# Explicit keyword argument takes precedence over variable-length variadic expansion.
# The list unpacking should only fill `a` and `b`, leaving `c` for the keyword argument.
def _(args: list[str]) -> None:
f(*args, c=1.0)
# Fixed-length tuple unpacking with keyword argument also works correctly.
def _(args: tuple[str, str]) -> None:
f(*args, c=1.0)
# But, with a fixed-length tuple that is too long, we get the expected error.
def _(args: tuple[str, str, str]) -> None:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int | float`, found `str`"
# error: [parameter-already-assigned] "Multiple values provided for parameter `c` of function `f`"
f(*args, c=1.0)
However, when there's no explicit keyword argument, the behavior remains conservative:
# Positional argument after variable-length variadic is ambiguous
def _(args: list[str]) -> None:
# error: [invalid-argument-type]
# error: [too-many-positional-arguments]
f(*args, 1.0)
# Multiple variable-length variadics are also ambiguous
def _(args1: list[str], args2: list[float]) -> None:
# error: [invalid-argument-type]
f(*args1, *args2)
# Keyword variadic (**dict) with unknown keys is also ambiguous
def _(args: list[str], kwargs: dict[str, float]) -> None:
# error: [invalid-argument-type]
f(*args, **kwargs)
# Keyword variadic with TypedDict has known keys but is still handled conservatively
# but we could possibly improve this in the future.
class CKwargs(TypedDict):
c: float
def _(args: list[str]) -> None:
# error: [invalid-argument-type]
# error: [parameter-already-assigned]
f(*args, **CKwargs(c=1.0))
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
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
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
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
from typing import Sized
class Foo: ...
class Bar: ...
class Baz: ...
def f(x: Sized): ...
def g(
a: str | Foo,
b: list[str] | str | dict[str, str] | tuple[str, ...] | bytes | frozenset[str] | set[str] | Foo,
c: list[str] | str | dict[str, str] | tuple[str, ...] | bytes | frozenset[str] | set[str] | Foo | Bar,
d: list[str] | str | dict[str, str] | tuple[str, ...] | bytes | frozenset[str] | set[str] | Foo | Bar | Baz,
):
f(a) # error: [invalid-argument-type]
f(b) # error: [invalid-argument-type]
f(c) # error: [invalid-argument-type]
f(d) # error: [invalid-argument-type]
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
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
def f(*args: int) -> int:
return 1
reveal_type(f(1, 2, 3)) # revealed: int
def f(**kwargs: int) -> int:
return 1
reveal_type(f(foo=1, bar=2)) # revealed: int
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
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
def f(x: int = 1) -> int:
return 1
reveal_type(f()) # revealed: int
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
def f(*args: int) -> int:
return 1
reveal_type(f()) # revealed: int
def f(**kwargs: int) -> int:
return 1
reveal_type(f()) # revealed: int
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
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
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
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_typefrom 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_assertfrom 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"
# error: [invalid-argument-type] "Argument to function `static_assert` is incorrect: Expected `LiteralString | None`, found `Literal[2]`"
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)
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)
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.
def empty() -> None: ...
def _(kwargs: dict[str, int]) -> None:
empty(**kwargs)
empty(**{})
empty(**dict())
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))
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)
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))
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))
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)
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)
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)
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))
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]):
def __iter__(self): ...
def __len__(self): ...
def __getitem__(self, key): ...
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)
from collections.abc import Mapping
def f(**kwargs: str) -> None: ...
class DictSubclass(dict[str, int]): ...
class MappingSubclass(Mapping[str, int]):
def __iter__(self): ...
def __len__(self): ...
def __getitem__(self, key) -> int:
return 42
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 typefrom ty_extensions import Unknown
def f(**kwargs: int) -> None: ...
def _(kwargs: Unknown):
f(**kwargs)
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())
When **kwargs with a non-mapping type is passed to an overloaded function, the error should report
the specific mapping type error.
overloaded.pyi:
from typing import overload
@overload
def f(x: int, **kwargs: int) -> int: ...
@overload
def f(x: str, **kwargs: str) -> str: ...
from overloaded import f
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `None`"
f(1, **None)
def _(kwargs: dict[str, int] | int):
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `dict[str, int] | int`"
f(1, **kwargs)
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
A starred argument must be iterable. If it is not, an error should be reported.
def some_fn(a: int):
pass
# error: [not-iterable] "Object of type `None` is not iterable"
some_fn(*None)
This also applies when the type might not be iterable:
def f(*args: int) -> int:
return 1
def _(x: int | list[int]):
# error: [not-iterable] "Object of type `int | list[int]` may not be iterable"
f(*x)
overloaded.pyi:
from typing import overload
@overload
def foo(a: int) -> tuple[int]: ...
@overload
def foo(a: int, b: int) -> tuple[int, int]: ...
from overloaded import foo
# error: [not-iterable] "Object of type `None` is not iterable"
foo(*None)
def _(arg: int):
# error: [not-iterable] "Object of type `int` is not iterable"
foo(*arg)
When a union type containing variable-length elements (like Unknown) is unpacked as *args, the
variadic expansion should not greedily consume optional positional parameters that are also provided
as explicit keyword arguments.
from ty_extensions import Unknown
def f(a: int = 0, b: int = 0, c: int = 0, fmt: str | None = None) -> None: ...
def _(args: "Unknown | tuple[int, int, int]"):
f(*args, fmt="{key}") # fine
def g(a: int, b: int = 0, c: int = 0) -> None: ...
def _(args: tuple[int, int] | tuple[int, int, int]):
g(*args, c=1) # error: [parameter-already-assigned]
When unpacking (a union of) fixed-length tuples, variadic matching should stop once the known
positions are exhausted. Otherwise, optional positional parameters can be incorrectly treated as
already assigned, causing false positives for **kwargs.
(This test uses **kwargs unpacking of a TypedDict instead of the simpler c=1 keyword argument,
because c=1 is a known keyword argument and we always prevent unpacking *args over an
explicitly-provided keyword argument. The case shown here, without the explicit keyword argument,
requires instead that we use our knowledge of the tuple length to prevent over-unpacking.)
from typing import TypedDict
class CKwargs(TypedDict):
c: int
def f(a: int = 0, b: int = 0, c: int = 0) -> None: ...
def _(args_tuple: tuple[int, int], args_union: tuple[int] | tuple[int, int], kwargs: CKwargs) -> None:
f(*args_tuple, **kwargs) # fine
f(*args_union, **kwargs) # fine