mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Added logic to differentiate between "may break" and "always breaks" within a scope. Added reporting of unreachable code after an "always breaks" condition is detected within a loop.
This commit is contained in:
parent
8a61caaeb6
commit
8cac3dec0c
@ -87,7 +87,11 @@ export class Scope {
|
||||
|
||||
// Tracks whether a "break" statement was executed within
|
||||
// the loop.
|
||||
private _breaksFromLoop = false;
|
||||
private _mayBreak = false;
|
||||
|
||||
// Tracks whether a "break" statement is always executed
|
||||
// along all paths within this scope.
|
||||
private _alwaysBreaks = false;
|
||||
|
||||
// Inferred return and yield types for the scope.
|
||||
private _returnType = new InferredType();
|
||||
@ -222,8 +226,14 @@ export class Scope {
|
||||
// If the scope we're merging isn't a looping scope,
|
||||
// transfer the break to the this scope. This allows the break
|
||||
// to propagate to the nearest looping scope but no further.
|
||||
if (!scopeToMerge._isLooping && scopeToMerge._breaksFromLoop) {
|
||||
this._breaksFromLoop = true;
|
||||
if (!scopeToMerge._isLooping) {
|
||||
if (scopeToMerge._mayBreak) {
|
||||
this._mayBreak = true;
|
||||
}
|
||||
|
||||
if (scopeToMerge._alwaysBreaks) {
|
||||
this._alwaysBreaks = true;
|
||||
}
|
||||
}
|
||||
|
||||
const typeConstraints = TypeConstraintUtils.dedupeTypeConstraints(
|
||||
@ -251,12 +261,18 @@ export class Scope {
|
||||
contributingTypeConstraints.map(s => s.getTypeConstraints()));
|
||||
combinedScope.addTypeConstraints(combinedTypeConstraints);
|
||||
|
||||
// Combine the return and yield types.
|
||||
// Combine the return and yield types and break flags.
|
||||
combinedScope._alwaysBreaks = scopes.length > 0;
|
||||
for (let scope of scopes) {
|
||||
combinedScope._returnType.addSources(scope._returnType);
|
||||
combinedScope._yieldType.addSources(scope._yieldType);
|
||||
if (scope._breaksFromLoop) {
|
||||
combinedScope._breaksFromLoop = true;
|
||||
|
||||
if (scope._mayBreak) {
|
||||
combinedScope._mayBreak = true;
|
||||
}
|
||||
|
||||
if (!scope._alwaysRaises && !scope._alwaysReturns && !scope._alwaysBreaks) {
|
||||
combinedScope._alwaysBreaks = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,12 +315,20 @@ export class Scope {
|
||||
return this._alwaysReturns || this._alwaysRaises;
|
||||
}
|
||||
|
||||
setBreaksFromLoop() {
|
||||
this._breaksFromLoop = true;
|
||||
setMayBreak() {
|
||||
this._mayBreak = true;
|
||||
}
|
||||
|
||||
getBreaksFromLoop() {
|
||||
return this._breaksFromLoop;
|
||||
getMayBreak() {
|
||||
return this._mayBreak;
|
||||
}
|
||||
|
||||
setAlwaysBreaks() {
|
||||
this._alwaysBreaks = true;
|
||||
}
|
||||
|
||||
getAlwaysBreaks() {
|
||||
return this._alwaysBreaks;
|
||||
}
|
||||
|
||||
getTypeConstraints() {
|
||||
|
@ -606,7 +606,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
}, true);
|
||||
|
||||
if (loopScope.getAlwaysReturnsOrRaises() && elseScope.getAlwaysReturnsOrRaises()) {
|
||||
// If both an loop and else clauses are executed but they both return or
|
||||
// If both loop and else clauses are executed but they both return or
|
||||
// raise an exception, mark the current scope as always returning or
|
||||
// raising an exception.
|
||||
if (loopScope.getAlwaysRaises() && elseScope.getAlwaysRaises()) {
|
||||
@ -837,7 +837,8 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
visitBreak(node: BreakNode): boolean {
|
||||
this._currentScope.setBreaksFromLoop();
|
||||
this._currentScope.setMayBreak();
|
||||
this._currentScope.setAlwaysBreaks();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1485,7 +1486,10 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
node.statements.forEach((statement, index) => {
|
||||
this.walk(statement);
|
||||
|
||||
if (this._currentScope.getAlwaysRaises() || this._currentScope.getAlwaysReturns()) {
|
||||
if (this._currentScope.getAlwaysRaises() ||
|
||||
this._currentScope.getAlwaysReturns() ||
|
||||
this._currentScope.getAlwaysBreaks()) {
|
||||
|
||||
if (!reportedUnreachableCode) {
|
||||
if (index < node.statements.length - 1) {
|
||||
// Create a text range that covers the next statement through
|
||||
@ -2293,8 +2297,10 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
}
|
||||
}
|
||||
|
||||
let ifContributions = !ifScope.getAlwaysReturnsOrRaises() && !isElseUnconditional ? ifScope : undefined;
|
||||
let elseContributions = !elseScope.getAlwaysReturnsOrRaises() && !isIfUnconditional ? elseScope : undefined;
|
||||
const ifContributions = !ifScope.getAlwaysReturnsOrRaises() &&
|
||||
!isElseUnconditional ? ifScope : undefined;
|
||||
const elseContributions = !elseScope.getAlwaysReturnsOrRaises() &&
|
||||
!isIfUnconditional ? elseScope : undefined;
|
||||
|
||||
// Figure out how to combine the scopes.
|
||||
if (ifContributions && elseContributions) {
|
||||
@ -2321,7 +2327,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
}
|
||||
}
|
||||
|
||||
if (isIfUnconditional && isWhile && !ifScope.getBreaksFromLoop()) {
|
||||
if (isIfUnconditional && isWhile && !ifScope.getMayBreak()) {
|
||||
// If this is an infinite loop, mark it as always raising
|
||||
// So we don't assume that we'll fall through and possibly
|
||||
// return None at the end of the function.
|
||||
|
Loading…
Reference in New Issue
Block a user