Skip to content

Commit 3881700

Browse files
committed
Handle more ParamSpec literal form special cases
This is more:tm: correct as shown by changes in test cases. I can't find any issues that are closed by this though. From what I remember, this was a followup fix for python#14903 in order to fix some incorrect mypy-primer output.
1 parent 16667f3 commit 3881700

File tree

3 files changed

+48
-16
lines changed

3 files changed

+48
-16
lines changed

mypy/checkexpr.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
has_any_from_unimported_type,
106106
instantiate_type_alias,
107107
make_optional_type,
108+
pack_paramspec_args,
108109
set_any_tvars,
109110
)
110111
from mypy.typeops import (
@@ -4083,6 +4084,9 @@ def apply_type_arguments_to_callable(
40834084
tp = get_proper_type(tp)
40844085

40854086
if isinstance(tp, CallableType):
4087+
if len(tp.variables) == 1 and isinstance(tp.variables[0], ParamSpecType):
4088+
args = pack_paramspec_args(args)
4089+
40864090
if len(tp.variables) != len(args):
40874091
if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple":
40884092
# TODO: Specialize the callable for the type arguments

mypy/typeanal.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,29 @@ def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> str:
166166
return msg
167167

168168

169+
def pack_paramspec_args(an_args: Sequence[Type]) -> list[Type]:
170+
# "Aesthetic" ParamSpec literals for single ParamSpec: C[int, str] -> C[[int, str]].
171+
# These do not support mypy_extensions VarArgs, etc. as they were already analyzed
172+
# TODO: should these be re-analyzed to get rid of this inconsistency?
173+
count = len(an_args)
174+
if count > 0:
175+
first_arg = get_proper_type(an_args[0])
176+
if not (count == 1 and isinstance(first_arg, (Parameters, ParamSpecType))):
177+
if isinstance(first_arg, AnyType) and first_arg.type_of_any == TypeOfAny.from_error:
178+
# Question: should I make new AnyTypes instead of reusing them?
179+
return [
180+
Parameters(
181+
[first_arg, first_arg],
182+
[ARG_STAR, ARG_STAR2],
183+
[None, None],
184+
is_ellipsis_args=True,
185+
)
186+
]
187+
else:
188+
return [Parameters(an_args, [ARG_POS] * count, [None] * count)]
189+
return list(an_args)
190+
191+
169192
class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface):
170193
"""Semantic analyzer for types.
171194
@@ -395,7 +418,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
395418
allow_param_spec_literals=node.has_param_spec_type,
396419
)
397420
if node.has_param_spec_type and len(node.alias_tvars) == 1:
398-
an_args = self.pack_paramspec_args(an_args)
421+
an_args = pack_paramspec_args(an_args)
399422

400423
disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub
401424
res = instantiate_type_alias(
@@ -440,17 +463,6 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
440463
else: # sym is None
441464
return AnyType(TypeOfAny.special_form)
442465

443-
def pack_paramspec_args(self, an_args: Sequence[Type]) -> list[Type]:
444-
# "Aesthetic" ParamSpec literals for single ParamSpec: C[int, str] -> C[[int, str]].
445-
# These do not support mypy_extensions VarArgs, etc. as they were already analyzed
446-
# TODO: should these be re-analyzed to get rid of this inconsistency?
447-
count = len(an_args)
448-
if count > 0:
449-
first_arg = get_proper_type(an_args[0])
450-
if not (count == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType))):
451-
return [Parameters(an_args, [ARG_POS] * count, [None] * count)]
452-
return list(an_args)
453-
454466
def cannot_resolve_type(self, t: UnboundType) -> None:
455467
# TODO: Move error message generation to messages.py. We'd first
456468
# need access to MessageBuilder here. Also move the similar
@@ -686,7 +698,7 @@ def analyze_type_with_type_info(
686698
ctx.column,
687699
)
688700
if len(info.type_vars) == 1 and info.has_param_spec_type:
689-
instance.args = tuple(self.pack_paramspec_args(instance.args))
701+
instance.args = tuple(pack_paramspec_args(instance.args))
690702

691703
if info.has_type_var_tuple_type:
692704
if instance.args:

test-data/unit/check-parameter-specification.test

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,12 @@ class C(Generic[P]):
325325
def m(self, *args: P.args, **kwargs: P.kwargs) -> int:
326326
return 1
327327

328-
c: C[Any]
329-
reveal_type(c) # N: Revealed type is "__main__.C[Any]"
330-
reveal_type(c.m) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> builtins.int"
328+
c_old: C[Any]
329+
reveal_type(c_old) # N: Revealed type is "__main__.C[[Any]]"
330+
reveal_type(c_old.m) # N: Revealed type is "def (Any) -> builtins.int"
331+
c: C[...]
332+
reveal_type(c) # N: Revealed type is "__main__.C[...]"
333+
reveal_type(c.m) # N: Revealed type is "def (*Any, **Any) -> builtins.int"
331334
c.m(4, 6, y='x')
332335
c = c
333336

@@ -1520,3 +1523,16 @@ def identity(func: Callable[P, None]) -> Callable[P, None]: ...
15201523
@identity
15211524
def f(f: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ...
15221525
[builtins fixtures/paramspec.pyi]
1526+
1527+
[case testRuntimeSpecialParamspecLiteralSyntax]
1528+
import sub
1529+
1530+
reveal_type(sub.Ex[None]()) # N: Revealed type is "sub.Ex[[None]]"
1531+
[file sub/__init__.py]
1532+
from typing_extensions import ParamSpec
1533+
from typing import Generic
1534+
1535+
P = ParamSpec("P")
1536+
1537+
class Ex(Generic[P]): ...
1538+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)