@@ -656,3 +656,73 @@ def f(x: P):
656656 # TODO: but this is incorrect, predating the functools.partial plugin
657657 reveal_type(partial(x, "a")()) # N: Revealed type is "builtins.int"
658658[builtins fixtures/tuple.pyi]
659+
660+ [case testFunctoolsPartialTypeVarErasure]
661+ from typing import Callable, TypeVar, Union
662+ from typing_extensions import ParamSpec, TypeVarTuple, Unpack
663+ from functools import partial
664+
665+ def use_int_callable(x: Callable[[int], int]) -> None:
666+ pass
667+ def use_func_callable(
668+ x: Callable[
669+ [Callable[[int], None]],
670+ Callable[[int], None],
671+ ],
672+ ) -> None:
673+ pass
674+
675+ Tc = TypeVar("Tc", int, str)
676+ Tb = TypeVar("Tb", bound=Union[int, str])
677+ P = ParamSpec("P")
678+ Ts = TypeVarTuple("Ts")
679+
680+ def func_b(a: Tb, b: str) -> Tb:
681+ return a
682+ def func_c(a: Tc, b: str) -> Tc:
683+ return a
684+
685+ def func_fn(fn: Callable[P, Tc], b: str) -> Callable[P, Tc]:
686+ return fn
687+ def func_fn_unpack(fn: Callable[[Unpack[Ts]], Tc], b: str) -> Callable[[Unpack[Ts]], Tc]:
688+ return fn
689+
690+ # We should not leak stray typevars that aren't in scope:
691+ reveal_type(partial(func_b, b="")) # N: Revealed type is "functools.partial[Any]"
692+ reveal_type(partial(func_c, b="")) # N: Revealed type is "functools.partial[Any]"
693+ reveal_type(partial(func_fn, b="")) # N: Revealed type is "functools.partial[def (*Any, **Any) -> Any]"
694+ reveal_type(partial(func_fn_unpack, b="")) # N: Revealed type is "functools.partial[def (*Any) -> Any]"
695+
696+ use_int_callable(partial(func_b, b=""))
697+ use_func_callable(partial(func_b, b=""))
698+ use_int_callable(partial(func_c, b=""))
699+ use_func_callable(partial(func_c, b=""))
700+ use_int_callable(partial(func_fn, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[Callable[[VarArg(Any), KwArg(Any)], Any]]"; expected "Callable[[int], int]" \
701+ # N: "partial[Callable[[VarArg(Any), KwArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any), KwArg(Any)], Any]]"
702+ use_func_callable(partial(func_fn, b=""))
703+ use_int_callable(partial(func_fn_unpack, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[Callable[[VarArg(Any)], Any]]"; expected "Callable[[int], int]" \
704+ # N: "partial[Callable[[VarArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any)], Any]]"
705+ use_func_callable(partial(func_fn_unpack, b=""))
706+
707+ # But we should not erase typevars that aren't bound by function
708+ # passed to `partial`:
709+
710+ def outer_b(arg: Tb) -> None:
711+
712+ def inner(a: Tb, b: str) -> Tb:
713+ return a
714+
715+ reveal_type(partial(inner, b="")) # N: Revealed type is "functools.partial[Tb`-1]"
716+ use_int_callable(partial(inner, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[Tb]"; expected "Callable[[int], int]" \
717+ # N: "partial[Tb].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Tb]"
718+
719+ def outer_c(arg: Tc) -> None:
720+
721+ def inner(a: Tc, b: str) -> Tc:
722+ return a
723+
724+ reveal_type(partial(inner, b="")) # N: Revealed type is "functools.partial[builtins.int]" \
725+ # N: Revealed type is "functools.partial[builtins.str]"
726+ use_int_callable(partial(inner, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[str]"; expected "Callable[[int], int]" \
727+ # N: "partial[str].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], str]"
728+ [builtins fixtures/tuple.pyi]
0 commit comments