Write binary and unary expressions parsing (light tree to FIR)

This commit is contained in:
Ivan Cilcic
2019-07-16 17:25:40 +03:00
committed by Mikhail Glukhikh
parent be073ce2e6
commit a13a043169
7 changed files with 495 additions and 47 deletions
@@ -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<LighterASTNode> {
fun LighterASTNode?.getChildNodesByType(type: IElementType): List<LighterASTNode> {
return this?.forEachChildrenReturnList { node, container ->
when (node.tokenType) {
type -> container += node
@@ -33,7 +34,7 @@ open class BaseConverter(
} ?: listOf()
}
protected fun LighterASTNode?.getChildrenAsArray(): Array<LighterASTNode?> {
fun LighterASTNode?.getChildrenAsArray(): Array<LighterASTNode?> {
if (this == null) return arrayOf()
val kidsRef = Ref<Array<LighterASTNode?>>()
@@ -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) {
@@ -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<FirFunction>()
val firFunctionCalls = mutableListOf<FirFunctionCall>()
fun <T> MutableList<T>.removeLast() {
removeAt(size - 1)
@@ -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<LighterASTNode>
) : 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<FirStatement>()
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<FirTypeParameter>()
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<FirTypeProjection> {
fun convertTypeArguments(typeArguments: LighterASTNode): List<FirTypeProjection> {
return typeArguments.forEachChildrenReturnList { node, container ->
when (node.tokenType) {
TYPE_PROJECTION -> container += convertTypeProjection(node)
@@ -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<LighterASTNode>
tree: FlyweightCapableTreeStructure<LighterASTNode>,
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<FirTypeProjection>()
val firValueArguments = mutableListOf<FirExpression>()
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<LighterASTNode, List<FirExpression>> {
lateinit var expression: FirExpression
val indexes: MutableList<FirExpression> = 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}")
}
}
@@ -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("<array-set>")
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("<complex-set>")
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("<unary>")
val temporaryVariable = generateTemporaryVariable(this@generateIncrementOrDecrementBlock.session, null, tempName, visitExpression(argument))
statements += temporaryVariable
val resultName = Name.special("<unary-result>")
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<LighterASTNode?>): 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
}
@@ -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
@@ -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<KtStringTemplateEntry>.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 {