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)

This commit is contained in:
Eric Traut 2024-04-07 11:36:31 -07:00 committed by GitHub
parent 3b70417dd5
commit e65ac3854d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 5 deletions

View File

@ -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) {

View File

@ -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)

View File

@ -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

View File

@ -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']);