Merge pull request #1390 from Microsoft/invertedIncremental

Move code responsible for determining node resuability (in incremental scenarios) to a single location.
This commit is contained in:
CyrusNajmabadi
2014-12-06 01:37:34 -08:00

View File

@@ -354,33 +354,6 @@ module TypeScript.Parser {
return result;
}
function currentNode(): ISyntaxNode {
// If we have any outstanding tokens, then don't reuse a node.
// TODO(cyrusn): This may be too conservative. Perhaps we could reuse hte node and
// attach the skipped tokens in front? For now though, being conservative is nice and
// safe, and likely won't ever affect perf.
if (!skippedTokens) {
var node = source.currentNode();
// We can only reuse a node if it was parsed under the same strict mode that we're
// currently in. i.e. if we originally parsed a node in non-strict mode, but then
// the user added 'using strict' at the top of the file, then we can't use that node
// again as the presense of strict mode may cause us to parse the tokens in the file
// differetly.
//
// Note: we *can* reuse tokens when the strict mode changes. That's because tokens
// are unaffected by strict mode. It's just the parser will decide what to do with it
// differently depending on what mode it is in.
if (node &&
parserContextFlags(node) === contextFlags) {
return node;
}
}
return undefined;
}
function currentToken(): ISyntaxToken {
return source.currentToken();
}
@@ -466,9 +439,10 @@ module TypeScript.Parser {
}
}
function consumeNode(node: ISyntaxNode): void {
function consumeNode(node: ISyntaxNode): ISyntaxNode {
Debug.assert(skippedTokens === undefined);
source.consumeNodeOrToken(node);
return node;
}
//this method is called very frequently
@@ -760,22 +734,12 @@ module TypeScript.Parser {
}
function isModuleElement(inErrorRecovery: boolean): boolean {
if (SyntaxUtilities.isModuleElement(currentNode())) {
return true;
}
var _modifierCount = modifierCount();
return isInterfaceEnumClassModuleImportExportOrTypeAlias(_modifierCount) ||
isStatement(_modifierCount, inErrorRecovery);
}
function tryParseModuleElement(inErrorRecovery: boolean): IModuleElementSyntax {
var node = currentNode();
if (SyntaxUtilities.isModuleElement(node)) {
consumeNode(node);
return <IModuleElementSyntax>node;
}
var _currentToken = currentToken();
var _modifierCount = modifierCount();
@@ -964,11 +928,6 @@ module TypeScript.Parser {
}
function isEnumElement(inErrorRecovery: boolean): boolean {
var node = currentNode();
if (node && node.kind === SyntaxKind.EnumElement) {
return true;
}
return isPropertyName(/*peekToken:*/ 0, inErrorRecovery);
}
@@ -977,12 +936,6 @@ module TypeScript.Parser {
}
function tryParseEnumElement(inErrorRecovery: boolean): EnumElementSyntax {
var node = currentNode();
if (node && node.kind === SyntaxKind.EnumElement) {
consumeNode(node);
return <EnumElementSyntax>node;
}
if (!isPropertyName(/*peekToken:*/ 0, inErrorRecovery)) {
return undefined;
}
@@ -1182,10 +1135,6 @@ module TypeScript.Parser {
}
function isClassElement(inErrorRecovery: boolean): boolean {
if (SyntaxUtilities.isClassElement(currentNode())) {
return true;
}
return isAtModifier() ||
isConstructorDeclaration() ||
isAccessor(inErrorRecovery) ||
@@ -1249,12 +1198,6 @@ module TypeScript.Parser {
}
function tryParseClassElement(inErrorRecovery: boolean): IClassElementSyntax {
var node = currentNode();
if (SyntaxUtilities.isClassElement(node)) {
consumeNode(node);
return <IClassElementSyntax>node;
}
// Have to check for indexers before anything else. That way if we see "[foo:" we
// parse it out as an indexer and not a member function or variable.
var modifiers = parseModifiers();
@@ -1429,10 +1372,6 @@ module TypeScript.Parser {
}
function isTypeMember(inErrorRecovery: boolean): boolean {
if (SyntaxUtilities.isTypeMember(currentNode())) {
return true;
}
return isCallSignature(/*tokenIndex:*/ 0) ||
isConstructSignature() ||
isIndexSignature(/*tokenIndex:*/ 0) ||
@@ -1468,12 +1407,6 @@ module TypeScript.Parser {
}
function tryParseTypeMember(inErrorRecovery: boolean): ITypeMemberSyntax {
var node = currentNode();
if (SyntaxUtilities.isTypeMember(node)) {
consumeNode(node);
return <ITypeMemberSyntax>node;
}
if (isCallSignature(/*tokenIndex:*/ 0)) {
// A call signature for a type member can both use 'yield' as a parameter name, and
// does not have parameter initializers. So we can pass 'false' for both [Yield]
@@ -1708,10 +1641,6 @@ module TypeScript.Parser {
}
function isStatement(modifierCount: number, inErrorRecovery: boolean): boolean {
if (SyntaxUtilities.isStatement(currentNode())) {
return true;
}
if (isDefinitelyNotStatement()) {
return false;
}
@@ -1750,12 +1679,6 @@ module TypeScript.Parser {
}
function tryParseStatement(inErrorRecovery: boolean): IStatementSyntax {
var node = currentNode();
if (SyntaxUtilities.isStatement(node)) {
consumeNode(node);
return <IStatementSyntax><ISyntaxNode>node;
}
var _currentToken = currentToken();
var currentTokenKind = _currentToken.kind;
return tryParseStatementWorker(_currentToken, currentTokenKind, modifierCount(), inErrorRecovery);
@@ -2049,33 +1972,20 @@ module TypeScript.Parser {
}
function isSwitchClause(): boolean {
if (SyntaxUtilities.isSwitchClause(currentNode())) {
return true;
}
var currentTokenKind = currentToken().kind;
return currentTokenKind === SyntaxKind.CaseKeyword || currentTokenKind === SyntaxKind.DefaultKeyword;
}
function tryParseSwitchClause(): ISwitchClauseSyntax {
// Debug.assert(isSwitchClause());
var node = currentNode();
if (SyntaxUtilities.isSwitchClause(node)) {
consumeNode(node);
return <ISwitchClauseSyntax><ISyntaxNode>node;
var _currentToken = currentToken();
switch (_currentToken.kind) {
case SyntaxKind.CaseKeyword:
return parseCaseSwitchClause(_currentToken);
case SyntaxKind.DefaultKeyword:
return parseDefaultSwitchClause(_currentToken);
}
var _currentToken = currentToken();
var kind = _currentToken.kind;
if (kind === SyntaxKind.CaseKeyword) {
return parseCaseSwitchClause(_currentToken);
}
else if (kind === SyntaxKind.DefaultKeyword) {
return parseDefaultSwitchClause(_currentToken);
}
else {
return undefined;
}
return undefined;
}
function parseCaseSwitchClause(caseKeyword: ISyntaxToken): CaseSwitchClauseSyntax {
@@ -2312,44 +2222,10 @@ module TypeScript.Parser {
}
function isVariableDeclarator(): boolean {
var node = currentNode();
if (node && node.kind === SyntaxKind.VariableDeclarator) {
return true;
}
return isIdentifier(currentToken());
}
function canReuseVariableDeclaratorNode(node: ISyntaxNode) {
if (!node || node.kind !== SyntaxKind.VariableDeclarator) {
return false;
}
// Very subtle incremental parsing bug. Consider the following code:
//
// var v = new List < A, B
//
// This is actually legal code. It's a list of variable declarators "v = new List<A"
// on one side and "B" on the other. If you then change that to:
//
// var v = new List < A, B >()
//
// then we have a problem. "v = new List<A" doesn't intersect the change range, so we
// start reparsing at "B" and we completely fail to handle this properly.
//
// In order to prevent this, we do not allow a variable declarator to be reused if it
// has an initializer.
var variableDeclarator = <VariableDeclaratorSyntax>node;
return variableDeclarator.equalsValueClause === undefined;
}
function tryParseVariableDeclarator(): VariableDeclaratorSyntax {
var node = currentNode();
if (canReuseVariableDeclaratorNode(node)) {
consumeNode(node);
return <VariableDeclaratorSyntax>node;
}
if (!isIdentifier(currentToken())) {
return undefined;
}
@@ -4160,10 +4036,6 @@ module TypeScript.Parser {
}
function isParameter(): boolean {
if (currentNode() && currentNode().kind === SyntaxKind.Parameter) {
return true;
}
return isParameterHelper(currentToken());
}
@@ -4181,12 +4053,6 @@ module TypeScript.Parser {
}
function tryParseParameter(): ParameterSyntax {
var node = currentNode();
if (node && node.kind === SyntaxKind.Parameter) {
consumeNode(node);
return <ParameterSyntax>node;
}
var dotDotDotToken = tryEatToken(SyntaxKind.DotDotDotToken);
var modifiers = parseModifiers();
@@ -4654,33 +4520,40 @@ module TypeScript.Parser {
return currentToken().kind === SyntaxKind.FinallyKeyword;
}
function isExpectedListItem(currentListType: ListParsingState, inErrorRecovery: boolean): any {
function isExpectedListItem(currentListType: ListParsingState, inErrorRecovery: boolean): boolean {
// If we're able to parse out a node for the current list type from the old parse tree,
// then definitely have an expected list item.
var node = currentNode(currentListType);
if (node) {
return true;
}
switch (currentListType) {
case ListParsingState.SourceUnit_ModuleElements: return isModuleElement(inErrorRecovery);
case ListParsingState.ClassDeclaration_ClassElements: return isClassElement(inErrorRecovery);
case ListParsingState.ModuleDeclaration_ModuleElements: return isModuleElement(inErrorRecovery);
case ListParsingState.SwitchStatement_SwitchClauses: return isSwitchClause();
case ListParsingState.SwitchClause_Statements: return isStatement(modifierCount(), inErrorRecovery);
case ListParsingState.Block_Statements: return isStatement(modifierCount(), inErrorRecovery);
case ListParsingState.SourceUnit_ModuleElements: return isModuleElement(inErrorRecovery);
case ListParsingState.ClassDeclaration_ClassElements: return isClassElement(inErrorRecovery);
case ListParsingState.ModuleDeclaration_ModuleElements: return isModuleElement(inErrorRecovery);
case ListParsingState.SwitchStatement_SwitchClauses: return isSwitchClause();
case ListParsingState.SwitchClause_Statements: return isStatement(modifierCount(), inErrorRecovery);
case ListParsingState.Block_Statements: return isStatement(modifierCount(), inErrorRecovery);
// These two are special. They're just augmentations of "Block_Statements"
// used so we can abort out of the try block if we see a 'catch' or 'finally'
// keyword. There are no additional list items that they add, so we just
// return 'false' here.
case ListParsingState.TryBlock_Statements: return false;
case ListParsingState.CatchBlock_Statements: return false;
case ListParsingState.EnumDeclaration_EnumElements: return isEnumElement(inErrorRecovery);
case ListParsingState.ObjectType_TypeMembers: return isTypeMember(inErrorRecovery);
case ListParsingState.ClassOrInterfaceDeclaration_HeritageClauses: return isHeritageClause();
case ListParsingState.HeritageClause_TypeNameList: return isHeritageClauseTypeName();
case ListParsingState.VariableDeclaration_VariableDeclarators: return isVariableDeclarator();
case ListParsingState.ArgumentList_AssignmentExpressions: return isExpectedArgumentList_AssignmentExpression();
case ListParsingState.ObjectLiteralExpression_PropertyAssignments: return isPropertyAssignment(inErrorRecovery);
case ListParsingState.TryBlock_Statements: return false;
case ListParsingState.CatchBlock_Statements: return false;
case ListParsingState.EnumDeclaration_EnumElements: return isEnumElement(inErrorRecovery);
case ListParsingState.ObjectType_TypeMembers: return isTypeMember(inErrorRecovery);
case ListParsingState.ClassOrInterfaceDeclaration_HeritageClauses: return isHeritageClause();
case ListParsingState.HeritageClause_TypeNameList: return isHeritageClauseTypeName();
case ListParsingState.VariableDeclaration_VariableDeclarators: return isVariableDeclarator();
case ListParsingState.ArgumentList_AssignmentExpressions: return isExpectedArgumentList_AssignmentExpression();
case ListParsingState.ObjectLiteralExpression_PropertyAssignments: return isPropertyAssignment(inErrorRecovery);
case ListParsingState.ArrayLiteralExpression_AssignmentExpressions: return isAssignmentOrOmittedExpression();
case ListParsingState.ParameterList_Parameters: return isParameter();
case ListParsingState.IndexSignature_Parameters: return isParameter();
case ListParsingState.TypeArgumentList_Types: return isType();
case ListParsingState.TypeParameterList_TypeParameters: return isTypeParameter();
case ListParsingState.TupleType_Types: return isType();
case ListParsingState.ParameterList_Parameters: return isParameter();
case ListParsingState.IndexSignature_Parameters: return isParameter();
case ListParsingState.TypeArgumentList_Types: return isType();
case ListParsingState.TypeParameterList_TypeParameters: return isTypeParameter();
case ListParsingState.TupleType_Types: return isType();
default: throw Errors.invalidOperation();
}
}
@@ -4702,6 +4575,11 @@ module TypeScript.Parser {
}
function tryParseExpectedListItemWorker(currentListType: ListParsingState, inErrorRecovery: boolean): ISyntaxNodeOrToken {
var node = currentNode(currentListType);
if (node) {
return node;
}
switch (currentListType) {
case ListParsingState.SourceUnit_ModuleElements: return tryParseModuleElement(inErrorRecovery);
case ListParsingState.ClassDeclaration_ClassElements: return tryParseClassElement(inErrorRecovery);
@@ -4728,6 +4606,151 @@ module TypeScript.Parser {
}
}
function currentNode(currentListType: ListParsingState): ISyntaxNode {
// If we have any outstanding tokens, then don't reuse a node.
// TODO(cyrusn): This may be too conservative. Perhaps we could reuse hte node and
// attach the skipped tokens in front? For now though, being conservative is nice and
// safe, and likely won't ever affect perf.
if (skippedTokens) {
return undefined;
}
var node = source.currentNode();
if (!node) {
return undefined;
}
// We can only reuse a node if it was parsed under the same strict mode that we're
// currently in. i.e. if we originally parsed a node in non-strict mode, but then
// the user added 'using strict' at the top of the file, then we can't use that node
// again as the presense of strict mode may cause us to parse the tokens in the file
// differetly.
//
// Note: we *can* reuse tokens when the strict mode changes. That's because tokens
// are unaffected by strict mode. It's just the parser will decide what to do with it
// differently depending on what mode it is in.
//
// This also applies to all our other context flags as well.
if (parserContextFlags(node) !== contextFlags) {
return undefined;
}
// Ok, we have a node that looks like it could be reused. Now verify that it is valid
// in the currest list parsing context that we're currently at.
if (!canReuseNode(node, currentListType)) {
return undefined;
}
// It was valid. Let teh source know we're consuming this node, and pass to the list
// parser.
return consumeNode(node);
}
function canReuseNode(node: ISyntaxNode, listParsingState: ListParsingState): boolean {
switch (listParsingState) {
case ListParsingState.SourceUnit_ModuleElements:
return SyntaxUtilities.isModuleElement(node);
case ListParsingState.ClassDeclaration_ClassElements:
return SyntaxUtilities.isClassElement(node);
case ListParsingState.ModuleDeclaration_ModuleElements:
return SyntaxUtilities.isModuleElement(node);
case ListParsingState.SwitchStatement_SwitchClauses:
return SyntaxUtilities.isSwitchClause(node);
case ListParsingState.SwitchClause_Statements:
case ListParsingState.Block_Statements:
case ListParsingState.TryBlock_Statements:
case ListParsingState.CatchBlock_Statements:
return SyntaxUtilities.isStatement(node);
case ListParsingState.EnumDeclaration_EnumElements:
return node.kind === SyntaxKind.EnumElement;
case ListParsingState.ObjectType_TypeMembers:
return SyntaxUtilities.isTypeMember(node);
case ListParsingState.VariableDeclaration_VariableDeclarators:
return canReuseVariableDeclaratorNode(node);
case ListParsingState.ParameterList_Parameters:
return node.kind === SyntaxKind.Parameter;
case ListParsingState.IndexSignature_Parameters:
return node.kind === SyntaxKind.Parameter;
// Any other lists we do not care about reusing nodes in. But feel free to add if
// you can do so safely. Danger areas involve nodes that may involve speculative
// parsing. If speculative parsing is involved with the node, then the range the
// parser reached while looking ahead might be in the edited range (see the example
// in canReuseVariableDeclaratorNode for a good case of this).
case ListParsingState.ClassOrInterfaceDeclaration_HeritageClauses:
// This would probably be safe to reuse. There is no speculative parsing with
// heritage clauses.
case ListParsingState.HeritageClause_TypeNameList:
// This would probably be safe to reuse. There is no speculative parsing with
// type names in a heritage clause. There can be generic names in the type
// name list. But because it is a type context, we never use speculative
// parsing on the type argument list.
case ListParsingState.TypeParameterList_TypeParameters:
// This would probably be safe to reuse. There is no speculative parsing with
// type parameters. Note that that's because type *parameters* only occur in
// unambiguous *type* contexts. While type *arguments* occur in very ambiguous
// *expression* contexts.
case ListParsingState.TupleType_Types:
// This would probably be safe to reuse. There is no speculative parsing with
// tuple types.
// Technically, type argument list types are probably safe to reuse. While
// speculative parsing is involved with them (since type argument lists are only
// produced from speculative parsing a < as a type argument list), we only have
// the types because speculative parsing succeeded. Thus, the lookahead never
// went past the end of the list and rewound.
case ListParsingState.TypeArgumentList_Types:
// Note: these are almost certainly not safe to ever reuse. Expressions commonly
// need a large amount of lookahead, and we should not reuse them as they may
// have actually intersected the edit.
case ListParsingState.ArgumentList_AssignmentExpressions:
case ListParsingState.ArrayLiteralExpression_AssignmentExpressions:
// This is not safe to reuse for the same reason as the 'AssignmentExpression'
// cases. i.e. a property assignment may end with an expression, and thus might
// have lookahead far beyond it's old node.
case ListParsingState.ObjectLiteralExpression_PropertyAssignments:
}
return false;
}
function canReuseVariableDeclaratorNode(node: ISyntaxNode) {
if (!node || node.kind !== SyntaxKind.VariableDeclarator) {
return false;
}
// Very subtle incremental parsing bug. Consider the following code:
//
// var v = new List < A, B
//
// This is actually legal code. It's a list of variable declarators "v = new List<A"
// on one side and "B" on the other. If you then change that to:
//
// var v = new List < A, B >()
//
// then we have a problem. "v = new List<A" doesn't intersect the change range, so we
// start reparsing at "B" and we completely fail to handle this properly.
//
// In order to prevent this, we do not allow a variable declarator to be reused if it
// has an initializer.
var variableDeclarator = <VariableDeclaratorSyntax>node;
return variableDeclarator.equalsValueClause === undefined;
}
function getExpectedListElementType(currentListType: ListParsingState): string {
switch (currentListType) {
case ListParsingState.SourceUnit_ModuleElements: return getLocalizedText(DiagnosticCode.module_class_interface_enum_import_or_statement, undefined);