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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user