diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f9fbd53866da..513065013958 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1426,6 +1426,7 @@ def check_callable_call( need_refresh = any( isinstance(v, (ParamSpecType, TypeVarTupleType)) for v in callee.variables ) + old_callee = callee callee = freshen_function_type_vars(callee) callee = self.infer_function_type_arguments_using_context(callee, context) if need_refresh: @@ -1440,7 +1441,7 @@ def check_callable_call( lambda i: self.accept(args[i]), ) callee = self.infer_function_type_arguments( - callee, args, arg_kinds, formal_to_actual, context + callee, args, arg_kinds, formal_to_actual, context, old_callee ) if need_refresh: formal_to_actual = map_actuals_to_formals( @@ -1730,6 +1731,7 @@ def infer_function_type_arguments( arg_kinds: list[ArgKind], formal_to_actual: list[list[int]], context: Context, + unfreshened_callee_type: CallableType, ) -> CallableType: """Infer the type arguments for a generic callee type. @@ -1773,6 +1775,28 @@ def infer_function_type_arguments( callee_type, args, arg_kinds, formal_to_actual, inferred_args, context ) + return_type = get_proper_type(callee_type.ret_type) + if isinstance(return_type, CallableType): + # fixup: + # def [T] () -> def (T) -> T + # into + # def () -> def [T] (T) -> T + for i, argument in enumerate(inferred_args): + if isinstance(get_proper_type(argument), UninhabitedType): + # un-"freshen" the type variable :^) + variable = unfreshened_callee_type.variables[i] + inferred_args[i] = variable + + # handle multiple type variables + return_type = return_type.copy_modified( + variables=[*return_type.variables, variable] + ) + + callee_type = callee_type.copy_modified( + # Question: am I allowed to assign the get_proper_type'd thing? + ret_type=return_type + ) + if ( callee_type.special_sig == "dict" and len(inferred_args) == 2 diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 166e173e7301..582423f39c4e 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3105,7 +3105,8 @@ T = TypeVar('T') def f(x: Optional[T] = None) -> Callable[..., T]: ... -x = f() # E: Need type annotation for "x" +# Question: Is this alright? +x: Callable[..., T] = f() y = x [case testDontNeedAnnotationForCallable] diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index fe66b18fbfea..21c2e7c7e850 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1520,3 +1520,28 @@ def identity(func: Callable[P, None]) -> Callable[P, None]: ... @identity def f(f: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ... [builtins fixtures/paramspec.pyi] + +[case testParamSpecificationSurvivesCall] +# https://github.com/python/mypy/issues/12595 +from typing import TypeVar, Callable +from typing_extensions import Concatenate, ParamSpec + +T = TypeVar('T') +U = TypeVar('U') +P = ParamSpec('P') + +def t(f: Callable[P, T]) -> Callable[P, T]: ... + +def pop_off( + transform: Callable[Concatenate[T, P], U] +) -> Callable[P, Callable[[T], U]]: ... + +u = pop_off(t) +reveal_type(u) # N: Revealed type is "def [P, T] () -> def (def (*P.args, **P.kwargs) -> T`-2) -> def (*P.args, **P.kwargs) -> T`-2" + +@u() +def f(x: int) -> None: ... + +reveal_type(f) # N: Revealed type is "def (x: builtins.int)" +f(0) +[builtins fixtures/paramspec.pyi]