mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-07 05:17:10 +03:00
Fixed bug that resulted in false positive when a variable was modified in a loop that employed conditional type narrowing and was also used as a member access expression.
This commit is contained in:
parent
9e96b5858d
commit
c938d54efa
@ -699,10 +699,12 @@ export function getCodeFlowEngine(
|
||||
// If there is an "Unknown" type within a union type, remove
|
||||
// it. Otherwise we might end up resolving the cycle with a type
|
||||
// that includes an undesirable unknown.
|
||||
// Note that we return isIncomplete = false here but do not
|
||||
// save the cached entry for next time. This is because the
|
||||
return {
|
||||
type: cacheEntry?.type ? removeUnknownFromUnion(cacheEntry.type) : undefined,
|
||||
usedOuterScopeAlias: loopUsedOuterScopeAlias,
|
||||
isIncomplete: true,
|
||||
isIncomplete: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -4309,6 +4309,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
{
|
||||
type: subtype,
|
||||
node,
|
||||
isIncomplete: baseTypeResult.isIncomplete,
|
||||
},
|
||||
usage,
|
||||
EvaluatorFlags.None
|
||||
@ -4365,34 +4366,37 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
let diagMessage = Localizer.Diagnostic.memberAccess();
|
||||
if (usage.method === 'set') {
|
||||
diagMessage = Localizer.Diagnostic.memberSet();
|
||||
} else if (usage.method === 'del') {
|
||||
diagMessage = Localizer.Diagnostic.memberDelete();
|
||||
}
|
||||
|
||||
// If there is an expected type diagnostic addendum (used for assignments),
|
||||
// use that rather than the local diagnostic addendum because it will be
|
||||
// more informative.
|
||||
if (usage.setExpectedTypeDiag) {
|
||||
diag = usage.setExpectedTypeDiag;
|
||||
}
|
||||
|
||||
const isFunctionRule =
|
||||
isFunction(baseType) ||
|
||||
isOverloadedFunction(baseType) ||
|
||||
(isClassInstance(baseType) && ClassType.isBuiltIn(baseType, 'function'));
|
||||
const [ruleSet, rule] = isFunctionRule
|
||||
? [fileInfo.diagnosticRuleSet.reportFunctionMemberAccess, DiagnosticRule.reportFunctionMemberAccess]
|
||||
: [fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues];
|
||||
|
||||
addDiagnostic(
|
||||
ruleSet,
|
||||
rule,
|
||||
diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(),
|
||||
node.memberName
|
||||
);
|
||||
if (!baseTypeResult.isIncomplete) {
|
||||
let diagMessage = Localizer.Diagnostic.memberAccess();
|
||||
if (usage.method === 'set') {
|
||||
diagMessage = Localizer.Diagnostic.memberSet();
|
||||
} else if (usage.method === 'del') {
|
||||
diagMessage = Localizer.Diagnostic.memberDelete();
|
||||
}
|
||||
|
||||
// If there is an expected type diagnostic addendum (used for assignments),
|
||||
// use that rather than the local diagnostic addendum because it will be
|
||||
// more informative.
|
||||
if (usage.setExpectedTypeDiag) {
|
||||
diag = usage.setExpectedTypeDiag;
|
||||
}
|
||||
|
||||
const [ruleSet, rule] = isFunctionRule
|
||||
? [fileInfo.diagnosticRuleSet.reportFunctionMemberAccess, DiagnosticRule.reportFunctionMemberAccess]
|
||||
: [fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues];
|
||||
|
||||
addDiagnostic(
|
||||
ruleSet,
|
||||
rule,
|
||||
diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(),
|
||||
node.memberName
|
||||
);
|
||||
}
|
||||
|
||||
// If this is member access on a function, use "Any" so if the
|
||||
// reportFunctionMemberAccess rule is disabled, we don't trigger
|
||||
|
22
packages/pyright-internal/src/tests/samples/loops14.py
Normal file
22
packages/pyright-internal/src/tests/samples/loops14.py
Normal file
@ -0,0 +1,22 @@
|
||||
# This sample tests a loop that modifies a variable through type narrowing.
|
||||
|
||||
from typing import Literal, Union
|
||||
|
||||
|
||||
class State:
|
||||
def confirm_dialog(self) -> Union["State", bool]:
|
||||
return False
|
||||
|
||||
|
||||
state = State()
|
||||
t0: Literal["State"] = reveal_type(state)
|
||||
|
||||
for _ in range(1):
|
||||
result = state.confirm_dialog()
|
||||
if isinstance(result, State):
|
||||
t1: Literal["State"] = reveal_type(state)
|
||||
t2: Literal["State"] = reveal_type(result)
|
||||
state = result
|
||||
else:
|
||||
t3: Literal["State"] = reveal_type(state)
|
||||
t4: Literal["bool"] = reveal_type(result)
|
@ -204,6 +204,12 @@ test('Loops13', () => {
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('Loops14', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['loops14.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('ForLoop1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['forLoop1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user