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:
Eric Traut 2019-08-09 22:20:55 -07:00
parent 8a61caaeb6
commit 8cac3dec0c
2 changed files with 46 additions and 16 deletions

View File

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

View File

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