Fixed a bug that results in spurious "variable not allowed in type expression" errors when a type alias is used in a loop in both a type expression and a value expression. This addresses #9017. (#9018)

This commit is contained in:
Eric Traut 2024-09-17 10:01:45 -07:00 committed by GitHub
parent e353c9dec5
commit c20e613319
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 2 deletions

View File

@ -401,6 +401,28 @@ export function getCodeFlowEngine(
flowNodeTypeCache.cache.delete(flowNode.id);
}
// Cleans any "incomplete unknowns" from the specified set of entries
// to compute the final type.
function cleanIncompleteUnknownForCacheEntry(cacheEntry: FlowNodeTypeResult): Type | undefined {
if (!cacheEntry.type) {
return undefined;
}
if (!cacheEntry.incompleteSubtypes || cacheEntry.incompleteSubtypes.length === 0) {
return cleanIncompleteUnknown(cacheEntry.type);
}
const typesToCombine: Type[] = [];
cacheEntry.incompleteSubtypes?.forEach((entry) => {
if (entry.type && !isIncompleteUnknown(entry.type)) {
typesToCombine.push(cleanIncompleteUnknown(entry.type));
}
});
return combineTypes(typesToCombine);
}
function evaluateAssignmentFlowNode(flowNode: FlowAssignment): TypeResult | undefined {
// For function and class nodes, the reference node is the name
// node, but we need to use the parent node (the FunctionNode or ClassNode)
@ -456,7 +478,7 @@ export function getCodeFlowEngine(
// has changed that may cause the previously-reported incomplete type to change.
if (cachedEntry.generationCount === flowIncompleteGeneration) {
return FlowNodeTypeResult.create(
cachedEntry.type ? cleanIncompleteUnknown(cachedEntry.type) : undefined,
cleanIncompleteUnknownForCacheEntry(cachedEntry),
/* isIncomplete */ true
);
}
@ -966,7 +988,7 @@ export function getCodeFlowEngine(
// that have not been evaluated even once, treat it as incomplete. We clean
// any incomplete unknowns from the type here to assist with type convergence.
return FlowNodeTypeResult.create(
cacheEntry.type ? cleanIncompleteUnknown(cacheEntry.type) : undefined,
cleanIncompleteUnknownForCacheEntry(cacheEntry),
/* isIncomplete */ true
);
}

View File

@ -0,0 +1,15 @@
# This sample tests the case where a type alias is accessed within
# a loop as both an annotation and a value expression.
from typing import Literal
TA1 = Literal["a", "b"]
def func1(values: list):
for value in values:
x: TA1 = value["x"]
if x not in TA1.__args__:
raise ValueError()

View File

@ -473,6 +473,12 @@ test('Loop49', () => {
TestUtils.validateResults(analysisResults, 0);
});
test('Loop50', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['loop50.py']);
TestUtils.validateResults(analysisResults, 0);
});
test('ForLoop1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['forLoop1.py']);