Skip to content

Commit 6e10cf9

Browse files
committed
Erase any stray typevars in functools.partial
1 parent c274971 commit 6e10cf9

File tree

2 files changed

+48
-1
lines changed

2 files changed

+48
-1
lines changed

mypy/plugins/functools.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import mypy.plugin
99
import mypy.semanal
1010
from mypy.argmap import map_actuals_to_formals
11+
from mypy.erasetype import erase_typevars
1112
from mypy.nodes import (
1213
ARG_POS,
1314
ARG_STAR2,
@@ -312,7 +313,11 @@ def handle_partial_with_callee(ctx: mypy.plugin.FunctionContext, callee: Type) -
312313
special_sig="partial",
313314
)
314315

315-
ret = ctx.api.named_generic_type(PARTIAL, [ret_type])
316+
# Do not leak typevars from generic functions - they cannot be usable.
317+
# Keep them in the wrapped callable, but avoid `partial[SomeStrayTypeVar]`
318+
erased_ret_type = erase_typevars(ret_type, [tv.id for tv in fn_type.variables])
319+
320+
ret = ctx.api.named_generic_type(PARTIAL, [erased_ret_type])
316321
ret = ret.copy_with_extra_attr("__mypy_partial", partially_applied)
317322
if partially_applied.param_spec():
318323
assert ret.extra_attrs is not None # copy_with_extra_attr above ensures this

test-data/unit/check-functools.test

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,3 +658,45 @@ def f(x: P):
658658
# TODO: but this is incorrect, predating the functools.partial plugin
659659
reveal_type(partial(x, "a")()) # N: Revealed type is "builtins.int"
660660
[builtins fixtures/tuple.pyi]
661+
662+
[case testFunctoolsPartialTypeVarErasure]
663+
from typing import Callable, TypeVar, Union
664+
from typing_extensions import ParamSpec, TypeVarTuple, Unpack
665+
from functools import partial
666+
667+
def use_int_callable(x: Callable[[int], int]) -> None:
668+
pass
669+
def use_func_callable(
670+
x: Callable[
671+
[Callable[[int], None]],
672+
Callable[[int], None],
673+
],
674+
) -> None:
675+
pass
676+
677+
Tc = TypeVar("Tc", int, str)
678+
Tb = TypeVar("Tb", bound=Union[int, str])
679+
P = ParamSpec("P")
680+
Ts = TypeVarTuple("Ts")
681+
682+
def func_b(a: Tb, b: str) -> Tb:
683+
return a
684+
def func_c(a: Tc, b: str) -> Tc:
685+
return a
686+
687+
def func_fn(fn: Callable[P, Tc], b: str) -> Callable[P, Tc]:
688+
return fn
689+
def func_fn_unpack(fn: Callable[[Unpack[Ts]], Tc], b: str) -> Callable[[Unpack[Ts]], Tc]:
690+
return fn
691+
692+
use_int_callable(partial(func_b, b=""))
693+
use_func_callable(partial(func_b, b=""))
694+
use_int_callable(partial(func_c, b=""))
695+
use_func_callable(partial(func_c, b=""))
696+
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]" \
697+
# N: "partial[Callable[[VarArg(Any), KwArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any), KwArg(Any)], Any]]"
698+
use_func_callable(partial(func_fn, b=""))
699+
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]" \
700+
# N: "partial[Callable[[VarArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any)], Any]]"
701+
use_func_callable(partial(func_fn_unpack, b=""))
702+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)