| /* |
| * 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]]) |
| "#, |
| ); |