blob: eeadcaaaa52aeb817a3c15a51e92f4e0716ce52c [file] [edit]
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use crate::test::util::TestEnv;
use crate::testcase;
// `CallArgPreEval::advance_after_match` is shared with shape-specific call matching. Keep this
// regression in the general callable suite so the refactor cannot change ordinary variadic
// type-variable consumption.
testcase!(
ordinary_type_var_tuple_argument_advancement_is_unchanged,
r#"
from typing import assert_type
def pack[*Ts](*args: *Ts) -> tuple[*Ts]: ...
assert_type(pack(1, "x"), tuple[int, str])
def check(xs: tuple[int, str]) -> None:
assert_type(pack(*xs), tuple[int, str])
"#,
);
testcase!(
test_lambda,
r#"
from typing import Callable, reveal_type
f1 = lambda x: 1
reveal_type(f1) # E: revealed type: (x: Unknown) -> Literal[1]
f2 = lambda x: reveal_type(x) # E: revealed type: Unknown
f3: Callable[[int], int] = lambda x: 1
reveal_type(f3) # E: revealed type: (int) -> int
f4: Callable[[int], int] = lambda x: reveal_type(x) # E: revealed type: int
f5: Callable[[int], int] = lambda x: x
f6: Callable[[int], int] = lambda x: "foo" # E: `(x: int) -> Literal['foo']` is not assignable to `(int) -> int`
f7: Callable[[int, int], int] = lambda x: 1 # E: `(x: int) -> Literal[1]` is not assignable to `(int, int) -> int`
f8: Callable[[int], int] = lambda x: x + "foo" # E: Argument `Literal['foo']` is not assignable to parameter `value` with type `int`
"#,
);
testcase!(
test_lambda_defaults,
r#"
from typing import reveal_type
f = lambda x, y=1: x + y
reveal_type(f) # E: revealed type: (x: Unknown, y: int = 1) -> Unknown
f(1) # OK, y has default
f(1, 2) # OK
f() # E: Missing argument `x`
g = lambda x, y="hello", z=None: (x, y, z)
reveal_type(g) # E: revealed type: (x: Unknown, y: str = 'hello', z: Unknown | None = None) -> tuple[Unknown, str, Unknown | None]
g(1) # OK
g(1, "world") # OK
g(1, "world", True) # OK, z is `Any | None`
"#,
);
testcase!(
test_lambda_default_promotes_literalstring,
r#"
from typing import Callable
KEYS="ABC"
VALUES="DEF"
x: Callable[[str], str] = lambda key, map=dict(zip(KEYS, VALUES)): map[key]
"#,
);
testcase!(
test_callable_variable_typevar_annotation,
r#"
from typing import Callable, TypeVar, reveal_type
T = TypeVar("T")
f: Callable[[T], T] = lambda x: x
reveal_type(f) # E: revealed type: [T](T) -> T
reveal_type(f(1)) # E: revealed type: int
"#,
);
testcase!(
test_callable_variable_multiple_typevars,
r#"
from typing import Callable, TypeVar, reveal_type
T = TypeVar("T")
U = TypeVar("U")
f: Callable[[T, U], T] = lambda x, y: x
reveal_type(f) # E: revealed type: [T, U](T, U) -> T
reveal_type(f(1, "a")) # E: revealed type: int
"#,
);
testcase!(
test_callable_variable_bounded_typevar,
r#"
from typing import Callable, TypeVar, reveal_type
T = TypeVar("T", bound=int)
f: Callable[[T], T] = lambda x: x
reveal_type(f) # E: revealed type: [T: int](T) -> T
reveal_type(f(1)) # E: revealed type: int
f("hello") # E: `str` is not assignable to upper bound `int` of type variable `T`
"#,
);
testcase!(
test_callable_typevartuple_varargs_homogeneous_tuple,
r#"
from typing import Callable
def test[*Ts](f: Callable[[*tuple[object, ...]], object]) -> Callable[[*Ts], object]:
return f
"#,
);
testcase!(
test_callable_annotation_only_typevar,
TestEnv::one_with_path(
"foo",
"foo.pyi",
r#"
from collections.abc import Callable
from typing import Any, TypeVar
F = TypeVar("F", bound=Callable[..., Any])
require_GET: Callable[[F], F]
"#
),
r#"
from typing import reveal_type
from foo import require_GET
def view() -> None:
return None
reveal_type(require_GET) # E: revealed type: [F: (...) -> Any](F) -> F
reveal_type(require_GET(view)) # E: revealed type: () -> None
"#,
);
testcase!(
test_list_of_callables_variable_typevar_annotation,
r#"
from typing import Callable, TypeVar, assert_type, reveal_type
T = TypeVar("T")
f: list[Callable[[T], T]] = [lambda x: x]
reveal_type(f) # E: revealed type: list[[T](T) -> T]
assert_type(f[0](1), int)
"#,
);
testcase!(
test_callable_of_list_variable_typevar_annotation,
r#"
from typing import Callable, TypeVar, assert_type, reveal_type
T = TypeVar("T")
f: Callable[[list[T]], list[T]] = lambda x: x
reveal_type(f) # E: revealed type: [T](list[T]) -> list[T]
assert_type(f([1]), list[int])
f(1) # E: not assignable
"#,
);
testcase!(
test_callable_returns_callable_variable_typevar_annotation,
r#"
from typing import Callable, TypeVar, assert_type, reveal_type
T = TypeVar("T")
f: Callable[[], Callable[[T], T]] = lambda: lambda x: x
reveal_type(f) # E: revealed type: () -> [T](T) -> T
assert_type(f()(1), int)
"#,
);
testcase!(
test_callable_ellipsis_upper_bound,
r#"
from typing import Callable
def test(f: Callable[[int, str], None]) -> Callable[..., None]:
return f
"#,
);
testcase!(
test_callable_ellipsis_lower_bound,
r#"
from typing import Callable
def test(f: Callable[..., None]) -> Callable[[int, str], None]:
return f
"#,
);
testcase!(
test_callable_invalid_annotation,
r#"
from typing import Callable, assert_type, Any
def test(x: Callable[int]): # E: Expected 2 arguments for `Callable`, got 1
assert_type(x, Callable[..., Any])
"#,
);
testcase!(
test_callable_constructor,
r#"
from typing import Callable, Self, NoReturn
class C1:
def __init__(self, x: str) -> None: pass
class C2:
def __new__(cls, x: str) -> Self:
return super(C2, cls).__new__(cls)
class C3:
def __init__(self, x: str) -> None: pass
def __new__(cls, x: str) -> Self:
return super(C3, cls).__new__(cls)
class C4: pass
class C5:
# The __init__ should be ignored
def __new__(cls, x: int) -> int:
return 1
def __init__(self, x: str) -> None: pass
class C6:
def __new__(cls, *args, **kwargs) -> Self:
return super(C6, cls).__new__(cls)
def __init__(self, x: int) -> None: pass
class CustomMeta(type):
def __call__(cls) -> NoReturn:
raise NotImplementedError("Class not constructable")
class C7(metaclass=CustomMeta):
def __new__(cls, x: int) -> Self:
return super(C7, cls).__new__(cls)
x1: Callable[[], int] = int
x2: Callable[[str], C1] = C1
x3: Callable[[str], C2] = C2
x4: Callable[[str], C3] = C3
x5: Callable[[], C4] = C4
x6: Callable[[int], int] = C5
x7: Callable[[int], C6] = C6
x8: Callable[[], NoReturn] = C7
x9: Callable[[], str] = int # E:
x10: Callable[[], C2] = C2 # E:
x11: Callable[[int], C3] = C3 # E:
x12: Callable[[int], C5] = C5 # E:
"#,
);
testcase!(
test_callable_constructor_unannotated_metaclass_call,
r#"
from typing import Self, Callable
class Meta(type):
# This is unannotated, so we should treat it as compatible and use the signature of __new__
def __call__(cls, x: str):
raise TypeError("Cannot instantiate class")
class MyClass(metaclass=Meta):
def __new__(cls, x: int) -> Self:
return super().__new__(cls)
x1: Callable[[int], MyClass] = MyClass # OK
x2: Callable[[str], MyClass] = MyClass # E: `type[MyClass]` is not assignable to `(str) -> MyClass`
"#,
);
testcase!(
test_callable_unpack,
r#"
from typing import Callable
def test(f: Callable[[bool, *tuple[int, str], bool], None]) -> Callable[[*tuple[bool, int, str, bool]], None]:
return f
"#,
);
testcase!(
test_callable_unpacked_homogeneous_tuple_args,
r#"
from typing import Callable
type VarCallback = Callable[[*tuple[int, ...]], None]
def takes(cb: VarCallback) -> None:
cb(1, 2, 3) # OK: any number of ints
cb("a") # E: Unpacked argument `tuple[Literal['a']]` is not assignable to varargs type `tuple[int, ...]`
def good(*args: int) -> None: ...
def bad(*args: str) -> None: ...
x: VarCallback = good # OK
y: VarCallback = bad # E: `(*args: str) -> None` is not assignable to `(**tuple[int, ...]) -> None`
"#,
);
testcase!(
test_callable_unpack_vararg,
r#"
from typing import Protocol
class P1(Protocol):
def __call__(self, *args: int): ...
class P2(Protocol):
def __call__(self, *args: *tuple[int, int]): ...
class P3(Protocol):
def __call__(self, *args: *tuple[int, str]): ...
class P4(Protocol):
def __call__(self, *args: *tuple[int, ...]): ...
class P5(Protocol):
def __call__(self, x: int, y: int, /): ...
class P6(Protocol):
def __call__(self, x: int, /, *args: *tuple[int]): ...
class P7(Protocol):
def __call__(self, x: int, y: int = 2, /): ...
def test(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7):
x1: P2 = p1
x2: P1 = p2 # E: `P2` is not assignable to `P1`
x3: P2 = p3 # E: `P3` is not assignable to `P2`
x4: P2 = p4
x5: P4 = p2 # E: `P2` is not assignable to `P4`
x6: P5 = p2
x7: P2 = p5
x8: P2 = p6
x9: P6 = p2
x10: P2 = p7
"#,
);
testcase!(
test_callable_unparameterized,
r#"
from typing import Callable, assert_type, Any
def test(f: Callable):
assert_type(f, Callable[..., Any])
"#,
);
testcase!(
test_callable_subtype_vararg_and_positional,
r#"
from typing import Protocol
class P1(Protocol):
def __call__(self, a: int, b: str) -> None: ...
class P2(Protocol):
def __call__(self, *args: int | str) -> None: ...
class P3(Protocol):
def __call__(self, *args: int | str, a: int, b: str) -> None: ...
class P4(Protocol):
def __call__(self, *args: int | str, a: int = 1, b: str = "") -> None: ...
def test(p2: P2, p3: P3, p4: P4):
# this one doesn't work because a/b can be passed by name
x1: P1 = p2 # E: `P2` is not assignable to `P1`
# this one doesn't work because a/b isn't always passed by name
x2: P1 = p3 # E: `P3` is not assignable to `P1`
x3: P1 = p4 # OK
"#,
);
testcase!(
test_callable_annot_too_few_args,
r#"
from typing import Callable
def test(f: Callable[[int], None]):
f() # E: Expected 1 more positional argument
"#,
);
testcase!(
test_callable_annot_too_many_args,
r#"
from typing import Callable
def test(f: Callable[[], None]):
f(
1, # E: Expected 0 positional arguments
2
)
"#,
);
testcase!(
test_callable_annot_keyword_args,
r#"
from typing import Callable
def test(f: Callable[[], None]):
f(
x=1, # E: Unexpected keyword argument `x`
y="hello" # E: Unexpected keyword argument `y`
)
"#,
);
testcase!(
test_callable_ellipsis_keyword_args,
r#"
from typing import Callable
def test(f: Callable[..., None]):
f(x=1, y="hello") # OK
"#,
);
testcase!(
test_callable_annot_upper_bound,
r#"
from typing import Callable
def test(f: Callable[[int, int], None]) -> None: ...
def f1(x: int, y: int) -> None: ...
test(f1) # OK
# Lower bound has too many args
def f2(x: int, y: int, z: int) -> None: ...
test(f2) # E: Argument `(x: int, y: int, z: int) -> None` is not assignable to parameter `f` with type `(int, int) -> None`
# Lower bound has too few args
def f3(x: int) -> None: ...
test(f3) # E: Argument `(x: int) -> None` is not assignable to parameter `f` with type `(int, int) -> None`
# Lower bound has wrong arg types
def f4(x: str, y: int) -> None: ...
test(f4) # E: Argument `(x: str, y: int) -> None` is not assignable to parameter `f` with type `(int, int) -> None`
# Lower bound has variadic args of compatible type
def f5(*args: int) -> None: ...
test(f5) # OK
# Lower bound has variadic args of incompatible type
def f6(*args: str) -> None: ...
test(f6) # E: Argument `(*args: str) -> None` is not assignable to parameter `f` with type `(int, int) -> None`
# Lower bound has extra kwargs of arbitrary type
class Arbitrary: pass
def f7(x: int, y: int, **kwargs: Arbitrary) -> None: ...
test(f7) # OK
# Lower bound has extra args with defaults
def f7(x: int, y: int, z: int = 0) -> None: ...
test(f7) # OK
"#,
);
testcase!(
test_positional_param_keyword_arg,
r#"
def test(x: int, y: str): ...
test(1, "hello") # OK
test(x=1, y="hello") # OK
test(y="hello", x=1) # OK
test(1, y="hello") # OK
test(1) # E: Missing argument `y`
test(1, "hello", x=2) # E: Multiple values for argument `x`
"#,
);
testcase!(
test_positional_only_params,
r#"
def test(x: int, y: str, /): ...
test(1, "hello") # OK
test(1) # E: Missing positional argument `y`
test(1, y="hello") # E: Expected argument `y` to be positional
test(1, "hello", 2) # E: Expected 2 positional arguments, got 3
"#,
);
testcase!(
test_historical_positional_only_params,
r#"
def f1(__x: str): ...
f1("hello") # OK
f1(__x="hello") # E: Expected argument `__x` to be positional
def f2(__x: str, /, __y: str, __z__: str): ...
f2(__x="hello", __y="my", __z__="world") # E: Expected argument `__x` to be positional
f2("hello", __y="my", __z__="world") # OK
def f3(__x: str, *, __y__: str, __z: str): ...
f3(__x="hello", __y__="my", __z="world") # OK
def f4(x: str, __y: str): ... # E: Positional-only parameter `__y` cannot appear after keyword parameters
class C:
def f5(self, __x: str): ...
def f6(self, x: str, __y: str): ... # E: Positional-only parameter `__y` cannot appear after keyword parameters
@classmethod
def f7(cls, __x: str): ...
c = C()
c.f5("hello") # OK
c.f5(__x="hello") # E: Expected argument `__x` to be positional
C.f7("hello") # OK
C.f7(__x="hello") # E: Expected argument `__x` to be positional
"#,
);
testcase!(
test_keyword_only_params,
r#"
def test(*, x: int, y: str): ...
test(x=1, y="hello") # OK
test(1, "hello") # E: Expected argument `x` to be passed by name # E: Expected argument `y` to be passed by name
test(x=1) # E: Missing argument `y`
test(y="hello") # E: Missing argument `x`
"#,
);
testcase!(
test_extra_positional_args,
r#"
def test(*, x: int): ...
test(1, 2) # E: Expected argument `x` to be passed by name # E: Expected 0 positional arguments, got 2
"#,
);
testcase!(
test_missing_self_and_kwonly,
r#"
class A:
def f(*, x): ...
A().f(1) # E: Expected argument `x` to be passed by name # E: Expected 0 positional arguments, got 2 (including implicit `self`)
"#,
);
testcase!(
test_varargs,
r#"
def test(*args: int): ...
test(1, 2, "foo", 4) # E: Argument `Literal['foo']` is not assignable to parameter `*args` with type `int`
"#,
);
testcase!(
test_kwargs,
r#"
def test(**kwargs: int): ...
test(x=1, y="foo", z=2) # E: Keyword argument `y` with type `Literal['foo']` is not assignable to parameter `**kwargs` with type `int` in function `test`
"#,
);
testcase!(
test_args_kwargs_type,
r#"
from typing import assert_type
def test(*args: int, **kwargs: int) -> None:
assert_type(args, tuple[int, ...])
assert_type(kwargs, dict[str, int])
"#,
);
testcase!(
test_defaults,
r#"
def test(x: int, y: int = 0, z: str = ""): ...
test() # E: Missing argument `x`
test(0, 1) # OK
test(0, 1, "foo") # OK
test(0, 1, "foo", 2) # E: Expected 3 positional arguments
"#,
);
testcase!(
test_defaults_posonly,
r#"
def test(x: int, y: int = 0, z: str = "", /): ...
test() # E: Missing positional argument `x`
test(0, 1) # OK
test(0, 1, "foo") # OK
test(0, 1, "foo", 2) # E: Expected 3 positional arguments
"#,
);
testcase!(
test_bad_default,
r#"
def f(x: int = ""): # E: Default `Literal['']` is not assignable to parameter `x` with type `int`
pass
"#,
);
testcase!(
test_infer_param_type_from_default,
r#"
from typing import Any, assert_type
def f(x, y = "", z = None):
assert_type(x, Any)
assert_type(y, Any | str)
assert_type(z, Any | None)
"#,
);
testcase!(
test_default_ellipsis,
r#"
def stub(x: int = ...): ... # OK
def err(x: int = ...): pass # E: Default `EllipsisType` is not assignable to parameter `x` with type `int`
"#,
);
testcase!(
test_default_value_checked_against_annotation,
r#"
def f1(x: int = 0) -> None: ... # OK
def f2(x: int | None = None) -> None: ... # OK
def f3(x: int = 0.0) -> None: ... # E: Default `float` is not assignable to parameter `x` with type `int`
def f4(x: int = None) -> None: ... # E: Default `None` is not assignable to parameter `x` with type `int`
"#,
);
testcase!(
test_splat_tuple,
r#"
def test(x: int, y: int, z: int): ...
test(*(1, 2, 3)) # OK
test(*(1, 2)) # E: Missing argument `z`
test(*(1, 2, 3, 4)) # E: Expected 3 positional arguments, got 4
"#,
);
testcase!(
test_splat_iterable,
r#"
def test(x: int, y: int, z: int): ...
test(*[1, 2, 3]) # OK
test(*[1, 2]) # E: Missing argument `z`
test(*[1, 2, 3, 4]) # E: Expected 3 positional arguments, got 4
test(*[1], 2) # E: Missing argument `z`
test(1, 2, 3, *[4]) # E: Expected 3 positional arguments, got 4
"#,
);
testcase!(
test_splat_list_literal_with_keyword,
r#"
def fun1(a):
return
def fun2(a, b):
return
fun1(*[]) # E: Missing argument `a`
fun1(*[""]) # OK
fun2(*[""], b=None) # OK
fun2(*["", ""]) # OK
fun2(*[""]) # E: Missing argument `b`
fun2(*["", "", ""]) # E: Expected 2 positional arguments, got 3
"#,
);
testcase!(
test_splat_set_literal_with_keyword,
r#"
def fun1(a):
return
def fun2(a, b):
return
fun1(*{""}) # OK
fun2(*{""}, b=None) # OK
fun2(*{"1", "2"}) # OK - note: set deduplicates at runtime, but type checker uses literal count
fun2(*{""}) # E: Missing argument `b`
fun2(*{"1", "2", "3"}) # E: Expected 2 positional arguments, got 3
"#,
);
testcase!(
test_splat_unknown_length_with_keyword,
r#"
def fun(a: str, b: str, c: int):
return
def test(xs: list[str]):
# Unknown-length star args should stop consuming positional params
# when reaching a parameter that has a keyword argument.
fun(*xs, b="", c=1) # OK
fun(*xs, c=1) # OK
fun(*xs) # E:
"#,
);
testcase!(
test_splat_unpacked_args,
r#"
from typing import assert_type
def test1(*args: *tuple[int, int, int]): ...
test1(*(1, 2, 3)) # OK
test1(*(1, 2)) # E: Expected 1 more positional argument in function `test1`
test1(*(1, 2, 3, 4)) # E: Expected 3 positional arguments, got 4 in function `test1`
def test2[*T](*args: *tuple[int, *T, int]) -> tuple[*T]: ...
assert_type(test2(*(1, 2, 3)), tuple[int])
assert_type(test2(*(1, 2)), tuple[()])
assert_type(test2(*(1, 2, 3, 4)), tuple[int, int])
assert_type(test2(1, 2, *(3, 4), 5), tuple[int, int, int])
assert_type(test2(1, *(2, 3), *("4", 5)), tuple[int, int, str])
assert_type(test2(1, *[2, 3], 4), tuple[int, int])
test2(1, *(2, 3), *(4, "5")) # E: Unpacked argument `tuple[Literal[1], Literal[2], Literal[3], Literal[4], Literal['5']]` is not assignable to parameter `*args` with type `tuple[int, *@_, int]` in function `test2`
"#,
);
// Splatting a tuple with a variadic middle preserves the positions of its fixed ends.
// See https://github.com/facebook/pyrefly/issues/4482
testcase!(
test_splat_unpacked_args_shape,
r#"
class P: ...
class V: ...
class S: ...
def f(a1: P, a2: P, /, *args: *tuple[*tuple[V, ...], S, S]) -> None: ...
def test(
p: P,
v: tuple[V, ...],
s: S,
vs: tuple[*tuple[V, ...], S],
pv: tuple[P, *tuple[V, ...]],
) -> None:
# Reassembles to exactly the `*args` type.
f(p, p, *vs, s)
# `a2` sees the prefix element `P`, not `P | V`.
f(p, *pv, s, s)
# All 45 ways to parenthesize the arguments are equivalent.
f(p, p, *v, s, s)
f(p, p, *v, *(s, s))
f(p, p, *(*v, s), s)
f(p, p, *(*v, s, s))
f(p, p, *(*v, *(s, s)))
f(p, p, *(*(*v, s), s))
f(p, *(p, *v), s, s)
f(p, *(p, *v), *(s, s))
f(p, *(p, *v, s), s)
f(p, *(p, *(*v, s)), s)
f(p, *(*(p, *v), s), s)
f(p, *(p, *v, s, s))
f(p, *(p, *v, *(s, s)))
f(p, *(p, *(*v, s), s))
f(p, *(p, *(*v, s, s)))
f(p, *(p, *(*v, *(s, s))))
f(p, *(p, *(*(*v, s), s)))
f(p, *(*(p, *v), s, s))
f(p, *(*(p, *v), *(s, s)))
f(p, *(*(p, *v, s), s))
f(p, *(*(p, *(*v, s)), s))
f(p, *(*(*(p, *v), s), s))
f(*(p, p), *v, s, s)
f(*(p, p), *v, *(s, s))
f(*(p, p), *(*v, s), s)
f(*(p, p), *(*v, s, s))
f(*(p, p), *(*v, *(s, s)))
f(*(p, p), *(*(*v, s), s))
f(*(p, p, *v), s, s)
f(*(p, *(p, *v)), s, s)
f(*(*(p, p), *v), s, s)
f(*(p, p, *v), *(s, s))
f(*(p, *(p, *v)), *(s, s))
f(*(*(p, p), *v), *(s, s))
f(*(p, p, *v, s), s)
f(*(p, p, *(*v, s)), s)
f(*(p, *(p, *v), s), s)
f(*(p, *(p, *v, s)), s)
f(*(p, *(p, *(*v, s))), s)
f(*(p, *(*(p, *v), s)), s)
f(*(*(p, p), *v, s), s)
f(*(*(p, p), *(*v, s)), s)
f(*(*(p, p, *v), s), s)
f(*(*(p, *(p, *v)), s), s)
f(*(*(*(p, p), *v), s), s)
"#,
);
// Against ordinary positional parameters, only positions past the prefix widen.
testcase!(
test_splat_unbounded_middle_against_positional,
r#"
def f(a: int, b: str, c: bytes) -> None: ...
def test(
prefix: tuple[int, *tuple[str, ...]],
suffix: tuple[*tuple[int, ...], bytes],
) -> None:
# `a` gets the prefix element exactly; `b` and `c` draw from the middle.
f(*prefix) # E: Argument `str` is not assignable to parameter `c` with type `bytes`
# No prefix, so which parameter the `bytes` reaches depends on the middle's length.
f(*suffix) # E: Argument `bytes | int` is not assignable to parameter `a` with type `int` # E: Argument `bytes | int` is not assignable to parameter `b` with type `str` # E: Argument `bytes | int` is not assignable to parameter `c` with type `bytes`
"#,
);
// Variadic parameter takes the whole remainder, so every element must be assignable to it.
testcase!(
test_splat_variadic_checks_whole_remainder,
r#"
def f(*args: int) -> None: ...
def test(
mixed: tuple[int, *tuple[str, ...]],
none: tuple[str, *tuple[bytes, ...]],
) -> None:
f(*mixed) # E: Argument `int | str` is not assignable to parameter `*args` with type `int`
f(*none) # E: Argument `bytes | str` is not assignable to parameter `*args` with type `int`
"#,
);
testcase!(
test_splat_union,
r#"
from typing import Iterable
def test(x: int, y: int, z: int): ...
def fixed_same_len_ok(xs: tuple[int, int, int] | tuple[int, int, int]):
test(*xs) # OK
def fixed_same_len_type_err(xs: tuple[int, int, int] | tuple[int, int, str]):
test(*xs) # E: Argument `int | str` is not assignable to parameter `z` with type `int`
def fixed_same_len_too_few(xs: tuple[int, int] | tuple[int, int]):
test(*xs) # E: Missing argument `z`
def fixed_diff_len(xs: tuple[int, int] | tuple[int, int, int]):
test(*xs) # OK (treated as Iterable[int])
def mixed_same_type(xs: tuple[int, int] | Iterable[int]):
test(*xs) # OK (treated as Iterable[int])
def mixed_type_err(xs: tuple[int, int] | Iterable[str]):
test(*xs) # E: Argument `int | str` is not assignable to parameter `x` with type `int` # E: Argument `int | str` is not assignable to parameter `y` with type `int` # E: Argument `int | str` is not assignable to parameter `z` with type `int`
"#,
);
// Normally, positional arguments can not come after keyword arguments. Splat args are an
// exception. However, splat args are still evaluated first, so they consume positional params
// before any keyword arguments.
// See https://github.com/python/cpython/issues/104007
testcase!(
test_splat_keyword_first,
r#"
def test(x: str, y: int, z: int): ...
test(x="", *(0, 1)) # E: Argument `Literal[0]` is not assignable to parameter `x` with type `str` # E: Multiple values for argument `x` # E: Missing argument `z`
"#,
);
testcase!(
test_splat_kwargs,
r#"
def f(x: int, y: int, z: int): ...
def test(kwargs: dict[str, int]):
f(**kwargs) # OK
f(1, **kwargs) # OK
"#,
);
testcase!(
test_splat_unknown_length_with_known_kwargs_keys,
r#"
from typing import Any
def get_content(
service_instance: Any,
obj_type: str,
property_list: list[str] | None = None,
container_ref: Any = None,
) -> dict[str, Any]:
return {}
def call_get_content(instance: Any, obj_type: str) -> dict[str, Any]:
args: list[Any] = [instance, obj_type]
kwargs = {
"property_list": ["name"],
"container_ref": None,
}
return get_content(*args, **kwargs) # OK
"#,
);
testcase!(
test_splat_kwargs_mixed_with_keywords,
r#"
def f(x: str, y: int, z: int): ...
def test(kwargs: dict[str, int]):
f("foo", **kwargs) # OK
f(x="foo", **kwargs) # OK
f(**kwargs) # E: Unpacked keyword argument `int` is not assignable to parameter `x` with type `str` in function `f`
"#,
);
testcase!(
test_splat_kwargs_multi,
r#"
def f(x: int, y: int, z: int): ...
def test(kwargs1: dict[str, int], kwargs2: dict[str, str]):
f(**kwargs1, **kwargs2) # E: Unpacked keyword argument `str` is not assignable to parameter `x` with type `int` in function `f` # E: Unpacked keyword argument `str` is not assignable to parameter `y` with type `int` in function `f` # E: Unpacked keyword argument `str` is not assignable to parameter `z` with type `int` in function `f`
"#,
);
testcase!(
test_splat_kwargs_mapping,
r#"
from typing import Mapping
def f(x: int, y: int, z: int): ...
def test(kwargs: Mapping[str, int]):
f(**kwargs) # OK
"#,
);
testcase!(
test_splat_kwargs_subclass,
r#"
class Counter[T](dict[T, int]): ...
def f(**kwargs: int): ...
def test(c: Counter[str]):
f(**c)
"#,
);
testcase!(
test_splat_kwargs_wrong_key,
r#"
def f(x: int): ...
def test(kwargs: dict[int, str]):
f(**kwargs) # E: Expected argument after ** to have `str` keys, got: int # E: Missing argument `x`
"#,
);
testcase!(
test_splat_kwargs_to_kwargs_param,
r#"
def f(**kwargs: int): ...
def g(**kwargs: str): ...
def test(kwargs: dict[str, int]):
f(**kwargs) # OK
g(**kwargs) # E: Unpacked keyword argument `int` is not assignable to parameter `**kwargs` with type `str` in function `g`
"#,
);
testcase!(
test_callable_async,
r#"
from typing import Any, Awaitable, Callable, Coroutine
async def f(x: int) -> int: ...
def test_corountine() -> Callable[[int], Coroutine[Any, Any, int]]:
return f
def test_awaitable() -> Callable[[int], Awaitable[int]]:
return f
def test_sync() -> Callable[[int], int]:
return f # E: Returned type `(x: int) -> Coroutine[Unknown, Unknown, int]` is not assignable to declared return type `(int) -> int`
"#,
);
testcase!(
test_assignability_both_typed_dicts,
r#"
from typing import TypedDict, Unpack, Protocol
class TD1(TypedDict):
x: int
class TD2(TypedDict):
x: int
y: int
class P1(Protocol):
def __call__(self, **kwargs: Unpack[TD1]): ...
class P2(Protocol):
def __call__(self, **kwargs: Unpack[TD2]): ...
def test(accept_td1: P1, accept_td2: P2):
a: P1 = accept_td2 # E: `P2` is not assignable to `P1`
b: P2 = accept_td1
"#,
);
testcase!(
test_assignability_one_typed_dict,
r#"
from typing import TypedDict, Unpack, NotRequired, Protocol
class TD(TypedDict):
string: str
number: NotRequired[int]
class P1(Protocol):
def __call__(self, **kwargs: Unpack[TD]): ...
class P2(Protocol):
def __call__(self, *, string: str, number: int = ...): ...
class P3(Protocol):
def __call__(self, string: str, number: int = ...): ...
def test(accept_td: P1, kwonly_args: P2, regular_args: P3):
a: P2 = accept_td
b: P3 = accept_td # E: `P1` is not assignable to `P3`
c: P1 = kwonly_args # E: `P2` is not assignable to `P1`
d: P1 = regular_args # E: `P3` is not assignable to `P1`
"#,
);
testcase!(
test_assignability_typed_dict_and_regular_kwargs,
r#"
from typing import TypedDict, Unpack, NotRequired, Protocol
class TD(TypedDict):
string: str
number: NotRequired[int]
class P1(Protocol):
def __call__(self, **kwargs): ...
class P2(Protocol):
def __call__(self, **kwargs: Unpack[TD]): ...
class P3(Protocol):
def __call__(self, **kwargs: int | str): ...
class P4(Protocol):
def __call__(self, **kwargs: int): ...
def test(unannotated: P1, unpacked: P2, annotated: P3, annotated_wrong: P4):
a: P2 = unannotated
b: P2 = annotated
c: P2 = annotated_wrong # E: `P4` is not assignable to `P2`
"#,
);
testcase!(
test_assignability_typed_dict_wrong_kwarg,
r#"
from typing import TypedDict, Protocol, Required, NotRequired, Unpack
class TD(TypedDict):
v1: Required[int]
v2: NotRequired[str]
v3: Required[str]
def func1(**kwargs: Unpack[TD]) -> None: ...
class P1(Protocol):
def __call__(self, *, v1: int, v2: int, v3: str) -> None:...
class P2(Protocol):
def __call__(self, *, v1: int) -> None: ...
class P3(Protocol):
def __call__(self, *, v1: int, v2: str, v4: str) -> None: ...
x: P1 = func1 # E: `(**kwargs: Unpack[TD]) -> None` is not assignable to `P1`
y: P2 = func1 # E: `(**kwargs: Unpack[TD]) -> None` is not assignable to `P2`
z: P3 = func1 # E: `(**kwargs: Unpack[TD]) -> None` is not assignable to `P3`
"#,
);
testcase!(
test_assignability_unpack_kwargs_to_regular_kwargs,
r#"
from typing import TypedDict, Unpack, Protocol
class TD(TypedDict):
x: int
class Untyped(Protocol):
def __call__(self, **kwargs) -> None: ...
class Traditional(Protocol):
def __call__(self, **kwargs: int) -> None: ...
def src(**kwargs: Unpack[TD]) -> None: ...
# An `Unpack[TypedDict]` source is not assignable to an untyped or traditionally
# typed `**kwargs` destination, because traditional kwargs are not checked for
# keyword names and could be called with keys the TypedDict does not permit.
a: Untyped = src # E: `(**kwargs: Unpack[TD]) -> None` is not assignable to `Untyped`
b: Traditional = src # E: `(**kwargs: Unpack[TD]) -> None` is not assignable to `Traditional`
"#,
);
testcase!(
test_forwarding_unpack_kwargs_to_fixed_signature,
TestEnv::new().enable_open_unpacking_error(),
r#"
from typing import Never, TypedDict, Unpack
class Open(TypedDict):
name: str
class Closed(TypedDict, closed=True):
name: str
class ExtraNever(TypedDict, extra_items=Never):
name: str
def has_kwargs(**kwargs: Unpack[Open]) -> None: ...
def takes_name(name: str) -> None: ...
def forward_open(**kwargs: Unpack[Open]) -> None:
has_kwargs(**kwargs) # OK: target accepts **kwargs
takes_name(**kwargs) # E: `Open` may contain extra items of type `object`, which cannot be unpacked into a callable that accepts no extra keyword arguments
def forward_closed(**kwargs: Unpack[Closed]) -> None:
takes_name(**kwargs) # OK: closed TypedDict has no extra keys
def forward_extra_never(**kwargs: Unpack[ExtraNever]) -> None:
takes_name(**kwargs) # OK: `extra_items=Never` means the same as `closed=True`
"#,
);
testcase!(
test_unpacking_open_typed_dict_into_call,
TestEnv::new().enable_open_unpacking_error(),
r#"
from typing import TypedDict, Unpack
class Open(TypedDict):
name: str
class Closed(TypedDict, closed=True):
name: str
def takes_name(name: str) -> None: ...
def takes_closed(**kwargs: Unpack[Closed]) -> None: ...
def takes_untyped_kwargs(name: str, **kwargs) -> None: ...
def takes_object_kwargs(name: str, **kwargs: object) -> None: ...
def takes_str_kwargs(name: str, **kwargs: str) -> None: ...
def f(open: Open, closed: Closed) -> None:
takes_name(**open) # E: `Open` may contain extra items of type `object`, which cannot be unpacked into a callable that accepts no extra keyword arguments
takes_closed(**open) # E: `Open` may contain extra items of type `object`, which cannot be unpacked into a callable that accepts no extra keyword arguments
takes_str_kwargs(**open) # E: Extra items of type `object` are not assignable to parameter `kwargs` with type `str`
takes_untyped_kwargs(**open) # OK
takes_object_kwargs(**open) # OK
takes_closed(**closed) # OK
takes_closed(bogus=1) # E: Missing argument `name` # E: Unexpected keyword argument `bogus`
"#,
);
// An open TypedDict's extra items are only speculative, so they are reported under the opt-in
// `open-unpacking` kind. Extra items declared with `extra_items` are always reported.
testcase!(
test_unpacking_open_typed_dict_into_call_without_open_unpacking,
r#"
from typing import TypedDict, Unpack
class Open(TypedDict):
name: str
class ExtraInt(TypedDict, extra_items=int):
name: str
def takes_name(name: str) -> None: ...
def takes_str_kwargs(name: str, **kwargs: str) -> None: ...
def f(open: Open, extra_int: ExtraInt) -> None:
takes_name(**open) # OK
takes_str_kwargs(**open) # OK
takes_name(**extra_int) # E: `ExtraInt` may contain extra items of type `int`, which cannot be unpacked into a callable that accepts no extra keyword arguments
takes_str_kwargs(**extra_int) # E: Extra items of type `int` are not assignable to parameter `kwargs` with type `str`
"#,
);
testcase!(
test_unpacking_typed_dict_extra_items_into_call,
r#"
from typing import TypedDict, Unpack
class Closed(TypedDict, closed=True):
name: str
class ExtraInt(TypedDict, extra_items=int):
name: str
def takes_closed(**kwargs: Unpack[Closed]) -> None: ...
def takes_int_kwargs(name: str, **kwargs: int) -> None: ...
def takes_label(name: str, *, label: str = "", **kwargs: int) -> None: ...
def takes_count(name: str, *, count: int = 0, **kwargs: int) -> None: ...
def takes_other(name: str, *, other: str, **kwargs: int) -> None: ...
def f(extra_int: ExtraInt) -> None:
takes_closed(**extra_int) # E: `ExtraInt` may contain extra items of type `int`, which cannot be unpacked into a callable that accepts no extra keyword arguments
takes_int_kwargs(**extra_int) # OK
takes_count(**extra_int) # OK
takes_label(**extra_int) # E: Extra items of type `int` are not assignable to parameter `label` with type `str`
# Extra items don't excuse a missing required argument.
takes_other(**extra_int) # E: Extra items of type `int` are not assignable to parameter `other` with type `str`
"#,
);
testcase!(
test_unpacking_generic_typed_dict_extra_items_into_call,
r#"
from typing import Never, TypedDict
class Extra[T](TypedDict, extra_items=T):
name: str
class IntExtra(Extra[int]):
pass
def takes_name(name: str) -> None: ...
def takes_str_kwargs(name: str, **kwargs: str) -> None: ...
def f(strings: Extra[str], ints: Extra[int], inherited: IntExtra, closed: Extra[Never]) -> None:
takes_str_kwargs(**strings) # OK
takes_str_kwargs(**ints) # E: Extra items of type `int` are not assignable to parameter `kwargs` with type `str`
takes_str_kwargs(**inherited) # E: Extra items of type `int` are not assignable to parameter `kwargs` with type `str`
takes_name(**closed) # OK: the instantiated extra-items type is `Never`
"#,
);
testcase!(
test_typed_dict_extra_items_can_splat_into_kwonly_args,
r#"
from typing import TypedDict
class OpenTD(TypedDict): ...
class ExtraItemsTD(TypedDict, extra_items=int): ...
class ClosedTD(TypedDict, closed=True): ...
def f1(*, x: int, **kwargs): ...
def g1(open_td: OpenTD, extra_items_td: ExtraItemsTD, closed_td: ClosedTD):
# Technically, a subclass of `OpenTD` could declare `x`. But it's much more likely that this is
# an error.
f1(**open_td) # E: Missing argument `x`
f1(**extra_items_td) # ok, `x` could be an extra item
f1(**closed_td) # E: Missing argument `x`
def f2(*, x: str, **kwargs): ...
def g2(extra_items_td: ExtraItemsTD):
f2(**extra_items_td) # E: Extra items of type `int` are not assignable to parameter `x` with type `str`
"#,
);
testcase!(
test_unpacking_multiple_typed_dict_extra_items_into_call,
r#"
from typing import NotRequired, TypedDict
class ExtraInt(TypedDict, extra_items=int):
pass
class MaybeLabel(TypedDict, closed=True):
label: NotRequired[str]
def takes_label(*, label: str = "", **kwargs: int) -> None: ...
def f(extra: ExtraInt, maybe_label: MaybeLabel) -> None:
takes_label(**extra, **maybe_label) # E: Extra items of type `int` are not assignable to parameter `label` with type `str`
"#,
);
testcase!(
test_unpacking_mapping_onto_not_required_field,
r#"
from typing import NotRequired, TypedDict
class Opts(TypedDict, closed=True):
verbose: NotRequired[bool]
def takes_verbose(*, verbose: bool = False) -> None: ...
def f(opts: Opts, extra: dict[str, str]) -> None:
# `verbose` may be absent from `opts` at runtime, so `extra` may supply it instead.
takes_verbose(**opts, **extra) # E: Unpacked keyword argument `str` is not assignable to parameter `verbose` with type `bool`
"#,
);
testcase!(
test_unpacking_typed_dict_extra_items_onto_own_declared_key,
r#"
from typing import NotRequired, TypedDict
class ExtraInt(TypedDict, extra_items=int):
label: NotRequired[str]
def takes_label(*, label: str = "", **kwargs: int) -> None: ...
def f(extra: ExtraInt) -> None:
# `label` is declared by `ExtraInt`, so its extra items cannot land on that parameter
# even though the field is NotRequired.
takes_label(**extra) # OK
"#,
);
testcase!(
test_function_vs_callable,
r#"
from typing import assert_type, Callable
def f(x: int) -> int:
return x
# This assertion (correctly) fails because x is a positional parameter rather than a positional-only one.
# This test verifies that we produce a sensible error message that shows the mismatch.
assert_type(f, Callable[[int], int]) # E: assert_type((x: int) -> int, (int) -> int) failed
"#,
);
testcase!(
test_function_name_in_error,
TestEnv::one("foo", "def f(x: int): ..."),
r#"
import foo
foo.f("") # E: in function `foo.f`
def f(x: int): ...
f("") # E: in function `f`
class A:
def f(self, x: int): ...
@classmethod
def g(cls, x: int): ...
@staticmethod
def h(x: int): ...
A().f("") # E: in function `A.f`
A.f(A(), "") # E: in function `A.f`
A.g("") # E: in function `A.g`
A.h("") # E: in function `A.h`
class B(A):
pass
B().f("") # E: in function `A.f`
"#,
);
testcase!(
test_args_kwargs_assignment,
r#"
from typing import TypedDict, Unpack
def test1(*cmd: str, **keywords: str) -> None:
cmd = ("mycmd",)
cmd = (1,) # E: `tuple[Literal[1]]` is not assignable to variable `cmd` with type `tuple[str, ...]`
keywords = {"key": "value"}
keywords = {"key": 0} # E: `Literal[0]` is not assignable to dict value type `str`
class MyDict(TypedDict):
x: int
y: int
def test2(my_dict: MyDict, *cmd: *tuple[str, str], **keywords: Unpack[MyDict]) -> None:
cmd = ("mycmd", "mycmd2")
cmd = ("mycmd",) # E: `tuple[Literal['mycmd']]` is not assignable to variable `cmd` with type `tuple[str, str]`
keywords = my_dict
keywords = { "x": 1 } # E: Missing required key `y` for TypedDict `MyDict`
"#,
);
testcase!(
test_never_callable,
r#"
from typing import Never
def f(x: Never) -> Never:
return x()
"#,
);
testcase!(
test_param_matching_rhs_empty,
r#"
from typing import Callable
def foo(f: Callable[[], None]) -> None: ...
def optional_pos_only_ok(x: int = 0, /) -> None: ...
foo(optional_pos_only_ok)
def optional_pos_ok(x: int = 0) -> None: ...
foo(optional_pos_ok)
def optional_kw_only_ok(*, x: int = 0) -> None: ...
foo(optional_kw_only_ok)
def optional_all_default_ok(x: int = 0, /, y: int = 1, *, z: int = 2) -> None: ...
foo(optional_all_default_ok)
def varargs_ok(*args: int) -> None: ...
foo(varargs_ok)
def kwargs_ok(**kwargs: int) -> None: ...
foo(kwargs_ok)
def varargs_kwargs_ok(*args: int, **kwargs: int) -> None: ...
foo(varargs_kwargs_ok)
def varargs_bad(*args: int, x: int) -> None: ...
foo(varargs_bad) # E: not assignable to parameter `f`
def varargs_kwargs_bad(*args: int, x: int, **kwargs: int) -> None: ...
foo(varargs_kwargs_bad) # E: not assignable to parameter `f`
"#,
);
testcase!(
test_callable_class,
r#"
from typing import Callable
class C:
def __call__(self, x: int) -> int:
return 1
def test(cls: C):
x: Callable[[int], int] = cls
"#,
);
testcase!(
test_callable_class_functools_partial,
r#"
from __future__ import annotations
from functools import partial
from typing import Callable, Match
def bar(a: Match[str], b: int) -> str:
return f'{a}{b}'
def zoo(a: Callable[[Match[str]], str]) -> None:
return None
zoo(partial(bar, b=99))
"#,
);
testcase!(
bug = "Self in Metaclass should be treated as Any. Any in metaclass call should act like no annot.",
test_callable_class_substitute_self,
r#"
from typing import Any, Callable, Self, assert_type
def ret[T](f: Callable[[], T]) -> T: ...
class Meta(type):
def __call__(self, *args, **kwargs) -> Self: ... # E: `Self` cannot be used in a metaclass
# metaclass __call__
class A(metaclass=Meta):
pass
# __new__
class B:
def __new__(cls, *args, **kwargs) -> Self: ...
# __init__
class C:
def __init__(self, *args, **kwargs) -> None: ...
assert_type(ret(A), A) # TODO # E: assert_type(type[A], A) failed
assert_type(ret(B), B)
assert_type(ret(C), C)
"#,
);
testcase!(
test_callable_class_self_confusion,
r#"
from typing import Callable, Self, assert_type
class A:
def __new__(cls) -> Self: ...
class B[T]:
def __new__(self, f: Callable[[], T]) -> Self: ...
assert_type(B(A), B[A])
"#,
);
testcase!(
test_call_self,
r#"
from typing import assert_type
class Foo:
def __call__(self, a: int) -> int:
return a
def bar(self, b: int) -> None:
assert_type(self(b), int)
"#,
);
testcase!(
test_ellipsis_body,
TestEnv::new().enable_empty_body_error(),
r#"
from typing import TYPE_CHECKING, Protocol, assert_type, overload
from abc import abstractmethod
def f(): ...
def g() -> None: ...
def h() -> int | None: ...
def i() -> str: ... # E: Function body cannot consist only of `...` when the return type is not `None`
async def j() -> None: ...
async def k() -> str: ... # E: Function body cannot consist only of `...` when the return type is not `None`
if TYPE_CHECKING:
def tc() -> str: ...
DOCS_BUILDING = False
if TYPE_CHECKING or DOCS_BUILDING:
def tc_or_docs() -> str: ... # E: Function body cannot consist only of `...` when the return type is not `None`
if not TYPE_CHECKING:
pass
else:
def tc_else() -> str: ...
class P(Protocol):
def m(self) -> str: ...
class A:
@abstractmethod
def m(self) -> str: ...
@overload
def ov(x: int) -> int: ...
@overload
def ov(x: str) -> str: ...
def ov(x: int | str) -> int | str:
return x
assert_type(f(), None)
assert_type(g(), None)
"#,
);
testcase!(
test_ellipsis_body_in_pyi,
TestEnv::one_with_path("foo", "foo.pyi", "def f() -> int: ...").enable_empty_body_error(),
r#"
from typing import assert_type
from foo import f
assert_type(f(), int)
"#,
);
testcase!(
test_ellipsis_body_type_checking_guards,
TestEnv::new().enable_empty_body_error(),
r#"
import typing
from typing import TYPE_CHECKING
# `typing.TYPE_CHECKING` is a valid guard.
if typing.TYPE_CHECKING:
def a() -> str: ...
# `TYPE_CHECKING is False` / `TYPE_CHECKING == False` make the `else` type-checking-only.
if TYPE_CHECKING is False:
pass
else:
def b() -> str: ...
if TYPE_CHECKING == False: # noqa
pass
else:
def c() -> str: ...
# An unrelated attribute named `TYPE_CHECKING` is not a guard.
class NotTyping:
TYPE_CHECKING = True
nt = NotTyping()
if nt.TYPE_CHECKING:
def d() -> str: ... # E: Function body cannot consist only of `...` when the return type is not `None`
"#,
);
testcase!(
test_posonly_kwargs_duplicate_ok,
r#"
def f(x: int, /, **kwargs: str):
pass
f(0, x="1")
"#,
);
testcase!(
test_not_a_class_object,
r#"
isinstance(1, "not a class object") # E: Expected class object
issubclass(str, "not a class object") # E: Expected class object
"#,
);
testcase!(
test_generic_function_is_callable,
r#"
from typing import Any, Callable
def f[T](*, x: T) -> T:
return x
def g(f: Callable[..., Any]):
pass
g(f)
"#,
);
testcase!(
test_generic_bounds_to_callable,
r#"
from typing import Callable
class A: ...
class B(A): ...
class C(B): ...
def f[T: B](x: T) -> T: ...
c1: Callable[[A], A] = f # E: `[T: B](x: T) -> T` is not assignable to `(A) -> A`
c2: Callable[[B], B] = f # OK
c3: Callable[[C], C] = f # OK
"#,
);
testcase!(
test_return_generic_callable,
r#"
from typing import assert_type, Callable
def f[T]() -> Callable[[T], T]:
return lambda x: x
g = f()
assert_type(g(0), int)
assert_type(g(""), str)
@f()
def h(x: int) -> int:
return x
assert_type(h(0), int)
"#,
);
testcase!(
test_generic_callable_union,
r#"
from typing import assert_type, Callable
def f[T]() -> Callable[[T], T] | Callable[[T], list[T]]: ...
g = f()
assert_type(g(0), int | list[int])
assert_type(g(""), str | list[str])
"#,
);
testcase!(
test_callable_returns_callable_returns_callable,
r#"
from typing import assert_type, Callable
def f[T]() -> Callable[[], Callable[[T], T]]:
def f():
return lambda x: x
return f
g = f()()
assert_type(g(0), int)
assert_type(g(""), str)
"#,
);
testcase!(
test_return_substituted_callable,
r#"
from typing import assert_type, Callable
def f[T](x: T) -> Callable[[T], T]: ...
g = f(0)
assert_type(g(0), int)
assert_type(g(""), int) # E: `Literal['']` is not assignable to parameter with type `int`
"#,
);
testcase!(
test_generic_callable_or_none,
r#"
from typing import assert_type, Callable
def f[T]() -> Callable[[T], T] | None: ...
g = f()
if g:
assert_type(g(0), int)
assert_type(g(""), str)
"#,
);
testcase!(
test_pass_literals_through_identity,
r#"
from typing import Callable, Literal, reveal_type
def f(x: Literal[1]) -> Literal[1]:
return x
def g[T](x: T) -> T:
return x
h = g(f)
reveal_type(h) # E: revealed type: (x: Literal[1]) -> Literal[1]
"#,
);
testcase!(
test_boundmethod_union,
r#"
from typing import assert_type
def _(flag: bool):
class C:
if flag:
def __getitem__(self, key: int) -> str:
return str(key)
else:
def __getitem__(self, key: int) -> bytes:
return bytes()
c = C()
assert_type(c[0], bytes | str)
"#,
);
testcase!(
test_unknown_varargs_kwargs,
r#"
from typing import Any, assert_type
def f(*args, **kwargs):
assert_type(args, tuple[Any, ...])
assert_type(kwargs, dict[str, Any])
"#,
);
testcase!(
test_bad_varargs_kwargs,
r#"
from typing import Annotated, Any, assert_type
def f(*args: Annotated, **kwargs: Annotated): # E: # E:
assert_type(args, tuple[Any, ...])
assert_type(kwargs, dict[str, Any])
"#,
);
testcase!(
test_isinstance_narrow,
r#"
from typing import assert_type, reveal_type, Any, Callable
def f(x: object):
if isinstance(x, Callable):
assert_type(x, Callable[..., Any])
def g(x: int):
if isinstance(x, Callable):
reveal_type(x) # E: ((...) -> Unknown) & int
def h(x: Callable[[int], int]):
if isinstance(x, Callable):
assert_type(x, Callable[[int], int])
"#,
);
testcase!(
test_isinstance_error,
r#"
from typing import Any, Callable
def f(x: object):
isinstance(x, Callable[..., Any]) # E: Expected class object, got `type[(...) -> Any]`
"#,
);
testcase!(
test_builtins_callable_narrow,
r#"
from typing import Any, Callable, assert_type
def f(
x1: Callable[[int], int],
x2: Callable[..., int],
x3: Callable[[int], Any],
x4: Callable[..., int | Any],
x5: Callable,
):
if callable(x1):
assert_type(x1, Callable[[int], int])
if callable(x2):
assert_type(x2, Callable[..., int])
if callable(x3):
assert_type(x3, Callable[[int], Any])
if callable(x4):
assert_type(x4, Callable[..., int | Any])
if callable(x5):
assert_type(x5, Callable[..., Any])
"#,
);
testcase!(
test_builtins_callable_narrow_unknown,
r#"
from typing import Any, Callable, TypeIs, assert_type
def f(x):
assert callable(x)
assert_type(x, Callable[..., Any])
assert_type(x(), Any)
def g(x: object):
assert callable(x)
assert_type(x, Callable[..., Any])
def is_object_callable(x: object) -> TypeIs[Callable[..., object]]:
return callable(x)
def h(x):
assert is_object_callable(x)
assert_type(x(), object)
"#,
);
testcase!(
test_narrow_union,
r#"
from typing import Any, Callable, assert_type
def f(x: type[int] | Callable[[int], Any]):
if callable(x):
assert_type(x, type[int] | Callable[[int], Any])
"#,
);
testcase!(
test_narrow_function,
r#"
from typing import Any, Callable, assert_type
def f() -> Any:
pass
if callable(f):
assert_type(f, Callable[[], Any])
"#,
);
testcase!(
test_unbound_name_ok_in_lambda,
r#"
x: int
f1 = lambda: x
f2 = lambda: [x for _ in range(10)]
"#,
);
testcase!(
test_unknown_name_error_in_lambda,
r#"
f = lambda: x # E: Could not find name `x`
"#,
);
testcase!(
test_unbound_module_name_ok_in_def,
r#"
from typing import assert_type
x: int
def f():
assert_type(x, int)
"#,
);
testcase!(
test_unbound_local_name_error_in_def,
r#"
def f():
x: int
print(x) # E: `x` is uninitialized
"#,
);
testcase!(
test_anywhere_name_in_lambda,
r#"
from typing import assert_type
f = lambda: A.x
class A:
x: int = 0
assert_type(f(), int)
"#,
);
testcase!(
test_preserve_param_default,
r#"
from typing import reveal_type
def f(x: bool = True) -> bool:
return x
def g[T](f: T) -> T:
return f
reveal_type(g(f)) # E: (x: bool = True) -> bool
"#,
);
testcase!(
test_lambda_matches_generic_callable,
r#"
from typing import Callable, List, TypeVar
T = TypeVar("T")
def f(x):
return x
def wrap(fn: Callable[[List[T]], T]): ...
def g():
return wrap(lambda x: f(x))
"#,
);
testcase!(
test_protocol_paramspec_ellipsis,
r#"
from typing import Any, Protocol, ParamSpec
P = ParamSpec("P")
class Proto3(Protocol):
def __call__(self, a: int, *args: Any, **kwargs: Any) -> None: ...
class Proto4(Protocol[P]):
def __call__(self, a: int, *args: P.args, **kwargs: P.kwargs) -> None: ...
class Proto6(Protocol):
# Note: conformance uses `*args: Any, *, k: str` which pyrefly incorrectly treats as parse error
def __call__(self, a: int, /, *args: Any, k: str, **kwargs: Any) -> None: ...
class Proto7(Protocol):
def __call__(self, a: float, /, b: int, *, k: str, m: str) -> None: ...
def test(p4: Proto4[...], p7: Proto7):
# Both should be OK per conformance spec.
ok10: Proto3 = p4
ok11: Proto6 = p7
"#,
);
testcase!(
test_constructor_callable_conversion,
r#"
from typing import Callable, ParamSpec, TypeVar, Self, assert_type, overload, Generic
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
def accepts_callable(cb: Callable[P, R]) -> Callable[P, R]:
return cb
class Class3:
def __new__(cls, *args, **kwargs) -> Self: ...
def __init__(self, x: int) -> None: ...
r3 = accepts_callable(Class3)
class Class7(Generic[T]):
@overload
def __init__(self: "Class7[int]", x: int) -> None: ...
@overload
def __init__(self: "Class7[str]", x: str) -> None: ...
def __init__(self, x: int | str) -> None:
pass
r7 = accepts_callable(Class7)
assert_type(r7(""), Class7[str])
class Class8(Generic[T]):
def __new__(cls, x: list[T], y: list[T]) -> Self:
return super().__new__(cls)
r8 = accepts_callable(Class8)
assert_type(r8([""], [""]), Class8[str])
r8([1], [""]) # E: Argument `list[str]` is not assignable to parameter `y` with type `list[int]`
"#,
);
testcase!(
test_generic_classmethod_to_callable_preserves_class_tparams,
r#"
from typing import Callable, Generic, ParamSpec, TypeVar, assert_type
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
def accepts_callable(cb: Callable[P, R]) -> Callable[P, R]:
return cb
class Box(Generic[T]):
pass
class Factory(Generic[T]):
@classmethod
def make(cls, x: list[T], y: list[T]) -> Box[T]: ...
r = accepts_callable(Factory.make)
assert_type(r([""], [""]), Box[str])
"#,
);
testcase!(
test_generic_classmethod_to_callable_within_classmethod_preserves_class_tparams,
r#"
from typing import Callable, Generic, ParamSpec, TypeVar, assert_type
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
U = TypeVar("U")
def accepts_callable(cb: Callable[P, R]) -> Callable[P, R]:
return cb
class Box(Generic[T]):
pass
class Factory(Generic[T]):
@classmethod
def make(cls, x: list[U], y: list[U]) -> Box[U]: ...
@classmethod
def test(cls):
r = accepts_callable(cls.make)
assert_type(r([""], [""]), Box[str])
"#,
);
testcase!(
test_callable_instance_with_unknown_base,
r#"
class MyModel(BaseClass): # E: Could not find name `BaseClass`
pass
class Pipeline:
def __init__(self) -> None:
self.model = MyModel()
def run(self, data: object) -> object:
return self.model(data)
"#,
);
// Regression test for https://github.com/facebook/pyrefly/issues/1178
testcase!(
test_callable_as_base_class,
r#"
from collections.abc import Callable
class A(Callable): # E: Invalid base class
pass
"#,
);
// Regression test for https://github.com/facebook/pyrefly/issues/769
testcase!(
test_callable_no_args_assignable_to_varargs,
r#"
from typing import Callable
def schedule(delay: int, func: Callable[..., object]) -> None: ...
def after_func() -> None: ...
schedule(1000, after_func)
"#,
);
// Regression test for https://github.com/facebook/pyrefly/issues/3912
testcase!(
test_callable_ellipsis_return,
r#"
from typing import Callable, reveal_type
def f(x: Callable[..., ...]): # E: `...` is not a valid return type
reveal_type(x) # E: revealed type: (...) -> Unknown
"#,
);
testcase!(
test_implicit_any_lambda,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
f = lambda x: x # E: Type of lambda parameter `x` is unknown
"#,
);
testcase!(
test_implicit_any_lambda_param_only,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
f = lambda x: 1 # E: Type of lambda parameter `x` is unknown
"#,
);
testcase!(
test_implicit_any_lambda_multiple_params,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
f = lambda x, y: x + y # E: Type of lambda parameter `x` is unknown # E: Type of lambda parameter `y` is unknown
"#,
);
testcase!(
test_lambda_implicit_any_body_no_error,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
def untyped(x):
return x
f = lambda: untyped(1)
"#,
);
testcase!(
test_lambda_type_contextual_no_error,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
f: Callable[[int], int] = lambda x: x
"#,
);
testcase!(
test_implicit_any_lambda_partial_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
f: Callable[[int], None] = lambda x, y: None # E: Type of lambda parameter `y` is unknown # E: `(x: int, y: Unknown) -> None` is not assignable to `(int) -> None`
"#,
);
testcase!(
test_implicit_any_lambda_explicit_any_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Any, Callable, Protocol, assert_type
class VariadicAny(Protocol):
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
f: Callable[[Any], Any] = lambda x: x
g: Callable[[Any, int], int] = lambda x, y: y
h: Any = lambda x: None # E: Type of lambda parameter `x` is unknown
variadic: VariadicAny = lambda *args, **kwargs: (
assert_type(args, tuple[Any, ...]),
assert_type(kwargs, dict[str, Any]),
)
assert_type(f, Callable[[Any], Any])
assert_type(g, Callable[[Any, int], int])
"#,
);
testcase!(
test_implicit_any_lambda_generic_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
def unconstrained[T](f: Callable[[T], None]) -> None: ...
def constrained_first[T](x: T, f: Callable[[T], None]) -> None: ...
unconstrained(lambda x: None) # E: Type of lambda parameter `x` is unknown
constrained_first(0, lambda x: None)
"#,
);
testcase!(
test_implicit_any_lambda_late_generic_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
def constrained_later[T](f: Callable[[T], None], x: T) -> None: ...
constrained_later(lambda x: None, 0)
"#,
);
testcase!(
test_implicit_any_lambda_variadic_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
uncontextualized = lambda *args, **kwargs: None # E: Type of lambda parameter `args` is unknown # E: Type of lambda parameter `kwargs` is unknown
ellipsis_positional: Callable[..., None] = lambda x: None # E: Type of lambda parameter `x` is unknown
ellipsis: Callable[..., None] = lambda *args, **kwargs: None # E: Type of lambda parameter `args` is unknown # E: Type of lambda parameter `kwargs` is unknown
"#,
);
testcase!(
test_implicit_any_lambda_paramspec_variadics,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable, reveal_type
def apply[**P](f: Callable[P, None], g: Callable[P, None]) -> None: ...
def varargs(x: int, *args: str) -> None: ...
def kwargs(x: int, **kwargs: str) -> None: ...
def positional(x: int, y: str, /) -> None: ...
def keyword_only(*, x: int, y: str) -> None: ...
apply(varargs, lambda x, *args: (reveal_type(args), None)[1]) # E: revealed type: tuple[str, ...]
apply(kwargs, lambda x, **kwargs: (reveal_type(kwargs), None)[1]) # E: revealed type: dict[str, str]
apply(positional, lambda *args: (reveal_type(args), None)[1]) # E: revealed type: tuple[int | str, ...]
apply(keyword_only, lambda **kwargs: (reveal_type(kwargs), None)[1]) # E: revealed type: dict[str, int | str]
"#,
);
testcase!(
test_lambda_typed_dict_kwargs_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable, NotRequired, Protocol, TypedDict, Unpack, assert_type
class Options(TypedDict):
required: int
optional: NotRequired[str]
class SplitOptions(TypedDict):
consumed: int
remaining: str
class Callback(Protocol):
def __call__(self, **kwargs: Unpack[Options]) -> None: ...
class SplitCallback(Protocol):
def __call__(self, **kwargs: Unpack[SplitOptions]) -> None: ...
class GenericCallback(Protocol):
def __call__(self, **kwargs: Unpack[Options]) -> int | str: ...
def apply[**P](f: Callable[P, None], g: Callable[P, None]) -> None: ...
def source(**kwargs: Unpack[Options]) -> None: ...
def split_source(**kwargs: Unpack[SplitOptions]) -> None: ...
def split_plain(consumed: int, **kwargs: str) -> None:
assert_type(kwargs, dict[str, str])
assert_type(kwargs["remaining"], str)
assert_type(kwargs["consumed"], str)
def generic[T](**kwargs: T) -> T: ...
def check_args[*Ts]() -> None:
callback: Callable[[*Ts], None] = lambda *args: (
assert_type(args, tuple[*Ts]),
None,
)[1]
callback: Callback = lambda **kwargs: (
assert_type(kwargs, Options),
assert_type(kwargs["required"], int),
assert_type(kwargs.get("optional"), str | None),
None,
)[3]
split_callback: SplitCallback = lambda consumed, **kwargs: (
assert_type(consumed, int),
assert_type(kwargs, dict[str, str]),
assert_type(kwargs["remaining"], str),
assert_type(kwargs["consumed"], str),
None,
)[4]
generic_callback: GenericCallback = generic
apply(source, lambda **kwargs: (
assert_type(kwargs, Options),
assert_type(kwargs["required"], int),
assert_type(kwargs.get("optional"), str | None),
None,
)[3])
apply(split_source, lambda consumed, **kwargs: (
assert_type(consumed, int),
assert_type(kwargs, dict[str, str]),
assert_type(kwargs["remaining"], str),
assert_type(kwargs["consumed"], str),
None,
)[4])
plain_callback: SplitCallback = split_plain
apply(split_source, split_plain)
"#,
);
testcase!(
test_implicit_any_lambda_paramspec_partial_context,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
def apply[**P](f: Callable[P, None], g: Callable[P, None]) -> None: ...
def infer[**P](f: Callable[P, None]) -> None: ...
def source(x: int, y: str) -> None: ...
apply(source, lambda x, y: None)
apply(source, lambda x, z: None) # E: Type of lambda parameter `z` is unknown # E: Argument `(x: int, z: Unknown) -> None` is not assignable to parameter `g` with type `(x: int, y: str) -> None`
infer(lambda x: None) # E: Type of lambda parameter `x` is unknown
infer(lambda *args, **kwargs: None) # E: Type of lambda parameter `args` is unknown # E: Type of lambda parameter `kwargs` is unknown
"#,
);
testcase!(
test_lambda_unconstrained_paramspec_not_first_use_inferred,
r#"
from collections.abc import Callable
type Handler[**P] = Callable[P, None]
def channel[**P](handler: Handler[P]) -> Handler[P]:
return handler
events: list[tuple[str, int]] = []
post = channel(lambda name, value: events.append((name, value)))
post("time-pos", 1)
post("time-pos", 2)
"#,
);
testcase!(
test_implicit_any_lambda_partial_context_with_default,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable
f: Callable[[int], None] = lambda x, y=0: None
"#,
);
testcase!(
test_lambda_no_params_known_return_no_error,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
f = lambda: 1
"#,
);
testcase!(
test_lambda_default_param_no_error,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
f = lambda x=1: x
"#,
);
testcase!(
test_implicit_any_lambda_in_generic_call,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
xs = sorted([3, 1, 2], key=lambda x: x)
"#,
);
testcase!(
test_lambda_default_infers_type,
r#"
from typing import reveal_type
f = lambda x=1: x
reveal_type(f) # E: revealed type: (x: int = 1) -> int
"#,
);
testcase!(
test_lambda_default_none_infers_optional,
r#"
from typing import reveal_type
f = lambda x=None: x
reveal_type(f) # E: revealed type: (x: Unknown | None = None) -> Unknown | None
"#,
);
testcase!(
test_lambda_default_no_unknown,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
f = lambda x=1: x
g = lambda x="a": x
h = lambda x=None: x
"#,
);
testcase!(
test_lambda_default_contextual_type_takes_precedence,
TestEnv::new().enable_implicit_any_lambda_error(),
r#"
from typing import Callable, assert_type
f: Callable[[str | None], str | None] = lambda x="hi": x
assert_type(f, Callable[[str | None], str | None])
"#,
);
testcase!(
test_ellipsis_default_is_valid_in_type_checking_block,
r#"
from typing import TYPE_CHECKING
if TYPE_CHECKING:
def f(x: int = ...):
pass
"#,
);
// A `*args: Any, **kwargs: Any` signature written in a definition is gradual (equivalent to
// `...`), but an `Any` that only arises from substituting a type parameter is not: the
// signature stays strict.
testcase!(
test_gradual_variadic_params_annotation_vs_substitution,
r#"
from typing import Any, ParamSpec, Protocol, TypeVar
P = ParamSpec("P")
T_contra = TypeVar("T_contra", contravariant=True)
class Gradual(Protocol):
def __call__(self, *args: Any, **kwargs: Any) -> None: ...
# `*args`/`**kwargs` typed via a TypeVar, specialized with `Any`.
class Subst(Protocol[T_contra]):
def __call__(self, *args: T_contra, **kwargs: T_contra) -> None: ...
# `*args`/`**kwargs` typed via a ParamSpec, specialized with `...`.
class SubstP(Protocol[P]):
def __call__(self, a: int, *args: P.args, **kwargs: P.kwargs) -> None: ...
class NoArgs(Protocol):
def __call__(self) -> None: ...
def f(n: NoArgs) -> None:
ok: Gradual = n # a gradual target accepts a stricter callable
err1: Subst[Any] = n # E: `NoArgs` is not assignable to `Subst[Any]`
err2: SubstP[...] = n # E: `NoArgs` is not assignable to `SubstP[...]`
"#,
);
testcase!(
test_defaultdict_frozenset,
r#"
from collections import defaultdict
from typing import Any, assert_type
class C:
def __init__(self):
self.x = defaultdict(frozenset)
assert_type(self.x, defaultdict[Any, frozenset[Any]])
"#,
);