diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 51d26afd55e4..db5272e14c8a 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 970ba45d0e8e..be802a260714 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]