diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 1234677e8..9014ee81a 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -11640,6 +11640,11 @@ export function createTypeEvaluator( paramSpecTypeVarContext.forEach((paramSpecTypeVarContext) => { if (paramSpecTypeVarContext) { specializedReturnType = applySolvedTypeVars(specializedReturnType, paramSpecTypeVarContext); + + // It's possible that one or more of the TypeVars or ParamSpecs + // in the typeVarContext refer to TypeVars that were solved in + // the paramSpecTypeVarContext. Apply these solved TypeVars accordingly. + applySourceContextTypeVars(typeVarContext, paramSpecTypeVarContext); } }); } diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index ec81799ca..75de2b068 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -3329,7 +3329,7 @@ export function convertParamSpecValueToType(type: FunctionType): Type { FunctionType.addHigherOrderTypeVarScopeIds(functionType, withoutParamSpec.details.typeVarScopeId); FunctionType.addHigherOrderTypeVarScopeIds(functionType, withoutParamSpec.details.higherOrderTypeVarScopeIds); - withoutParamSpec.details.parameters.forEach((entry) => { + withoutParamSpec.details.parameters.forEach((entry, index) => { FunctionType.addParameter(functionType, { category: entry.category, name: entry.name, @@ -3337,7 +3337,7 @@ export function convertParamSpecValueToType(type: FunctionType): Type { defaultValueExpression: entry.defaultValueExpression, isNameSynthesized: entry.isNameSynthesized, hasDeclaredType: true, - type: entry.type, + type: FunctionType.getEffectiveParameterType(withoutParamSpec, index), }); }); diff --git a/packages/pyright-internal/src/tests/samples/constructor30.py b/packages/pyright-internal/src/tests/samples/constructor30.py new file mode 100644 index 000000000..4ede66b65 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/constructor30.py @@ -0,0 +1,31 @@ +# This sample tests the case where a class is parameterized by a ParamSpec +# which is inferred by a call to the constructor, and the passed value +# is a generic function whose types are informed by additional parameters +# also passed to the constructor. + +from typing import Callable, Generic, ParamSpec, TypeVar + +P = ParamSpec("P") +T = TypeVar("T") + + +class ABase: ... + + +class A(ABase): ... + + +TA = TypeVar("TA", bound=ABase) + + +class B(Generic[P, T]): + def __init__( + self, _type: Callable[P, T], *args: P.args, **kwargs: P.kwargs + ) -> None: ... + + +def func1(t: type[TA]) -> TA: ... + + +b = B(func1, A) +reveal_type(b, expected_text="B[(t: type[A]), A]") diff --git a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts index 0a9fd47d7..c8ba668e6 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts @@ -773,6 +773,12 @@ test('Constructor29', () => { TestUtils.validateResults(analysisResults, 0); }); +test('Constructor30', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor30.py']); + + TestUtils.validateResults(analysisResults, 0); +}); + test('ConstructorCallable1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructorCallable1.py']);