Merge pull request #1369 from Microsoft/parserErrors2

Track if the parser encountered any errors as a bit in the next node that is produced.
This commit is contained in:
CyrusNajmabadi 2014-12-04 13:37:50 -08:00
commit f99bc15783
2 changed files with 98 additions and 15 deletions

View file

@ -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));
}
@ -1014,6 +1041,35 @@ module ts {
// descent parsing and unwinding.
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;
@ -1115,23 +1171,23 @@ 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;
}
function scanError(message: DiagnosticMessage) {
@ -1172,8 +1228,9 @@ module ts {
// caller asked us to always reset our state).
var saveToken = token;
var saveParseDiagnosticsLength = sourceFile.parseDiagnostics.length;
var saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
// Note: it is not actually necessary to save/restore the context falgs here. That's
// Note: it is not actually necessary to save/restore the context flags here. That's
// because the saving/restorating of these flags happens naturally through the recursive
// descent nature of our parser. However, we still store this here just so we can
// assert that that invariant holds.
@ -1193,6 +1250,7 @@ module ts {
if (!result || isLookAhead) {
token = saveToken;
sourceFile.parseDiagnostics.length = saveParseDiagnosticsLength;
parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode;
}
return result;
@ -1301,6 +1359,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;
}

View file

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