diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f9fbd53866da..f604b3495b58 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -105,6 +105,7 @@ has_any_from_unimported_type, instantiate_type_alias, make_optional_type, + pack_paramspec_args, set_any_tvars, ) from mypy.typeops import ( @@ -4083,6 +4084,9 @@ def apply_type_arguments_to_callable( tp = get_proper_type(tp) if isinstance(tp, CallableType): + if len(tp.variables) == 1 and isinstance(tp.variables[0], ParamSpecType): + args = pack_paramspec_args(args) + if len(tp.variables) != len(args): if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple": # TODO: Specialize the callable for the type arguments diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d09fa5e339d1..7e3cd44ad79d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -166,6 +166,29 @@ def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> str: return msg +def pack_paramspec_args(an_args: Sequence[Type]) -> list[Type]: + # "Aesthetic" ParamSpec literals for single ParamSpec: C[int, str] -> C[[int, str]]. + # These do not support mypy_extensions VarArgs, etc. as they were already analyzed + # TODO: should these be re-analyzed to get rid of this inconsistency? + count = len(an_args) + if count > 0: + first_arg = get_proper_type(an_args[0]) + if not (count == 1 and isinstance(first_arg, (Parameters, ParamSpecType))): + if isinstance(first_arg, AnyType) and first_arg.type_of_any == TypeOfAny.from_error: + # Question: should I make new AnyTypes instead of reusing them? + return [ + Parameters( + [first_arg, first_arg], + [ARG_STAR, ARG_STAR2], + [None, None], + is_ellipsis_args=True, + ) + ] + else: + return [Parameters(an_args, [ARG_POS] * count, [None] * count)] + return list(an_args) + + class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface): """Semantic analyzer for types. @@ -395,7 +418,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) allow_param_spec_literals=node.has_param_spec_type, ) if node.has_param_spec_type and len(node.alias_tvars) == 1: - an_args = self.pack_paramspec_args(an_args) + an_args = pack_paramspec_args(an_args) disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub res = instantiate_type_alias( @@ -440,17 +463,6 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) else: # sym is None return AnyType(TypeOfAny.special_form) - def pack_paramspec_args(self, an_args: Sequence[Type]) -> list[Type]: - # "Aesthetic" ParamSpec literals for single ParamSpec: C[int, str] -> C[[int, str]]. - # These do not support mypy_extensions VarArgs, etc. as they were already analyzed - # TODO: should these be re-analyzed to get rid of this inconsistency? - count = len(an_args) - if count > 0: - first_arg = get_proper_type(an_args[0]) - if not (count == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType))): - return [Parameters(an_args, [ARG_POS] * count, [None] * count)] - return list(an_args) - def cannot_resolve_type(self, t: UnboundType) -> None: # TODO: Move error message generation to messages.py. We'd first # need access to MessageBuilder here. Also move the similar @@ -686,7 +698,7 @@ def analyze_type_with_type_info( ctx.column, ) if len(info.type_vars) == 1 and info.has_param_spec_type: - instance.args = tuple(self.pack_paramspec_args(instance.args)) + instance.args = tuple(pack_paramspec_args(instance.args)) if info.has_type_var_tuple_type: if instance.args: diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index fe66b18fbfea..a38cc2617e13 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -325,9 +325,12 @@ class C(Generic[P]): def m(self, *args: P.args, **kwargs: P.kwargs) -> int: return 1 -c: C[Any] -reveal_type(c) # N: Revealed type is "__main__.C[Any]" -reveal_type(c.m) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> builtins.int" +c_old: C[Any] +reveal_type(c_old) # N: Revealed type is "__main__.C[[Any]]" +reveal_type(c_old.m) # N: Revealed type is "def (Any) -> builtins.int" +c: C[...] +reveal_type(c) # N: Revealed type is "__main__.C[...]" +reveal_type(c.m) # N: Revealed type is "def (*Any, **Any) -> builtins.int" c.m(4, 6, y='x') c = c @@ -1520,3 +1523,16 @@ 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 testRuntimeSpecialParamspecLiteralSyntax] +import sub + +reveal_type(sub.Ex[None]()) # N: Revealed type is "sub.Ex[[None]]" +[file sub/__init__.py] +from typing_extensions import ParamSpec +from typing import Generic + +P = ParamSpec("P") + +class Ex(Generic[P]): ... +[builtins fixtures/paramspec.pyi]