crates/ty_python_semantic/resources/mdtest/function/parameters.md
Within a function scope, the declared type of each parameter is its annotated type (or Unknown if not annotated). The initial inferred type is the annotated type of the parameter, if any:
def f(declared: int, unannotated):
reveal_type(declared) # revealed: int
reveal_type(unannotated) # revealed: Unknown
The variadic parameter is a variadic tuple of its annotated type; the variadic-keywords parameter is a dictionary from strings to its annotated type:
def g(*args: int, **kwargs: int):
reveal_type(args) # revealed: tuple[int, ...]
reveal_type(kwargs) # revealed: dict[str, int]
If there is no annotation but there is a default value, the inferred paramter type is the union of
the inferred type of the default value and Unknown:
def f(a="foo", b=0, c=True, d=None):
reveal_type(a) # revealed: Unknown | Literal["foo"]
reveal_type(b) # revealed: Unknown | Literal[0]
reveal_type(c) # revealed: Unknown | Literal[True]
reveal_type(d) # revealed: Unknown | None
This means that the code in the function body needs to handle the case where the parameter is set to the default value:
def g(x=0):
print(x + 1)
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `Unknown | Literal[0]` and `Literal["foo"]`"
x + "foo"
But it still allows callers to pass in arguments of a wider type:
g(1.5) # fine
from typing import Literal
def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5, h: Literal[6] = 6, **kwargs: str):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: int
reveal_type(c) # revealed: Unknown | Literal[1]
reveal_type(d) # revealed: int
reveal_type(e) # revealed: Unknown | Literal[3]
reveal_type(f) # revealed: Literal[4]
reveal_type(g) # revealed: Unknown | Literal[5]
reveal_type(h) # revealed: Literal[6]
reveal_type(args) # revealed: tuple[object, ...]
reveal_type(kwargs) # revealed: dict[str, str]
...are inferred as tuple of Unknown or dict from string to Unknown.
def g(*args, **kwargs):
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(kwargs) # revealed: dict[str, Unknown]
If there is an annotation, we respect it fully and don't union in the default value type.
from typing import Any
def f(x: Any = 1):
reveal_type(x) # revealed: Any
The default value type must be assignable to the annotated type. If not, we emit a diagnostic, and fall back to inferring the annotated type, ignoring the default value type.
# error: [invalid-parameter-default]
def f(x: int = "foo"):
reveal_type(x) # revealed: int
# The check is assignable-to, not subtype-of, so this is fine:
from typing import Any
def g(x: Any = "foo"):
reveal_type(x) # revealed: Any
from typing import TypedDict
class Foo(TypedDict):
x: int
def x(a: Foo = {"x": 42}): ...
def y(a: Foo = dict(x=42)): ...
from typing import TypedDict
class Foo(TypedDict):
x: int
y: int
# error: [missing-typed-dict-key]
def missing_key(a: Foo = {"x": 42}): ...
# error: [invalid-argument-type]
def wrong_type(a: Foo = {"x": "s", "y": 1}): ...
# error: [invalid-key]
def extra_key(a: Foo = {"x": 1, "y": 2, "z": 3}): ...
[environment]
python-version = "3.12"
from typing import Protocol
class Foo(Protocol):
def x(self, y: bool = ...): ...
def y[T](self, y: T = ...) -> T: ...
class GenericFoo[T](Protocol):
def x(self, y: bool = ...) -> T: ...
from abc import abstractmethod
class Bar:
@abstractmethod
def x(self, y: bool = ...): ...
@abstractmethod
def y[T](self, y: T = ...) -> T: ...
from typing import overload
@overload
def x(y: None = ...) -> None: ...
@overload
def x(y: int) -> str: ...
def x(y: int | None = None) -> str | None: ...
if TYPE_CHECKING blocksWe generally view code in if TYPE_CHECKING blocks as having the same semantics and exemptions to
code in stub files:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
def foo(x: bool = ...): ... # fine