From 1976f0de2efaabecbd4240a718a4cad219ca3011 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 4 Dec 2014 10:19:57 -0800 Subject: [PATCH 1/2] Track if the parser encountered any errors as a bit in the next node that is produced. --- src/compiler/parser.ts | 59 +++++++++++++++++++++++++++++++++++------- src/compiler/types.ts | 25 +++++++++++++++--- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 58c715d721f..406bdabc7d7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1007,6 +1007,35 @@ module ts { // understand when these values should be changed versus when they should be inherited. var contextFlags: ParserContextFlags = 0; + // Whether or not we've had a parse error since creating the last AST node. If we have + // encountered an error, it will be stored on the next AST node we create. Parse errors + // can be broken down into three categories: + // + // 1) An error that occurred during scanning. For example, an unterminated literal, or a + // character that was completely not understood. + // + // 2) A token was expected, but was not present. This type of error is commonly produced + // by the 'parseExpected' function. + // + // 3) A token was present that no parsing function was able to consume. This type of error + // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser + // decides to skip the token. + // + // In all of these cases, we want to mark the next node as having had an error before it. + // With this mark, we can know in incremental settings if this node can be reused, or if + // we have to reparse it. If we don't keep this information around, we may just reuse the + // node. in that event we would then not produce the same errors as we did before, causing + // significant confusion problems. + // + // Note: it is necessary that this value be saved/restored during speculative/lookahead + // parsing. During lookahead parsing, we will often create a node. That node will have + // this value attached, and then this value will be set back to 'false'. If we decide to + // rewind, we must get back to the same value we had prior to the lookahead. + // + // Note: any errors at the end of the file that do not precede a regular node, should get + // attached to the EOF token. + var parseErrorBeforeNextFinishedNode = false; + function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { contextFlags |= flag; @@ -1108,24 +1137,24 @@ module ts { return getPositionFromLineAndCharacter(getLineStarts(), line, character); } - function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): void { + function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { var start = scanner.getTokenPos(); var length = scanner.getTextPos() - start; - parseErrorAtPosition(start, length, message, arg0, arg1, arg2); + parseErrorAtPosition(start, length, message, arg0); } - function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): void { - var lastErrorPosition = sourceFile.parseDiagnostics.length - ? sourceFile.parseDiagnostics[sourceFile.parseDiagnostics.length - 1].start - : -1; - + function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { // Don't report another error if it would just be at the same position as the last error. - if (start !== lastErrorPosition) { - var diagnostic = createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2); - sourceFile.parseDiagnostics.push(diagnostic); + var lastError = lastOrUndefined(sourceFile.parseDiagnostics); + if (!lastError || start !== lastError.start) { + sourceFile.parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0)); } + // Mark that we've encountered an error. We'll set an appropriate bit on the next + // node we finish so that it can't be reused incrementally. + parseErrorBeforeNextFinishedNode = true; + if (lookAheadMode === LookAheadMode.NoErrorYet) { lookAheadMode = LookAheadMode.Error; } @@ -1173,6 +1202,7 @@ module ts { // caller asked us to always reset our state). var saveToken = token; var saveSyntacticErrorsLength = sourceFile.parseDiagnostics.length; + var saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; // Keep track of the current look ahead mode (this matters if we have nested // speculative parsing). @@ -1195,6 +1225,7 @@ module ts { if (!result || alwaysResetState) { token = saveToken; sourceFile.parseDiagnostics.length = saveSyntacticErrorsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; } return result; @@ -1309,6 +1340,14 @@ module ts { node.parserContextFlags = contextFlags; } + // Keep track on the node if we encountered an error while parsing it. If we did, then + // we cannot reuse the node incrementally. Once we've marked this node, clear out the + // flag so that we don't mark any subsequent nodes. + if (parseErrorBeforeNextFinishedNode) { + parseErrorBeforeNextFinishedNode = false; + node.parserContextFlags |= ParserContextFlags.ContainsError; + } + return node; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ab396d13a85..a5cdcb6b24e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -294,10 +294,27 @@ module ts { export const enum ParserContextFlags { // Set if this node was parsed in strict mode. Used for grammar error checks, as well as // checking if the node can be reused in incremental settings. - StrictMode = 1 << 0, - DisallowIn = 1 << 1, - Yield = 1 << 2, - GeneratorParameter = 1 << 3, + StrictMode = 1 << 0, + + // If this node was parsed in a context where 'in-expressions' are not allowed. + DisallowIn = 1 << 1, + + // If this node was parsed in the 'yield' context created when parsing a generator. + Yield = 1 << 2, + + // If this node was parsed in the parameters of a generator. + GeneratorParameter = 1 << 3, + + // If the parser encountered an error when parsing the code that created this node. Note + // the parser only sets this directly on the node it creates right after encountering the + // error. We then propagate that flag upwards to parent nodes during incremental parsing. + ContainsError = 1 << 4, + + // Used during incremental parsing to determine if we need to visit this node to see if + // any of its children had an error. Once we compute that once, we can set this bit on the + // node to know that we never have to do it again. From that point on, we can just check + // the node directly for 'ContainsError'. + HasPropagatedChildContainsErrorFlag = 1 << 5 } export interface Node extends TextRange { From 8756142e25e2ac3a54ae11c56b47eef48cc779fe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 4 Dec 2014 10:36:18 -0800 Subject: [PATCH 2/2] Provide utility function to report back if a node contained a parse error anywhere inside of it. --- src/compiler/parser.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 406bdabc7d7..33a8bbe3177 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -9,6 +9,33 @@ module ts { return node.end - node.pos; } + function hasFlag(val: number, flag: number): boolean { + return (val & flag) !== 0; + } + + // Returns true if this node contains a parse error anywhere underneath it. + export function containsParseError(node: Node): boolean { + if (!hasFlag(node.parserContextFlags, ParserContextFlags.HasPropagatedChildContainsErrorFlag)) { + // A node is considered to contain a parse error if: + // a) the parser explicitly marked that it had an error + // b) any of it's children reported that it had an error. + var val = hasFlag(node.parserContextFlags, ParserContextFlags.ContainsError) || + forEachChild(node, containsParseError); + + // If so, mark ourselves accordingly. + if (val) { + node.parserContextFlags |= ParserContextFlags.ContainsError; + } + + // Also mark that we've propogated the child information to this node. This way we can + // always consult the bit directly on this node without needing to check its children + // again. + node.parserContextFlags |= ParserContextFlags.HasPropagatedChildContainsErrorFlag; + } + + return hasFlag(node.parserContextFlags, ParserContextFlags.ContainsError); + } + export function getNodeConstructor(kind: SyntaxKind): new () => Node { return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)); }