Skip to content

Commit f99bc15

Browse files
Merge pull request microsoft#1369 from Microsoft/parserErrors2
Track if the parser encountered any errors as a bit in the next node that is produced.
2 parents fc47fc3 + 15e6b64 commit f99bc15

2 files changed

Lines changed: 98 additions & 15 deletions

File tree

src/compiler/parser.ts

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@ module ts {
99
return node.end - node.pos;
1010
}
1111

12+
function hasFlag(val: number, flag: number): boolean {
13+
return (val & flag) !== 0;
14+
}
15+
16+
// Returns true if this node contains a parse error anywhere underneath it.
17+
export function containsParseError(node: Node): boolean {
18+
if (!hasFlag(node.parserContextFlags, ParserContextFlags.HasPropagatedChildContainsErrorFlag)) {
19+
// A node is considered to contain a parse error if:
20+
// a) the parser explicitly marked that it had an error
21+
// b) any of it's children reported that it had an error.
22+
var val = hasFlag(node.parserContextFlags, ParserContextFlags.ContainsError) ||
23+
forEachChild(node, containsParseError);
24+
25+
// If so, mark ourselves accordingly.
26+
if (val) {
27+
node.parserContextFlags |= ParserContextFlags.ContainsError;
28+
}
29+
30+
// Also mark that we've propogated the child information to this node. This way we can
31+
// always consult the bit directly on this node without needing to check its children
32+
// again.
33+
node.parserContextFlags |= ParserContextFlags.HasPropagatedChildContainsErrorFlag;
34+
}
35+
36+
return hasFlag(node.parserContextFlags, ParserContextFlags.ContainsError);
37+
}
38+
1239
export function getNodeConstructor(kind: SyntaxKind): new () => Node {
1340
return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind));
1441
}
@@ -1014,6 +1041,35 @@ module ts {
10141041
// descent parsing and unwinding.
10151042
var contextFlags: ParserContextFlags = 0;
10161043

1044+
// Whether or not we've had a parse error since creating the last AST node. If we have
1045+
// encountered an error, it will be stored on the next AST node we create. Parse errors
1046+
// can be broken down into three categories:
1047+
//
1048+
// 1) An error that occurred during scanning. For example, an unterminated literal, or a
1049+
// character that was completely not understood.
1050+
//
1051+
// 2) A token was expected, but was not present. This type of error is commonly produced
1052+
// by the 'parseExpected' function.
1053+
//
1054+
// 3) A token was present that no parsing function was able to consume. This type of error
1055+
// only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser
1056+
// decides to skip the token.
1057+
//
1058+
// In all of these cases, we want to mark the next node as having had an error before it.
1059+
// With this mark, we can know in incremental settings if this node can be reused, or if
1060+
// we have to reparse it. If we don't keep this information around, we may just reuse the
1061+
// node. in that event we would then not produce the same errors as we did before, causing
1062+
// significant confusion problems.
1063+
//
1064+
// Note: it is necessary that this value be saved/restored during speculative/lookahead
1065+
// parsing. During lookahead parsing, we will often create a node. That node will have
1066+
// this value attached, and then this value will be set back to 'false'. If we decide to
1067+
// rewind, we must get back to the same value we had prior to the lookahead.
1068+
//
1069+
// Note: any errors at the end of the file that do not precede a regular node, should get
1070+
// attached to the EOF token.
1071+
var parseErrorBeforeNextFinishedNode = false;
1072+
10171073
function setContextFlag(val: Boolean, flag: ParserContextFlags) {
10181074
if (val) {
10191075
contextFlags |= flag;
@@ -1115,23 +1171,23 @@ module ts {
11151171
return getPositionFromLineAndCharacter(getLineStarts(), line, character);
11161172
}
11171173

1118-
function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): void {
1174+
function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void {
11191175
var start = scanner.getTokenPos();
11201176
var length = scanner.getTextPos() - start;
11211177

1122-
parseErrorAtPosition(start, length, message, arg0, arg1, arg2);
1178+
parseErrorAtPosition(start, length, message, arg0);
11231179
}
11241180

1125-
function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): void {
1126-
var lastErrorPosition = sourceFile.parseDiagnostics.length
1127-
? sourceFile.parseDiagnostics[sourceFile.parseDiagnostics.length - 1].start
1128-
: -1;
1129-
1181+
function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void {
11301182
// Don't report another error if it would just be at the same position as the last error.
1131-
if (start !== lastErrorPosition) {
1132-
var diagnostic = createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2);
1133-
sourceFile.parseDiagnostics.push(diagnostic);
1183+
var lastError = lastOrUndefined(sourceFile.parseDiagnostics);
1184+
if (!lastError || start !== lastError.start) {
1185+
sourceFile.parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0));
11341186
}
1187+
1188+
// Mark that we've encountered an error. We'll set an appropriate bit on the next
1189+
// node we finish so that it can't be reused incrementally.
1190+
parseErrorBeforeNextFinishedNode = true;
11351191
}
11361192

11371193
function scanError(message: DiagnosticMessage) {
@@ -1172,8 +1228,9 @@ module ts {
11721228
// caller asked us to always reset our state).
11731229
var saveToken = token;
11741230
var saveParseDiagnosticsLength = sourceFile.parseDiagnostics.length;
1231+
var saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
11751232

1176-
// Note: it is not actually necessary to save/restore the context falgs here. That's
1233+
// Note: it is not actually necessary to save/restore the context flags here. That's
11771234
// because the saving/restorating of these flags happens naturally through the recursive
11781235
// descent nature of our parser. However, we still store this here just so we can
11791236
// assert that that invariant holds.
@@ -1193,6 +1250,7 @@ module ts {
11931250
if (!result || isLookAhead) {
11941251
token = saveToken;
11951252
sourceFile.parseDiagnostics.length = saveParseDiagnosticsLength;
1253+
parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode;
11961254
}
11971255

11981256
return result;
@@ -1301,6 +1359,14 @@ module ts {
13011359
node.parserContextFlags = contextFlags;
13021360
}
13031361

1362+
// Keep track on the node if we encountered an error while parsing it. If we did, then
1363+
// we cannot reuse the node incrementally. Once we've marked this node, clear out the
1364+
// flag so that we don't mark any subsequent nodes.
1365+
if (parseErrorBeforeNextFinishedNode) {
1366+
parseErrorBeforeNextFinishedNode = false;
1367+
node.parserContextFlags |= ParserContextFlags.ContainsError;
1368+
}
1369+
13041370
return node;
13051371
}
13061372

src/compiler/types.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,27 @@ module ts {
294294
export const enum ParserContextFlags {
295295
// Set if this node was parsed in strict mode. Used for grammar error checks, as well as
296296
// checking if the node can be reused in incremental settings.
297-
StrictMode = 1 << 0,
298-
DisallowIn = 1 << 1,
299-
Yield = 1 << 2,
300-
GeneratorParameter = 1 << 3,
297+
StrictMode = 1 << 0,
298+
299+
// If this node was parsed in a context where 'in-expressions' are not allowed.
300+
DisallowIn = 1 << 1,
301+
302+
// If this node was parsed in the 'yield' context created when parsing a generator.
303+
Yield = 1 << 2,
304+
305+
// If this node was parsed in the parameters of a generator.
306+
GeneratorParameter = 1 << 3,
307+
308+
// If the parser encountered an error when parsing the code that created this node. Note
309+
// the parser only sets this directly on the node it creates right after encountering the
310+
// error. We then propagate that flag upwards to parent nodes during incremental parsing.
311+
ContainsError = 1 << 4,
312+
313+
// Used during incremental parsing to determine if we need to visit this node to see if
314+
// any of its children had an error. Once we compute that once, we can set this bit on the
315+
// node to know that we never have to do it again. From that point on, we can just check
316+
// the node directly for 'ContainsError'.
317+
HasPropagatedChildContainsErrorFlag = 1 << 5
301318
}
302319

303320
export interface Node extends TextRange {

0 commit comments

Comments
 (0)