From 822b12a09c086b7322714ed6abf65673df385f8a Mon Sep 17 00:00:00 2001 From: themylogin Date: Fri, 29 May 2026 14:07:54 +0200 Subject: [PATCH] Fix crashing on invalid `Concatenate` usage --- mypy/typeanal.py | 9 +++++++++ test-data/unit/check-parameter-specification.test | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 51d26afd55e46..db5272e14c8a0 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1199,6 +1199,15 @@ def visit_callable_type( "Arguments not allowed after ParamSpec.args", t, code=codes.VALID_TYPE ) at = self.anal_type(ut, nested=nested, allow_unpack=False) + if isinstance(at, Parameters): + # A Parameters here comes from a misplaced Concatenate ending in + # an explicit ellipsis, e.g. Callable[[Concatenate[int, ...]], None]. + self.fail( + "Concatenate is only valid as the first argument to Callable", + ut, + code=codes.VALID_TYPE, + ) + at = AnyType(TypeOfAny.from_error) arg_types.append(at) if nested and arg_types: diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 970ba45d0e8e2..be802a2607141 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2755,3 +2755,16 @@ reveal_type(Sneaky(f8, 1, y='').kwargs) # N: Revealed type is "builtins.dict[bu reveal_type(Sneaky(f9, 1, y=0).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" reveal_type(Sneaky(f9, 1, y=0, z='').kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" [builtins fixtures/paramspec.pyi] + +[case testParamSpecNoCrashOnConcatenateInCallableArg] +from typing import Callable +from typing_extensions import Concatenate, ParamSpec + +P = ParamSpec("P") + +def takes_paramspec(f: Callable[P, object]) -> bool: + return False + +def run(callback: Callable[[Concatenate[int, ...]], None]) -> None: # E: Concatenate is only valid as the first argument to Callable + takes_paramspec(callback) +[builtins fixtures/paramspec.pyi]