Implement recovery for incomplete expression before declaration

It's needed when declarations are parsed as a part of previous expression
(see tests)

Currently we apply this kind of recovery in a conservative way,
only when declaration starts at the next line, and while
the condition could be relaxed, there's no need to do this

 #KT-4948 Fixed
 #KT-7118 Fixed
This commit is contained in:
Denis Zharkov
2016-07-13 18:32:47 +03:00
parent 06a659e6e7
commit 4725dd3028
14 changed files with 623 additions and 30 deletions
@@ -21,6 +21,7 @@ import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.KtNodeType;
import org.jetbrains.kotlin.KtNodeTypes;
import org.jetbrains.kotlin.lexer.KtToken;
@@ -42,6 +43,9 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
private static final ImmutableMap<String, KtToken> KEYWORD_TEXTS = tokenSetToMap(KEYWORDS);
private static final IElementType[] LOCAL_DECLARATION_FIRST =
new IElementType[] {CLASS_KEYWORD, INTERFACE_KEYWORD, FUN_KEYWORD, VAL_KEYWORD, VAR_KEYWORD, TYPE_ALIAS_KEYWORD};
private static ImmutableMap<String, KtToken> tokenSetToMap(TokenSet tokens) {
ImmutableMap.Builder<String, KtToken> builder = ImmutableMap.builder();
for (IElementType token : tokens.getTypes()) {
@@ -298,8 +302,6 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
* see the precedence table
*/
private void parseBinaryExpression(Precedence precedence) {
// System.out.println(precedence.name() + " at " + myBuilder.getTokenText());
PsiBuilder.Marker expression = mark();
precedence.parseHigherPrecedence(this);
@@ -331,10 +333,8 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
* operation? prefixExpression
*/
private void parsePrefixExpression() {
// System.out.println("pre at " + myBuilder.getTokenText());
if (at(AT)) {
if (!parseLocalDeclaration()) {
if (!parseLocalDeclaration(/* rollbackIfDefinitelyNotExpression = */ false)) {
PsiBuilder.Marker expression = mark();
myKotlinParsing.parseAnnotations(DEFAULT);
parsePrefixExpression();
@@ -454,7 +454,7 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
expression = mark();
}
parseCallExpression();
parseSelectorCallExpression();
if (firstExpressionParsed) {
expression.done(expressionType);
@@ -515,7 +515,7 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
/*
* atomicExpression typeParameters? valueParameters? functionLiteral*
*/
private void parseCallExpression() {
private void parseSelectorCallExpression() {
PsiBuilder.Marker mark = mark();
parseAtomicExpression();
if (!myBuilder.newlineBeforeCurrentToken() && parseCallSuffix()) {
@@ -624,8 +624,6 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
* ;
*/
private boolean parseAtomicExpression() {
// System.out.println("atom at " + myBuilder.getTokenText());
boolean ok = true;
if (at(LPAR)) {
@@ -670,9 +668,9 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
else if (at(DO_KEYWORD)) {
parseDoWhile();
}
else if (atSet(CLASS_KEYWORD, INTERFACE_KEYWORD, FUN_KEYWORD, VAL_KEYWORD,
VAR_KEYWORD, TYPE_ALIAS_KEYWORD)) {
parseLocalDeclaration();
else if (atSet(LOCAL_DECLARATION_FIRST) &&
parseLocalDeclaration(/* rollbackIfDefinitelyNotExpression = */ myBuilder.newlineBeforeCurrentToken())) {
// declaration was parsed, do nothing
}
else if (at(IDENTIFIER)) {
parseSimpleNameExpression();
@@ -1018,12 +1016,12 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
/*
* modifiers declarationRest
*/
private boolean parseLocalDeclaration() {
private boolean parseLocalDeclaration(boolean rollbackIfDefinitelyNotExpression) {
PsiBuilder.Marker decl = mark();
KotlinParsing.ModifierDetector detector = new KotlinParsing.ModifierDetector();
myKotlinParsing.parseModifierList(detector, DEFAULT, TokenSet.EMPTY);
IElementType declType = parseLocalDeclarationRest(detector.isEnumDetected());
IElementType declType = parseLocalDeclarationRest(detector.isEnumDetected(), rollbackIfDefinitelyNotExpression);
if (declType != null) {
// we do not attach preceding comments (non-doc) to local variables because they are likely commenting a few statements below
@@ -1220,7 +1218,7 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
* ;
*/
private void parseStatement(boolean isScriptTopLevel) {
if (!parseLocalDeclaration()) {
if (!parseLocalDeclaration(/* rollbackIfDefinitelyNotExpression = */false)) {
if (!atSet(EXPRESSION_FIRST)) {
errorAndAdvance("Expecting a statement");
}
@@ -1245,9 +1243,17 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
* : object
* ;
*/
private IElementType parseLocalDeclarationRest(boolean isEnum) {
@Nullable
private IElementType parseLocalDeclarationRest(boolean isEnum, boolean failIfDefinitelyNotExpression) {
IElementType keywordToken = tt();
IElementType declType = null;
if (failIfDefinitelyNotExpression) {
if (keywordToken != FUN_KEYWORD) return null;
return myKotlinParsing.parseFunction(/* failIfIdentifierExists = */ true);
}
if (keywordToken == CLASS_KEYWORD || keywordToken == INTERFACE_KEYWORD) {
declType = myKotlinParsing.parseClass(isEnum);
}
@@ -21,6 +21,7 @@ import com.intellij.lang.WhitespacesBinders;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.lexer.KtKeywordToken;
@@ -1444,6 +1445,11 @@ public class KotlinParsing extends AbstractKotlinParsing {
return accessorKind;
}
@NotNull
IElementType parseFunction() {
return parseFunction(false);
}
/*
* function
* : modifiers "fun" typeParameters?
@@ -1454,7 +1460,8 @@ public class KotlinParsing extends AbstractKotlinParsing {
* functionBody?
* ;
*/
IElementType parseFunction() {
@Contract("false -> !null")
IElementType parseFunction(boolean failIfIdentifierExists) {
assert _at(FUN_KEYWORD);
advance(); // FUN_KEYWORD
@@ -1476,6 +1483,11 @@ public class KotlinParsing extends AbstractKotlinParsing {
TokenSet functionNameFollow = TokenSet.create(LT, LPAR, RPAR, COLON, EQ);
boolean receiverFound = parseReceiverType("function", functionNameFollow);
if (at(IDENTIFIER) && failIfIdentifierExists) {
myBuilder.restoreJoiningComplexTokensState();
return null;
}
// function as expression has no name
parseFunctionOrPropertyName(receiverFound, "function", functionNameFollow, /*nameRequired = */ false);
@@ -1598,15 +1610,15 @@ public class KotlinParsing extends AbstractKotlinParsing {
/*
* IDENTIFIER
*/
private void parseFunctionOrPropertyName(boolean receiverFound, String title, TokenSet nameFollow, boolean nameRequired) {
if (!nameRequired && atSet(nameFollow)) return; // no name
private boolean parseFunctionOrPropertyName(boolean receiverFound, String title, TokenSet nameFollow, boolean nameRequired) {
if (!nameRequired && atSet(nameFollow)) return true; // no name
TokenSet recoverySet = TokenSet.orSet(nameFollow, TokenSet.create(LBRACE, RBRACE), TOP_LEVEL_DECLARATION_FIRST);
if (!receiverFound) {
expect(IDENTIFIER, "Expecting " + title + " name or receiver type", recoverySet);
return expect(IDENTIFIER, "Expecting " + title + " name or receiver type", recoverySet);
}
else {
expect(IDENTIFIER, "Expecting " + title + " name", recoverySet);
return expect(IDENTIFIER, "Expecting " + title + " name", recoverySet);
}
}