From e65ac3854d633b5f782dca0a41553235972652f5 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 7 Apr 2024 11:36:31 -0700 Subject: [PATCH] Fixed a bug that results in a false negative when a generic function returns a Callable type that is specialized to include a live (in-scope) type variable. This addresses #7542. (#7634) --- .../src/analyzer/typeEvaluator.ts | 10 +++++++-- .../src/tests/samples/solver10.py | 6 +++--- .../src/tests/samples/solver34.py | 21 +++++++++++++++++++ .../src/tests/typeEvaluator2.test.ts | 6 ++++++ 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/solver34.py diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 162121de7..f75a64d37 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -11541,9 +11541,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions } } + const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode); specializedReturnType = adjustCallableReturnType( type, specializedReturnType, + liveTypeVarScopes, signatureTracker.getTrackedSignatures() ); @@ -11612,10 +11614,14 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions function adjustCallableReturnType( callableType: FunctionType, returnType: Type, + liveTypeVarScopes: TypeVarScopeId[], trackedSignatures?: SignatureWithOffsets[] ): Type { if (isFunction(returnType) && !returnType.details.name && callableType.details.typeVarScopeId) { - const typeVarsInReturnType = getTypeVarArgumentsRecursive(returnType); + // What type variables are referenced in the callable return type? Do not include any live type variables. + const typeVarsInReturnType = getTypeVarArgumentsRecursive(returnType).filter( + (t) => !liveTypeVarScopes.some((scopeId) => t.scopeId === scopeId) + ); // If there are no unsolved type variables, we're done. If there are // unsolved type variables, treat them as though they are rescoped @@ -21727,7 +21733,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions ) { const specializedReturnType = FunctionType.getSpecializedReturnType(type, /* includeInferred */ false); if (specializedReturnType && !isUnknown(specializedReturnType)) { - return adjustCallableReturnType(type, specializedReturnType, /* trackedSignatures */ undefined); + return adjustCallableReturnType(type, specializedReturnType, /* liveTypeVarScopes */ []); } if (inferTypeIfNeeded) { diff --git a/packages/pyright-internal/src/tests/samples/solver10.py b/packages/pyright-internal/src/tests/samples/solver10.py index 068f41879..d7b6dc798 100644 --- a/packages/pyright-internal/src/tests/samples/solver10.py +++ b/packages/pyright-internal/src/tests/samples/solver10.py @@ -17,9 +17,9 @@ def extend_if(xs: list[_T], ys: list[tuple[_T, bool]]) -> list[_T]: extend_if(["foo"], [("bar", True), ("baz", True)]) -def Return(value: _T) -> Callable[[_T], None]: +def func1(value: _T) -> Callable[[_T], None]: ... -def func1() -> Callable[[bool], None]: - return Return(True) +def func2() -> Callable[[bool], None]: + return func1(True) diff --git a/packages/pyright-internal/src/tests/samples/solver34.py b/packages/pyright-internal/src/tests/samples/solver34.py new file mode 100644 index 000000000..7af73f079 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/solver34.py @@ -0,0 +1,21 @@ +# This sample tests the case where a generic function returns a Callable type +# that is specialized with unsolved type variables. + +from collections.abc import Container +from typing import TypeVar, Callable + + +T = TypeVar("T") +VT = TypeVar("VT") + + +def func1(container: Container[T]) -> Callable[[T], bool]: ... + + +def func2(a: T, b: Container[VT]) -> T: + cmp = func1(b) + + # This should generate an error. + cmp(a) + + return a diff --git a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts index 33d13abea..5f46cc8c4 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts @@ -747,6 +747,12 @@ test('Solver33', () => { TestUtils.validateResults(analysisResults, 0); }); +test('Solver34', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['solver34.py']); + + TestUtils.validateResults(analysisResults, 1); +}); + test('SolverScoring1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['solverScoring1.py']);