diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 6fb5b7848a46..e85a345b317d 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -26629,42 +26629,60 @@ export function createTypeEvaluator( !effectiveSrcParamSpec || !isTypeSame(effectiveSrcParamSpec, effectiveDestParamSpec, { ignoreTypeFlags: true }) ) { - const remainingFunction = FunctionType.createInstance( - '', - '', - '', - effectiveSrcType.shared.flags | FunctionTypeFlags.SynthesizedMethod, - effectiveSrcType.shared.docString - ); - remainingFunction.shared.deprecatedMessage = effectiveSrcType.shared.deprecatedMessage; - remainingFunction.shared.typeVarScopeId = effectiveSrcType.shared.typeVarScopeId; - remainingFunction.priv.constructorTypeVarScopeId = effectiveSrcType.priv.constructorTypeVarScopeId; - remainingFunction.shared.methodClass = effectiveSrcType.shared.methodClass; - remainingParams.forEach((param) => { - FunctionType.addParam(remainingFunction, param); - }); - if (effectiveSrcParamSpec) { - FunctionType.addParamSpecVariadics(remainingFunction, convertToInstance(effectiveSrcParamSpec)); - } + const effectiveSrcPosCount = isContra ? destPositionalCount : srcPositionalCount; + const effectiveDestPosCount = isContra ? srcPositionalCount : destPositionalCount; + + // If the src and dest both have ParamSpecs but the src has additional positional + // parameters that have not been matched to dest positional parameters (probably due + // to a Concatenate), don't attempt to assign the remaining parameters to the ParamSpec. + if (!effectiveSrcParamSpec || effectiveSrcPosCount >= effectiveDestPosCount) { + const remainingFunction = FunctionType.createInstance( + '', + '', + '', + effectiveSrcType.shared.flags | FunctionTypeFlags.SynthesizedMethod, + effectiveSrcType.shared.docString + ); + remainingFunction.shared.deprecatedMessage = effectiveSrcType.shared.deprecatedMessage; + remainingFunction.shared.typeVarScopeId = effectiveSrcType.shared.typeVarScopeId; + remainingFunction.priv.constructorTypeVarScopeId = + effectiveSrcType.priv.constructorTypeVarScopeId; + remainingFunction.shared.methodClass = effectiveSrcType.shared.methodClass; + remainingParams.forEach((param) => { + FunctionType.addParam(remainingFunction, param); + }); + if (effectiveSrcParamSpec) { + FunctionType.addParamSpecVariadics( + remainingFunction, + convertToInstance(effectiveSrcParamSpec) + ); + } - if ( - !assignType(effectiveDestParamSpec, remainingFunction, /* diag */ undefined, constraints, flags) - ) { - // If we couldn't assign the function to the ParamSpec, see if we can - // assign only the ParamSpec. This is possible if there were no - // remaining parameters. if ( - remainingParams.length > 0 || - !effectiveSrcParamSpec || !assignType( - convertToInstance(effectiveDestParamSpec), - convertToInstance(effectiveSrcParamSpec), + effectiveDestParamSpec, + remainingFunction, /* diag */ undefined, constraints, flags ) ) { - canAssign = false; + // If we couldn't assign the function to the ParamSpec, see if we can + // assign only the ParamSpec. This is possible if there were no + // remaining parameters. + if ( + remainingParams.length > 0 || + !effectiveSrcParamSpec || + !assignType( + convertToInstance(effectiveDestParamSpec), + convertToInstance(effectiveSrcParamSpec), + /* diag */ undefined, + constraints, + flags + ) + ) { + canAssign = false; + } } } } diff --git a/packages/pyright-internal/src/tests/samples/paramSpec55.py b/packages/pyright-internal/src/tests/samples/paramSpec55.py new file mode 100644 index 000000000000..655a7b993dac --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/paramSpec55.py @@ -0,0 +1,24 @@ +# This sample tests the case where a function with a ParamSpec +# is assigned to another function with a Concatenate and a ParamSpec. + +from typing import Any, Concatenate, Callable + + +class MyGeneric[**P0]: + def __call__(self, *args: P0.args, **kwargs: P0.kwargs) -> Any: ... + + +def deco1[**P1](func: Callable[[Callable[P1, Any]], Any]) -> MyGeneric[P1]: ... + + +@deco1 +def func1[**P2](func: Callable[Concatenate[int, P2], Any]): ... + + +reveal_type(func1, expected_text="MyGeneric[(int, **P2@func1)]") + + +v1: MyGeneric[[int]] = func1 + +# This should generate an error. +v2: MyGeneric[[int, int]] = func1 diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index c225fd78cf53..1bc8273fd7d4 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -857,6 +857,11 @@ test('ParamSpec54', () => { TestUtils.validateResults(results, 0); }); +test('ParamSpec55', () => { + const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec55.py']); + TestUtils.validateResults(results, 1); +}); + test('Slice1', () => { const results = TestUtils.typeAnalyzeSampleFiles(['slice1.py']); TestUtils.validateResults(results, 0);