KtBlockExpression as lazy reparseable node (KT-13841)

Changed KtBlockExpression to support LazyReparseable behaviour
Added LazyKtBlockExpressionTest
Added new BlockWrapper delegations
This commit is contained in:
Igor Yakovlev
2019-03-12 13:43:13 +03:00
parent e774f3fe77
commit 1f29b42cd3
16 changed files with 443 additions and 128 deletions
@@ -0,0 +1,107 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin
import com.intellij.lang.ASTNode
import com.intellij.lang.Language
import com.intellij.lang.PsiBuilderFactory
import com.intellij.openapi.project.Project
import com.intellij.psi.tree.ICompositeElementType
import com.intellij.psi.tree.IErrorCounterReparseableElementType
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes.BLOCK_CODE_FRAGMENT
import org.jetbrains.kotlin.KtNodeTypes.FUNCTION_LITERAL
import org.jetbrains.kotlin.KtNodeTypes.SCRIPT
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.parsing.KotlinParser
import org.jetbrains.kotlin.psi.KtBlockExpression
class BlockExpressionElementType : IErrorCounterReparseableElementType("BLOCK", KotlinLanguage.INSTANCE), ICompositeElementType {
override fun createCompositeNode() = KtBlockExpression(null)
override fun createNode(text: CharSequence?) = KtBlockExpression(text)
override fun isParsable(parent: ASTNode?, buffer: CharSequence, fileLanguage: Language, project: Project) =
fileLanguage == KotlinLanguage.INSTANCE &&
BlockExpressionElementType.isAllowedParentNode(parent) &&
BlockExpressionElementType.isReparseableBlock(buffer) &&
super.isParsable(buffer, fileLanguage, project)
override fun getErrorsCount(seq: CharSequence, fileLanguage: Language, project: Project) =
ElementTypeUtils.getKotlinBlockImbalanceCount(seq)
override fun parseContents(chameleon: ASTNode): ASTNode {
val project = chameleon.psi.project
val builder = PsiBuilderFactory.getInstance().createBuilder(
project, chameleon, null, KotlinLanguage.INSTANCE, chameleon.chars
)
return KotlinParser.parseBlockExpression(builder).firstChildNode
}
companion object {
private fun isAllowedParentNode(node: ASTNode?) =
node != null &&
SCRIPT != node.elementType &&
FUNCTION_LITERAL != node.elementType &&
BLOCK_CODE_FRAGMENT != node.elementType
/**
* Check if this text is block but not a lambda, please refer to parsing rules!
@see [org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseFunctionLiteral]
*/
fun isReparseableBlock(blockText: CharSequence): Boolean {
fun advanceWhitespacesCheckIsEndOrArrow(lexer: KotlinLexer): Boolean {
lexer.advance()
while (lexer.tokenType != null && lexer.tokenType != KtTokens.EOF) {
if (lexer.tokenType == KtTokens.ARROW) return true
if (lexer.tokenType != KtTokens.WHITE_SPACE) return false
lexer.advance()
}
return true
}
val lexer = KotlinLexer()
lexer.start(blockText)
// Try to parse a simple name list followed by an ARROW
// {a -> ...}
// {a, b -> ...}
// {(a, b) -> ... }
if (lexer.tokenType != KtTokens.LBRACE) return false
if (advanceWhitespacesCheckIsEndOrArrow(lexer)) return false
if (lexer.tokenType != KtTokens.COLON &&
lexer.tokenType != KtTokens.IDENTIFIER &&
lexer.tokenType != KtTokens.LPAR
) return true
val searchForRPAR = lexer.tokenType == KtTokens.LPAR
if (advanceWhitespacesCheckIsEndOrArrow(lexer)) return false
val preferParamsToExpressions = lexer.tokenType == KtTokens.COMMA || lexer.tokenType == KtTokens.COLON
while (true) {
if (lexer.tokenType == KtTokens.LBRACE) return true
if (lexer.tokenType == KtTokens.RBRACE) return !preferParamsToExpressions
if (searchForRPAR && lexer.tokenType == KtTokens.RPAR) {
return !advanceWhitespacesCheckIsEndOrArrow(lexer)
}
if (advanceWhitespacesCheckIsEndOrArrow(lexer)) return false
}
}
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin
import com.intellij.psi.tree.IErrorCounterReparseableElementType
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtTokens
object ElementTypeUtils {
@JvmStatic
fun getKotlinBlockImbalanceCount(seq: CharSequence): Int {
val lexer = KotlinLexer()
lexer.start(seq)
if (lexer.tokenType !== KtTokens.LBRACE) return IErrorCounterReparseableElementType.FATAL_ERROR
lexer.advance()
var balance = 1
while (lexer.tokenType != KtTokens.EOF) {
val type = lexer.tokenType ?: break
if (balance == 0) {
return IErrorCounterReparseableElementType.FATAL_ERROR
}
if (type === KtTokens.LBRACE) {
balance++
} else if (type === KtTokens.RBRACE) {
balance--
}
lexer.advance()
}
return balance
}
}
@@ -112,9 +112,10 @@ public interface KtNodeTypes {
IElementType DO_WHILE = new KtNodeType("DO_WHILE", KtDoWhileExpression.class);
IElementType LOOP_RANGE = new KtNodeType("LOOP_RANGE", KtContainerNode.class);
IElementType BODY = new KtNodeType("BODY", KtContainerNodeForControlStructureBody.class);
IElementType BLOCK = new KtNodeType("BLOCK", KtBlockExpression.class);
IElementType LAMBDA_EXPRESSION = new LambdaExpressionElementType();
IElementType BLOCK = new BlockExpressionElementType();
IElementType LAMBDA_EXPRESSION = new LambdaExpressionElementType();
IElementType FUNCTION_LITERAL = new KtNodeType("FUNCTION_LITERAL", KtFunctionLiteral.class);
IElementType ANNOTATED_EXPRESSION = new KtNodeType("ANNOTATED_EXPRESSION", KtAnnotatedExpression.class);
@@ -147,27 +147,7 @@ class LambdaExpressionElementType extends IErrorCounterReparseableElementType {
}
@Override
public int getErrorsCount(CharSequence seq, Language fileLanguage, Project project) {
Lexer lexer = new KotlinLexer();
lexer.start(seq);
if (lexer.getTokenType() != KtTokens.LBRACE) return IErrorCounterReparseableElementType.FATAL_ERROR;
lexer.advance();
int balance = 1;
while (true) {
IElementType type = lexer.getTokenType();
if (type == null) break;
if (balance == 0) {
return IErrorCounterReparseableElementType.FATAL_ERROR;
}
if (type == KtTokens.LBRACE) {
balance++;
}
else if (type == KtTokens.RBRACE) {
balance--;
}
lexer.advance();
}
return balance;
public int getErrorsCount(CharSequence seq, Language fileLanguage, Project project){
return ElementTypeUtils.getKotlinBlockImbalanceCount(seq);
}
}
@@ -17,7 +17,10 @@
package org.jetbrains.kotlin.parsing;
import com.google.common.collect.ImmutableMap;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.PsiBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
@@ -1093,6 +1096,8 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
/**
* If it has no ->, it's a block, otherwise a function literal
*
* Please update {@link org.jetbrains.kotlin.BlockExpressionElementType#isParsable(ASTNode, CharSequence, Language, Project)} if any changes occurs!
*/
public void parseFunctionLiteral(boolean preferBlock, boolean collapse) {
assert _at(LBRACE);
@@ -1138,7 +1143,7 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
}
if (collapse) {
advanceLambdaBlock();
myKotlinParsing.advanceBalancedBlock();
literal.done(FUNCTION_LITERAL);
literalExpression.collapse(LAMBDA_EXPRESSION);
}
@@ -1157,24 +1162,6 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
myBuilder.restoreNewlinesState();
}
private void advanceLambdaBlock() {
int braceCount = 1;
while (!eof()) {
if (_at(LBRACE)) {
braceCount++;
}
else if (_at(RBRACE)) {
braceCount--;
}
advance();
if (braceCount == 0) {
break;
}
}
}
private boolean rollbackOrDropAt(PsiBuilder.Marker rollbackMarker, IElementType dropAt) {
if (at(dropAt)) {
advance(); // dropAt
@@ -80,4 +80,11 @@ public class KotlinParser implements PsiParser {
ktParsing.parseLambdaExpression();
return psiBuilder.getTreeBuilt();
}
@NotNull
public static ASTNode parseBlockExpression(PsiBuilder psiBuilder) {
KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
ktParsing.parseBlockExpression();
return psiBuilder.getTreeBuilt();
}
}
@@ -162,6 +162,10 @@ public class KotlinParsing extends AbstractKotlinParsing {
myExpressionParsing.parseFunctionLiteral(/* preferBlock = */ false, /* collapse = */false);
}
void parseBlockExpression() {
parseBlock(/* collapse = */ false);
}
void parseScript() {
PsiBuilder.Marker fileMarker = mark();
@@ -1725,17 +1729,50 @@ public class KotlinParsing extends AbstractKotlinParsing {
* ;
*/
void parseBlock() {
PsiBuilder.Marker block = mark();
parseBlock(/*collapse*/ true);
}
private void parseBlock(boolean collapse) {
PsiBuilder.Marker lazyBlock = mark();
myBuilder.enableNewlines();
expect(LBRACE, "Expecting '{' to open a block");
myExpressionParsing.parseStatements();
if(collapse){
advanceBalancedBlock();
}else{
myExpressionParsing.parseStatements();
expect(RBRACE, "Expecting '}'");
}
expect(RBRACE, "Expecting '}'");
myBuilder.restoreNewlinesState();
block.done(BLOCK);
if(collapse){
lazyBlock.collapse(BLOCK);
}else{
lazyBlock.done(BLOCK);
}
}
public void advanceBalancedBlock() {
int braceCount = 1;
while (!eof()) {
if (_at(LBRACE)) {
braceCount++;
}
else if (_at(RBRACE)) {
braceCount--;
}
advance();
if (braceCount == 0) {
break;
}
}
}
/*
@@ -16,21 +16,32 @@
package org.jetbrains.kotlin.psi;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiModifiableCodeBlock;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.impl.source.tree.LazyParseablePsiElement;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import kotlin.annotations.jvm.ReadOnly;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinLanguage;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class KtBlockExpression extends KtExpressionImpl implements KtStatementExpression, PsiModifiableCodeBlock {
public KtBlockExpression(@NotNull ASTNode node) {
super(node);
import static org.jetbrains.kotlin.KtNodeTypes.BLOCK;
public class KtBlockExpression extends LazyParseablePsiElement implements KtElement, KtExpression, KtStatementExpression, PsiModifiableCodeBlock {
public KtBlockExpression(@Nullable CharSequence text) {
super(BLOCK, text);
}
@Override
@@ -39,11 +50,92 @@ public class KtBlockExpression extends KtExpressionImpl implements KtStatementEx
return false;
}
@NotNull
@Override
public Language getLanguage() {
return KotlinLanguage.INSTANCE;
}
@Override
public String toString() {
return getNode().getElementType().toString();
}
@NotNull
@Override
public KtFile getContainingKtFile() {
return PsiUtilsKt.getContainingKtFile(this);
}
@Override
public <D> void acceptChildren(@NotNull KtVisitor<Void, D> visitor, D data) {
KtPsiUtil.visitChildren(this, visitor, data);
}
@Override
public <R, D> R accept(@NotNull KtVisitor<R, D> visitor, D data) {
return visitor.visitBlockExpression(this, data);
}
@Override
@SuppressWarnings("unchecked")
public final void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof KtVisitor) {
accept((KtVisitor) visitor, null);
}
else {
visitor.visitElement(this);
}
}
@Override
public void delete() throws IncorrectOperationException {
KtElementUtilsKt.deleteSemicolon(this);
super.delete();
}
@Override
@SuppressWarnings("deprecation")
public PsiReference getReference() {
PsiReference[] references = getReferences();
if (references.length == 1) return references[0];
else return null;
}
@Override
@NotNull
public PsiElement[] getChildren() {
PsiElement psiChild = getFirstChild();
List<PsiElement> result = null;
while (psiChild != null) {
if (psiChild.getNode() instanceof CompositeElement) {
if(result == null) result = new ArrayList<>();
result.add(psiChild);
}
psiChild = psiChild.getNextSibling();
}
return result == null ? PsiElement.EMPTY_ARRAY : PsiUtilCore.toPsiElementArray(result);
}
@NotNull
@Override
public PsiReference[] getReferences() {
return ReferenceProvidersRegistry.getReferencesFromProviders(this, PsiReferenceService.Hints.NO_HINTS);
}
@NotNull
@Override
public KtElement getPsiOrParent() {
return this;
}
@Override
public PsiElement getParent() {
PsiElement substitute = KtPsiUtilKt.getParentSubstitute(this);
return substitute != null ? substitute : super.getParent();
}
@ReadOnly
@NotNull
public List<KtExpression> getStatements() {
@@ -58,11 +150,11 @@ public class KtBlockExpression extends KtExpressionImpl implements KtStatementEx
@Nullable
public PsiElement getRBrace() {
return findChildByType(KtTokens.RBRACE);
return findPsiChildByType(KtTokens.RBRACE);
}
@Nullable
public PsiElement getLBrace() {
return findChildByType(KtTokens.LBRACE);
return findPsiChildByType(KtTokens.LBRACE);
}
}
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.KtNodeTypes;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import java.util.List;
@@ -76,13 +77,7 @@ public class KtLambdaExpression extends LazyParseablePsiElement implements KtExp
@NotNull
@Override
public KtFile getContainingKtFile() {
PsiFile file = getContainingFile();
if(!(file instanceof KtFile)) {
String fileString = (file != null && file.isValid()) ? file.getText() : "";
throw new IllegalStateException("KtElement not inside KtFile: " + file + fileString +
"for element " + this + " of type " + this.getClass() + " node = " + getNode());
}
return (KtFile) file;
return PsiUtilsKt.getContainingKtFile(this);
}
@Override
@@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.LocalTimeCounter
@@ -853,7 +854,8 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
}
private class BlockWrapper(fakeBlockExpression: KtBlockExpression, private val expression: KtExpression) :
KtBlockExpression(fakeBlockExpression.node), KtPsiUtil.KtExpressionWrapper {
KtBlockExpression(fakeBlockExpression.text), KtPsiUtil.KtExpressionWrapper {
override fun getStatements(): List<KtExpression> {
return listOf(expression)
}
@@ -861,5 +863,13 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
override fun getBaseExpression(): KtExpression {
return expression
}
override fun getParent(): PsiElement = expression.parent
override fun getPsiOrParent(): KtElement = expression.psiOrParent
override fun getContainingKtFile() = expression.containingKtFile
override fun getContainingFile(): PsiFile = expression.containingFile
}
}
@@ -20,6 +20,7 @@ import com.intellij.injected.editor.VirtualFileWindow
import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.LazyParseablePsiElement
import com.intellij.psi.impl.source.tree.TreeUtil
import com.intellij.psi.search.PsiSearchScopeUtil
import com.intellij.psi.search.SearchScope
@@ -380,7 +381,7 @@ fun PsiElement.getElementTextWithContext(): String {
// Find parent for element among file children
val topLevelElement = PsiTreeUtil.findFirstParent(this, { it.parent is PsiFile })
?: throw AssertionError("For non-file element we should always be able to find parent in file children")
?: throw AssertionError("For non-file element we should always be able to find parent in file children")
val startContextOffset = topLevelElement.startOffset
val elementContextOffset = textRange.startOffset
@@ -465,4 +466,14 @@ fun ASTNode.closestPsiElement(): PsiElement? {
node = node.treeParent
}
return node.psi
}
}
fun LazyParseablePsiElement.getContainingKtFile(): KtFile {
val file = this.containingFile
if (file is KtFile) return file
val fileString = if (file != null && file.isValid) file.text else ""
throw IllegalStateException("KtElement not inside KtFile: $file with text \"$fileString\" for element $this of type ${this::class.java} node = ${this.node}")
}