From a13a04316964db2737970417e7bed59fe30718c2 Mon Sep 17 00:00:00 2001 From: Ivan Cilcic Date: Tue, 16 Jul 2019 17:25:40 +0300 Subject: [PATCH] Write binary and unary expressions parsing (light tree to FIR) --- .../fir/lightTree/converter/BaseConverter.kt | 13 +- .../fir/lightTree/converter/ConverterUtil.kt | 13 +- .../converter/DeclarationsConverter.kt | 59 ++-- .../converter/ExpressionsConverter.kt | 260 +++++++++++++++++- .../converter/utils/ExpressionUtil.kt | 161 +++++++++++ .../fir/lightTree/compare/TreesCompareTest.kt | 12 +- .../kotlin/fir/builder/ConversionUtils.kt | 24 +- 7 files changed, 495 insertions(+), 47 deletions(-) create mode 100644 compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/utils/ExpressionUtil.kt diff --git a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt index bf650079b0b..1200780f3ed 100644 --- a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt +++ b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/BaseConverter.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.types.impl.* import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.* +import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.isExpression open class BaseConverter( session: FirSession, @@ -25,7 +26,7 @@ open class BaseConverter( protected val implicitAnnotationType = FirImplicitAnnotationTypeRef(session, null) protected val implicitType = FirImplicitTypeRefImpl(session, null) - protected fun LighterASTNode?.getChildNodesByType(type: IElementType): List { + fun LighterASTNode?.getChildNodesByType(type: IElementType): List { return this?.forEachChildrenReturnList { node, container -> when (node.tokenType) { type -> container += node @@ -33,7 +34,7 @@ open class BaseConverter( } ?: listOf() } - protected fun LighterASTNode?.getChildrenAsArray(): Array { + fun LighterASTNode?.getChildrenAsArray(): Array { if (this == null) return arrayOf() val kidsRef = Ref>() @@ -41,6 +42,14 @@ open class BaseConverter( return kidsRef.get() } + fun LighterASTNode.getExpressionInParentheses(): LighterASTNode { + this.forEachChildren { + if (it.isExpression()) return it + } + + throw Exception() + } + protected inline fun LighterASTNode.forEachChildren(vararg skipTokens: KtToken, f: (LighterASTNode) -> Unit) { val kidsArray = this.getChildrenAsArray() for (kid in kidsArray) { diff --git a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ConverterUtil.kt b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ConverterUtil.kt index 72243780da7..aadf1f25c16 100644 --- a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ConverterUtil.kt +++ b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ConverterUtil.kt @@ -6,29 +6,35 @@ package org.jetbrains.kotlin.fir.lightTree.converter import com.intellij.lang.LighterASTNode +import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.KtNodeType import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.FirFunctionTarget +import org.jetbrains.kotlin.fir.FirReference import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.* -import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall -import org.jetbrains.kotlin.fir.expressions.FirExpression -import org.jetbrains.kotlin.fir.expressions.FirReturnExpression +import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.impl.* import org.jetbrains.kotlin.fir.lightTree.fir.TypeConstraint +import org.jetbrains.kotlin.fir.references.FirErrorNamedReference +import org.jetbrains.kotlin.fir.references.FirExplicitThisReference import org.jetbrains.kotlin.fir.references.FirResolvedCallableReferenceImpl +import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference import org.jetbrains.kotlin.fir.symbols.CallableId import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.impl.* +import org.jetbrains.kotlin.lexer.KtSingleValueToken +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames +import org.jetbrains.kotlin.parsing.KotlinExpressionParsing import org.jetbrains.kotlin.psi.stubs.elements.KtConstantExpressionElementType import org.jetbrains.kotlin.psi.stubs.elements.KtStringTemplateExpressionElementType import org.jetbrains.kotlin.types.Variance @@ -194,6 +200,7 @@ object ClassNameUtil { object FunctionUtil { val firFunctions = mutableListOf() + val firFunctionCalls = mutableListOf() fun MutableList.removeLast() { removeAt(size - 1) diff --git a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt index a5333f7c53c..28aaa2e9c75 100644 --- a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt +++ b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/DeclarationsConverter.kt @@ -15,11 +15,9 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.* -import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall -import org.jetbrains.kotlin.fir.expressions.FirBlock -import org.jetbrains.kotlin.fir.expressions.FirDelegatedConstructorCall -import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.impl.* +import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.extractArgumentsFrom import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.getAsString import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.getAsStringWithoutBacktick @@ -53,7 +51,7 @@ class DeclarationsConverter( private val stubMode: Boolean, tree: FlyweightCapableTreeStructure ) : BaseConverter(session, tree) { - private val expressionConverter = ExpressionsConverter(session, stubMode, tree) + private val expressionConverter = ExpressionsConverter(session, stubMode, tree, this) /** * [org.jetbrains.kotlin.parsing.KotlinParsing.parseFile] @@ -96,6 +94,31 @@ class DeclarationsConverter( return firFile } + /** + * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseBlockExpression + */ + private fun convertBlockExpression(block: LighterASTNode): FirBlock { + val firStatements = mutableListOf() + block.forEachChildren { + when (it.tokenType) { + //TODO("not implemented") + BLOCK -> "" + BINARY_EXPRESSION -> firStatements += expressionConverter.convertBinaryExpression(it) + PREFIX_EXPRESSION, POSTFIX_EXPRESSION -> firStatements += expressionConverter.convertUnaryExpression(it) + else -> if (it.isExpression()) firStatements += expressionConverter.visitExpression(it) + } + } + return FirBlockImpl(session, null).apply { + firStatements.forEach { firStatement -> + if (firStatement !is FirBlock || firStatement.annotations.isNotEmpty()) { + statements += firStatement + } else { + statements += firStatement.statements + } + } + } + } + /***** PREAMBLE *****/ /** * @see org.jetbrains.kotlin.parsing.KotlinParsing.parsePackageName @@ -639,7 +662,7 @@ class DeclarationsConverter( lateinit var identifier: String val firTypeParameters = mutableListOf() var isReturnType = false - var isDelegate = false + var firDelegateExpression: FirExpression? = null var isVar = false var receiverType: FirTypeRef? = null var returnType: FirTypeRef = implicitType @@ -655,7 +678,7 @@ class DeclarationsConverter( COLON -> isReturnType = true TYPE_REFERENCE -> if (isReturnType) returnType = convertType(it) else receiverType = convertType(it) TYPE_CONSTRAINT_LIST -> typeConstraints += convertTypeConstraints(it) - PROPERTY_DELEGATE -> isDelegate = true + PROPERTY_DELEGATE -> firDelegateExpression = expressionConverter.visitExpression(it) VAR_KEYWORD -> isVar = true PROPERTY_ACCESSOR -> { val propertyAccessor = convertGetterOrSetter(it, returnType) @@ -675,11 +698,7 @@ class DeclarationsConverter( returnType, isVar, firExpression, - delegate = if (isDelegate) { - FirExpressionStub(session, null) - //TODO("not implemented") - //{ property.delegate?.expression }.toFirExpression("Should have delegate") - } else null + delegate = firDelegateExpression ) } else { FirMemberPropertyImpl( @@ -700,11 +719,7 @@ class DeclarationsConverter( firExpression, getter ?: FirDefaultPropertyGetter(session, null, returnType, modifiers.getVisibility()), if (isVar) setter ?: FirDefaultPropertySetter(session, null, returnType, modifiers.getVisibility()) else null, - if (isDelegate) { - FirExpressionStub(session, null) - //TODO("not implemented") - //{ property.delegate?.expression }.toFirExpression("Should have delegate") - } else null + firDelegateExpression ).apply { this.typeParameters += firTypeParameters this.joinTypeParameters(typeConstraints) @@ -854,6 +869,7 @@ class DeclarationsConverter( /** * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseFunctionBody + * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.buildFirBody */ private fun visitFunctionBody(firBlock: FirBlock?, firExpression: FirExpression?): FirBlock? { return when { @@ -878,11 +894,10 @@ class DeclarationsConverter( /** * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseBlock */ - private fun visitBlock(block: LighterASTNode?): FirBlock { + private fun visitBlock(block: LighterASTNode): FirBlock { return if (!stubMode) { - //TODO("not implemented") - FirBlockImpl(session, null) - //visitStatements(ctx.statements()) + val blockTree = LightTree2Fir.buildLightTreeBlockExpression(block.getAsString()) + return DeclarationsConverter(session, stubMode, blockTree).convertBlockExpression(blockTree.root) } else { FirSingleExpressionBlock( session, @@ -1116,7 +1131,7 @@ class DeclarationsConverter( /** * @see org.jetbrains.kotlin.parsing.KotlinParsing.parseTypeArgumentList */ - private fun convertTypeArguments(typeArguments: LighterASTNode): List { + fun convertTypeArguments(typeArguments: LighterASTNode): List { return typeArguments.forEachChildrenReturnList { node, container -> when (node.tokenType) { TYPE_PROJECTION -> container += convertTypeProjection(node) diff --git a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt index e1de8d158ed..e5fe429e07d 100644 --- a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt +++ b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt @@ -8,39 +8,238 @@ package org.jetbrains.kotlin.fir.lightTree.converter import com.intellij.lang.LighterASTNode import com.intellij.util.diff.FlyweightCapableTreeStructure import org.jetbrains.kotlin.KtNodeTypes.* +import org.jetbrains.kotlin.fir.FirReference import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.builder.parseCharacter -import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.builder.* +import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.impl.* import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.getAsString import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.isExpression import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.nameAsSafeName import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.getAsStringUnescapedValue +import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.extractArgumentsFrom +import org.jetbrains.kotlin.fir.lightTree.converter.FunctionUtil.removeLast +import org.jetbrains.kotlin.fir.lightTree.converter.utils.* +import org.jetbrains.kotlin.fir.references.FirErrorNamedReference +import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference +import org.jetbrains.kotlin.fir.types.FirTypeProjection import org.jetbrains.kotlin.ir.expressions.IrConstKind import org.jetbrains.kotlin.lexer.KtTokens.* +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.stubs.elements.KtConstantExpressionElementType import org.jetbrains.kotlin.resolve.constants.evaluate.* +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import org.jetbrains.kotlin.util.OperatorNameConventions class ExpressionsConverter( - private val session: FirSession, + val session: FirSession, private val stubMode: Boolean, - tree: FlyweightCapableTreeStructure + tree: FlyweightCapableTreeStructure, + private val declarationsConverter: DeclarationsConverter ) : BaseConverter(session, tree) { /***** EXPRESSIONS *****/ fun visitExpression(expression: LighterASTNode): FirExpression { if (!stubMode) { when (expression.tokenType) { + BINARY_EXPRESSION -> return convertBinaryExpression(expression) as FirExpression + PREFIX_EXPRESSION, POSTFIX_EXPRESSION -> return convertUnaryExpression(expression) as FirExpression + in qualifiedAccessTokens -> return convertQualifiedExpression(expression) + CALL_EXPRESSION -> return convertCallExpression(expression) + ARRAY_ACCESS_EXPRESSION -> return convertArrayAccessExpression(expression) STRING_TEMPLATE -> return convertStringTemplate(expression) is KtConstantExpressionElementType -> return convertConstantExpression(expression) + REFERENCE_EXPRESSION -> return convertSimpleNameExpression(expression) + PARENTHESIZED, PROPERTY_DELEGATE, INDICES -> return visitExpression(expression.getExpressionInParentheses()) } - //TODO("not implemented") + return FirExpressionStub(session, null) + //TODO("not fully implemented") } return FirExpressionStub(session, null) } /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseBinaryExpression + * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.visitBinaryExpression + */ + fun convertBinaryExpression(binaryExpression: LighterASTNode): FirStatement { + var isLeftArgument = true + lateinit var operationTokenName: String + lateinit var leftArgNode: LighterASTNode + lateinit var rightArgAsFir: FirExpression + binaryExpression.forEachChildren { + when (it.tokenType) { + OPERATION_REFERENCE -> { + isLeftArgument = false + operationTokenName = it.getAsString() + } + else -> if (it.isExpression()) { + if (isLeftArgument) { + leftArgNode = it + } else { + rightArgAsFir = visitExpression(it) + } + } + } + } + + val operationToken = operationTokenName.getOperationSymbol() + when (operationToken) { + ELVIS -> + return visitExpression(leftArgNode).generateNotNullOrOther(session, rightArgAsFir, "elvis", null) + ANDAND, OROR -> + return visitExpression(leftArgNode).generateLazyLogicalOperation(session, rightArgAsFir, operationToken == ANDAND, null) + in OperatorConventions.IN_OPERATIONS -> + return rightArgAsFir.generateContainsOperation(session, visitExpression(leftArgNode), operationToken == NOT_IN, null, null) + } + val conventionCallName = operationToken.toBinaryName() + return if (conventionCallName != null || operationToken == IDENTIFIER) { + FirFunctionCallImpl(session, null).apply { + calleeReference = FirSimpleNamedReference( + this@ExpressionsConverter.session, null, + conventionCallName ?: operationTokenName.nameAsSafeName() + ) + explicitReceiver = visitExpression(leftArgNode) + arguments += rightArgAsFir + } + } else { + val firOperation = operationToken.toFirOperation() + if (firOperation in FirOperation.ASSIGNMENTS) { + return convertAssignment(leftArgNode, rightArgAsFir, firOperation) + } else { + FirOperatorCallImpl(session, null, firOperation).apply { + arguments += visitExpression(leftArgNode) + arguments += rightArgAsFir + } + } + } + } + + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parsePostfixExpression + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parsePrefixExpression + * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.visitUnaryExpression + */ + fun convertUnaryExpression(unaryExpression: LighterASTNode): FirStatement { + lateinit var operationTokenName: String + lateinit var argument: LighterASTNode + unaryExpression.forEachChildren { + when (it.tokenType) { + OPERATION_REFERENCE -> operationTokenName = it.getAsString() + else -> if (it.isExpression()) argument = it + } + } + + val operationToken = operationTokenName.getOperationSymbol() + if (operationToken == EXCLEXCL) { + return bangBangToWhen(session, visitExpression(argument)) + } + + val conventionCallName = operationToken.toUnaryName() + return if (conventionCallName != null) { + if (operationToken in OperatorConventions.INCREMENT_OPERATIONS) { + return generateIncrementOrDecrementBlock( + argument, + callName = conventionCallName, + prefix = unaryExpression.tokenType == PREFIX_EXPRESSION + ) + } + FirFunctionCallImpl(session, null).apply { + calleeReference = FirSimpleNamedReference(this@ExpressionsConverter.session, null, conventionCallName) + explicitReceiver = visitExpression(argument) + } + } else { + val firOperation = operationToken.toFirOperation() + FirOperatorCallImpl(session, null, firOperation).apply { + arguments += visitExpression(argument) + } + } + } + + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parsePostfixExpression + * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.visitQualifiedExpression + */ + private fun convertQualifiedExpression(dotQualifiedExpression: LighterASTNode): FirExpression { + var isSelector = false + var isSafe = false + var firSelector: FirExpression? = null //after dot + lateinit var firReceiver: FirExpression //before dot + dotQualifiedExpression.forEachChildren { + when (it.tokenType) { + DOT -> isSelector = true + SAFE_ACCESS -> { + isSafe = true + isSelector = true + } + else -> if (isSelector) firSelector = visitExpression(it) else firReceiver = visitExpression(it) + } + } + + if (firSelector == null) { + return FirErrorExpressionImpl(session, null, "Qualified expression without selector") + } + + //TODO use contracts? + if (firSelector is FirModifiableQualifiedAccess<*>) { + (firSelector as FirModifiableQualifiedAccess<*>).safe = isSafe + (firSelector as FirModifiableQualifiedAccess<*>).explicitReceiver = firReceiver + } + return firSelector!! + } + + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseCallSuffix + */ + private fun convertCallExpression(callSuffix: LighterASTNode): FirExpression { + var name: String? = null + val firTypeArguments = mutableListOf() + val firValueArguments = mutableListOf() + var additionalArgument: FirExpression? = null + callSuffix.forEachChildren { + when (it.tokenType) { + REFERENCE_EXPRESSION -> name = it.getAsString() + TYPE_ARGUMENT_LIST -> firTypeArguments += declarationsConverter.convertTypeArguments(it) + VALUE_ARGUMENT_LIST -> firValueArguments += convertValueArguments(it) + LAMBDA_ARGUMENT -> firValueArguments += convertAnnotatedLambda(it) + else -> additionalArgument = visitExpression(it) + } + } + + return FirFunctionCallImpl(session, null).apply { + val calleeReference = when { + name != null -> FirSimpleNamedReference(this@ExpressionsConverter.session, null, name.nameAsSafeName()) + additionalArgument != null -> { + arguments += additionalArgument!! + FirSimpleNamedReference(this@ExpressionsConverter.session, null, OperatorNameConventions.INVOKE) + } + else -> FirErrorNamedReference(this@ExpressionsConverter.session, null, "Call has no callee") + } + this.calleeReference = calleeReference + FunctionUtil.firFunctionCalls += this + this.extractArgumentsFrom(firValueArguments, stubMode) + typeArguments += firTypeArguments + FunctionUtil.firFunctionCalls.removeLast() + } + } + + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseAnnotatedLambda + */ + private fun convertAnnotatedLambda(annotatedLambda: LighterASTNode): FirExpression { + annotatedLambda.forEachChildren { + when (it.tokenType) { + LAMBDA_EXPRESSION -> "" + LABELED_EXPRESSION -> "" + ANNOTATED_EXPRESSION -> "" + } + } + return FirExpressionStub(session, null) + } + + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseStringTemplate * @see org.jetbrains.kotlin.fir.builder.ConversionUtilsKt.toInterpolatingCall */ private fun convertStringTemplate(stringTemplate: LighterASTNode): FirExpression { @@ -94,6 +293,7 @@ class ExpressionsConverter( } /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseLiteralConstant * @see org.jetbrains.kotlin.fir.builder.ConversionUtilsKt.generateConstantExpressionByLiteral */ private fun convertConstantExpression(constantExpression: LighterASTNode): FirExpression { @@ -143,6 +343,39 @@ class ExpressionsConverter( } } + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseArrayAccess + * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.visitArrayAccessExpression + */ + private fun convertArrayAccessExpression(arrayAccess: LighterASTNode): FirFunctionCall {//Pair> { + lateinit var expression: FirExpression + val indexes: MutableList = mutableListOf() + arrayAccess.forEachChildren { + when (it.tokenType) { + INDICES -> indexes += visitExpression(it) + else -> if (it.isExpression()) expression = visitExpression(it) + } + } + return FirFunctionCallImpl(session, null).apply { + calleeReference = FirSimpleNamedReference(this@ExpressionsConverter.session, null, OperatorNameConventions.GET) + explicitReceiver = expression + for (indexExpression in indexes) { + arguments += indexExpression + } + } + } + + /** + * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseSimpleNameExpression + * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.visitSimpleNameExpression + */ + private fun convertSimpleNameExpression(referenceExpression: LighterASTNode): FirQualifiedAccessExpression { + return FirQualifiedAccessExpressionImpl(session, null).apply { + calleeReference = + FirSimpleNamedReference(this@ExpressionsConverter.session, null, referenceExpression.getAsString().nameAsSafeName()) + } + } + /** * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseValueArgumentList */ @@ -177,4 +410,21 @@ class ExpressionsConverter( else -> firExpression } } + + fun convertLValue(leftArgNode: LighterASTNode, container: FirModifiableQualifiedAccess<*>): FirReference { + leftArgNode.forEachChildren { + return when (it.tokenType) { + //THIS_EXPRESSION -> FirExplicitThisReference(session, left, left.getLabelName()) + REFERENCE_EXPRESSION -> FirSimpleNamedReference(session, null, it.getAsString().nameAsSafeName()) + in qualifiedAccessTokens -> (visitExpression(it) as FirQualifiedAccess).let { firQualifiedAccess -> + container.explicitReceiver = firQualifiedAccess.explicitReceiver + container.safe = firQualifiedAccess.safe + return@let firQualifiedAccess.calleeReference + } + PARENTHESIZED -> convertLValue(it.getExpressionInParentheses(), container) + else -> FirErrorNamedReference(session, null, "Unsupported LValue: ${leftArgNode.tokenType}") + } + } + return FirErrorNamedReference(session, null, "Unsupported LValue: ${leftArgNode.tokenType}") + } } \ No newline at end of file diff --git a/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/utils/ExpressionUtil.kt b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/utils/ExpressionUtil.kt new file mode 100644 index 00000000000..e4840e3adf8 --- /dev/null +++ b/compiler/fir/lightTree/src/org/jetbrains/kotlin/fir/lightTree/converter/utils/ExpressionUtil.kt @@ -0,0 +1,161 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.fir.lightTree.converter.utils + +import com.intellij.lang.LighterASTNode +import com.intellij.psi.tree.IElementType +import com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.KtNodeTypes.* +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.builder.* +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.impl.* +import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.getAsString +import org.jetbrains.kotlin.fir.lightTree.converter.ConverterUtil.nameAsSafeName +import org.jetbrains.kotlin.fir.lightTree.converter.ExpressionsConverter +import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference +import org.jetbrains.kotlin.lexer.KtSingleValueToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.lexer.KtTokens.DOT +import org.jetbrains.kotlin.lexer.KtTokens.SAFE_ACCESS +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.parsing.KotlinExpressionParsing +import org.jetbrains.kotlin.util.OperatorNameConventions + +val qualifiedAccessTokens = TokenSet.create(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION) + +fun String.getOperationSymbol(): IElementType { + KotlinExpressionParsing.ALL_OPERATIONS.types.forEach { + if (it is KtSingleValueToken && it.value == this) return it + } + return KtTokens.IDENTIFIER +} + +fun ExpressionsConverter.convertAssignment( + leftArgNode: LighterASTNode, + rightArgAsFir: FirExpression, + operation: FirOperation +): FirStatement { + if (leftArgNode.tokenType == PARENTHESIZED) { + return convertAssignment(leftArgNode.getExpressionInParentheses(), rightArgAsFir, operation) + } + if (leftArgNode.tokenType == ARRAY_ACCESS_EXPRESSION) { + val arrayAccessFunctionCall = visitExpression(leftArgNode) as FirFunctionCall + val firArrayExpression = arrayAccessFunctionCall.explicitReceiver!! + val arraySet = if (operation != FirOperation.ASSIGN) { + FirArraySetCallImpl(session, null, rightArgAsFir, operation).apply { + indexes += arrayAccessFunctionCall.arguments + } + } else { + return FirFunctionCallImpl(session, null).apply { + calleeReference = FirSimpleNamedReference(this@convertAssignment.session, null, OperatorNameConventions.SET) + explicitReceiver = firArrayExpression + arguments += arrayAccessFunctionCall.arguments + arguments += rightArgAsFir + } + } + if (leftArgNode.getChildNodesByType(REFERENCE_EXPRESSION).isNotEmpty()) { + return arraySet.apply { + lValue = (firArrayExpression as FirQualifiedAccess).calleeReference + } + } + return FirBlockImpl(this@convertAssignment.session, null).apply { + val name = Name.special("") + statements += generateTemporaryVariable(this@convertAssignment.session, null, name, firArrayExpression) + statements += arraySet.apply { lValue = FirSimpleNamedReference(this@convertAssignment.session, null, name) } + } + } + if (operation != FirOperation.ASSIGN && + leftArgNode.tokenType != REFERENCE_EXPRESSION && leftArgNode.tokenType != THIS_EXPRESSION && + (leftArgNode.tokenType !in qualifiedAccessTokens || getSelectorType(leftArgNode.getChildrenAsArray()) != REFERENCE_EXPRESSION) + ) { + return FirBlockImpl(session, null).apply { + val name = Name.special("") + statements += generateTemporaryVariable(this@convertAssignment.session, null, name, visitExpression(leftArgNode)) + statements += FirVariableAssignmentImpl(this@convertAssignment.session, null, rightArgAsFir, operation).apply { + lValue = FirSimpleNamedReference(this@convertAssignment.session, null, name) + } + } + } + return FirVariableAssignmentImpl(session, null, rightArgAsFir, operation).apply { + lValue = convertLValue(leftArgNode, this) + } + +} + +fun ExpressionsConverter.generateIncrementOrDecrementBlock( + argument: LighterASTNode?, + callName: Name, + prefix: Boolean +): FirExpression { + if (argument == null) { + return FirErrorExpressionImpl(session, null, "Inc/dec without operand") + } + return FirBlockImpl(session, null).apply { + val tempName = Name.special("") + val temporaryVariable = generateTemporaryVariable(this@generateIncrementOrDecrementBlock.session, null, tempName, visitExpression(argument)) + statements += temporaryVariable + val resultName = Name.special("") + val resultInitializer = FirFunctionCallImpl(this@generateIncrementOrDecrementBlock.session, null).apply { + this.calleeReference = FirSimpleNamedReference(this@generateIncrementOrDecrementBlock.session, null, callName) + this.explicitReceiver = generateResolvedAccessExpression(this@generateIncrementOrDecrementBlock.session, null, temporaryVariable) + } + val resultVar = generateTemporaryVariable(this@generateIncrementOrDecrementBlock.session, null, resultName, resultInitializer) + val assignment = convertAssignment( + argument, + if (prefix && argument.tokenType != REFERENCE_EXPRESSION) + generateResolvedAccessExpression(this@generateIncrementOrDecrementBlock.session, null, resultVar) + else + resultInitializer, + FirOperation.ASSIGN + ) + + fun appendAssignment() { + if (assignment is FirBlock) { + statements += assignment.statements + } else { + statements += assignment + } + } + + if (prefix) { + if (argument.tokenType != REFERENCE_EXPRESSION) { + statements += resultVar + appendAssignment() + statements += generateResolvedAccessExpression(this@generateIncrementOrDecrementBlock.session, null, resultVar) + } else { + appendAssignment() + statements += generateAccessExpression(this@generateIncrementOrDecrementBlock.session, null, argument.getAsString().nameAsSafeName()) + } + } else { + appendAssignment() + statements += generateResolvedAccessExpression(this@generateIncrementOrDecrementBlock.session, null, temporaryVariable) + } + } +} + +fun bangBangToWhen(session: FirSession, baseExpression: FirExpression): FirWhenExpression { + return baseExpression.generateNotNullOrOther( + session, + FirThrowExpressionImpl( + session, null, FirFunctionCallImpl(session, null).apply { + calleeReference = FirSimpleNamedReference(session, null, RawFirBuilder.KNPE) + } + ), "bangbang", null + ) +} + +private fun getSelectorType(qualifiedAccessChildren: Array): IElementType? { + var isSelector = false + qualifiedAccessChildren.forEach { + if (it == null) return null + when (it.tokenType) { + DOT, SAFE_ACCESS -> isSelector = true + else -> if (isSelector) return it.tokenType + } + } + return null +} diff --git a/compiler/fir/lightTree/tests/org/jetbrains/kotlin/fir/lightTree/compare/TreesCompareTest.kt b/compiler/fir/lightTree/tests/org/jetbrains/kotlin/fir/lightTree/compare/TreesCompareTest.kt index fd6de6cc0a0..0e3b37f714e 100644 --- a/compiler/fir/lightTree/tests/org/jetbrains/kotlin/fir/lightTree/compare/TreesCompareTest.kt +++ b/compiler/fir/lightTree/tests/org/jetbrains/kotlin/fir/lightTree/compare/TreesCompareTest.kt @@ -7,11 +7,17 @@ package org.jetbrains.kotlin.fir.lightTree.compare import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.CharsetToolkit +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.PsiManager +import com.intellij.psi.impl.PsiFileFactoryImpl +import com.intellij.psi.impl.PsiManagerImpl import com.intellij.testFramework.TestDataPath import com.intellij.util.PathUtil import junit.framework.TestCase +import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirRenderer import org.jetbrains.kotlin.fir.builder.AbstractRawFirBuilderTestCase +import org.jetbrains.kotlin.fir.builder.RawFirBuilder import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir import org.jetbrains.kotlin.fir.lightTree.walkTopDown import org.jetbrains.kotlin.parsing.KotlinParserDefinition @@ -23,7 +29,7 @@ import java.io.File @TestDataPath("\$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners::class) class TreesCompareTest : AbstractRawFirBuilderTestCase() { - private fun compareBase(stubMode: Boolean, compareFir: (File) -> Boolean) { + private fun compareBase(compareFir: (File) -> Boolean) { val path = System.getProperty("user.dir") var counter = 0 var errorCounter = 0 @@ -40,7 +46,7 @@ class TreesCompareTest : AbstractRawFirBuilderTestCase() { private fun compareAll(stubMode: Boolean) { val lightTreeConverter = LightTree2Fir(stubMode, myProject) - compareBase(stubMode) { file -> + compareBase { file -> val text = FileUtil.loadFile(file, CharsetToolkit.UTF8, true).trim() //light tree @@ -122,7 +128,7 @@ class TreesCompareTest : AbstractRawFirBuilderTestCase() { visitAssignment = visitAssignment ) val lightTreeConverter = LightTree2Fir(stubMode, myProject) - compareBase(stubMode) { file -> + compareBase { file -> val text = FileUtil.loadFile(file, CharsetToolkit.UTF8, true).trim() //light tree diff --git a/compiler/fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt b/compiler/fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt index 9ee37624712..755e4f9b078 100644 --- a/compiler/fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt +++ b/compiler/fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt @@ -149,15 +149,15 @@ internal fun generateConstantExpressionByLiteral(expression: KtConstantExpressio } -internal fun IElementType.toBinaryName(): Name? { +fun IElementType.toBinaryName(): Name? { return OperatorConventions.BINARY_OPERATION_NAMES[this] } -internal fun IElementType.toUnaryName(): Name? { +fun IElementType.toUnaryName(): Name? { return OperatorConventions.UNARY_OPERATION_NAMES[this] } -internal fun IElementType.toFirOperation(): FirOperation = +fun IElementType.toFirOperation(): FirOperation = when (this) { KtTokens.LT -> FirOperation.LT KtTokens.GT -> FirOperation.GT @@ -181,8 +181,8 @@ internal fun IElementType.toFirOperation(): FirOperation = else -> throw AssertionError(this.toString()) } -internal fun FirExpression.generateNotNullOrOther( - session: FirSession, other: FirExpression, caseId: String, basePsi: KtElement +fun FirExpression.generateNotNullOrOther( + session: FirSession, other: FirExpression, caseId: String, basePsi: KtElement? ): FirWhenExpression { val subjectName = Name.special("<$caseId>") val subjectVariable = generateTemporaryVariable(session, basePsi, subjectName, this) @@ -209,8 +209,8 @@ internal fun FirExpression.generateNotNullOrOther( } } -internal fun FirExpression.generateLazyLogicalOperation( - other: FirExpression, isAnd: Boolean, basePsi: KtElement +fun FirExpression.generateLazyLogicalOperation( + other: FirExpression, isAnd: Boolean, basePsi: KtElement? ): FirWhenExpression { val terminalExpression = FirConstExpressionImpl(psi, IrConstKind.Boolean, !isAnd) val terminalBlock = FirSingleExpressionBlock(terminalExpression) @@ -328,11 +328,11 @@ internal fun Array.toInterpolatingCall( return if (hasExpressions) result!! else FirConstExpressionImpl(base, IrConstKind.String, sb.toString()) } -internal fun FirExpression.generateContainsOperation( +fun FirExpression.generateContainsOperation( argument: FirExpression, inverted: Boolean, base: KtExpression?, - operationReference: KtOperationReferenceExpression + operationReference: KtOperationReferenceExpression? ): FirFunctionCall { val containsCall = FirFunctionCallImpl(base).apply { calleeReference = FirSimpleNamedReference(operationReference, OperatorNameConventions.CONTAINS) @@ -425,12 +425,12 @@ internal fun generateIncrementOrDecrementBlock( } } -internal fun generateAccessExpression(psi: PsiElement?, name: Name): FirQualifiedAccessExpression = +fun generateAccessExpression(psi: PsiElement?, name: Name): FirQualifiedAccessExpression = FirQualifiedAccessExpressionImpl(psi).apply { calleeReference = FirSimpleNamedReference(psi, name) } -internal fun generateResolvedAccessExpression(psi: PsiElement?, variable: FirVariable<*>): FirQualifiedAccessExpression = +fun generateResolvedAccessExpression(psi: PsiElement?, variable: FirVariable<*>): FirQualifiedAccessExpression = FirQualifiedAccessExpressionImpl(psi).apply { calleeReference = FirResolvedCallableReferenceImpl(psi, variable.name, variable.symbol) } @@ -462,7 +462,7 @@ internal fun generateDestructuringBlock( } } -internal fun generateTemporaryVariable( +fun generateTemporaryVariable( session: FirSession, psi: PsiElement?, name: Name, initializer: FirExpression ): FirVariable<*> = FirVariableImpl(session, psi, name, FirImplicitTypeRefImpl(psi), false, initializer, FirVariableSymbol(name)).apply {