
org.jetbrains.kotlin.fir.builder.BaseFirBuilder.kt Maven / Gradle / Ivy
/*
* 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.builder
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.fir.FirFunctionTarget
import org.jetbrains.kotlin.fir.FirLoopTarget
import org.jetbrains.kotlin.fir.FirReference
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirNamedFunction
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.impl.FirErrorFunction
import org.jetbrains.kotlin.fir.declarations.impl.FirErrorLoop
import org.jetbrains.kotlin.fir.declarations.impl.FirTypeParameterImpl
import org.jetbrains.kotlin.fir.declarations.impl.addDefaultBoundIfNecessary
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.*
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirExplicitThisReference
import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.symbols.CallableId
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeParameterType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.types.impl.ConeClassTypeImpl
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.fir.types.impl.FirResolvedTypeRefImpl
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.lexer.KtTokens.CLOSING_QUOTE
import org.jetbrains.kotlin.lexer.KtTokens.OPEN_QUOTE
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtUnaryExpression
import org.jetbrains.kotlin.resolve.constants.evaluate.*
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
//T can be either PsiElement, or LighterASTNode
abstract class BaseFirBuilder(val session: FirSession, val context: Context = Context()) {
protected val implicitUnitType = session.builtinTypes.unitType
protected val implicitAnyType = session.builtinTypes.anyType
protected val implicitEnumType = session.builtinTypes.enumType
protected val implicitAnnotationType = session.builtinTypes.annotationType
abstract val T.elementType: IElementType
abstract val T.asText: String
abstract val T.unescapedValue: String
abstract fun T.getReferencedNameAsName(): Name
abstract fun T.getLabelName(): String?
abstract fun T.getExpressionInParentheses(): T?
abstract fun T.getChildNodeByType(type: IElementType): T?
abstract val T?.selectorExpression: T?
/**** Class name utils ****/
inline fun withChildClassName(name: Name, l: () -> T): T {
context.className = context.className.child(name)
val t = l()
context.className = context.className.parent()
return t
}
fun callableIdForName(name: Name, local: Boolean = false) =
when {
local -> CallableId(name)
context.className == FqName.ROOT -> CallableId(context.packageFqName, name)
else -> CallableId(context.packageFqName, context.className, name)
}
fun callableIdForClassConstructor() =
if (context.className == FqName.ROOT) CallableId(context.packageFqName, Name.special(""))
else CallableId(context.packageFqName, context.className, context.className.shortName())
/**** Function utils ****/
fun MutableList.removeLast() {
removeAt(size - 1)
}
fun MutableList.pop(): T? {
val result = lastOrNull()
if (result != null) {
removeAt(size - 1)
}
return result
}
/**** Common utils ****/
companion object {
val KNPE = Name.identifier("KotlinNullPointerException")
}
fun FirExpression.toReturn(basePsi: PsiElement? = psi, labelName: String? = null): FirReturnExpression {
return FirReturnExpressionImpl(
basePsi,
this
).apply {
target = FirFunctionTarget(labelName)
val lastFunction = context.firFunctions.lastOrNull()
if (labelName == null) {
if (lastFunction != null) {
target.bind(lastFunction)
} else {
target.bind(FirErrorFunction([email protected], psi, "Cannot bind unlabeled return to a function"))
}
} else {
for (firFunction in context.firFunctions.asReversed()) {
when (firFunction) {
is FirAnonymousFunction -> {
if (firFunction.label?.name == labelName) {
target.bind(firFunction)
return@apply
}
}
is FirNamedFunction -> {
if (firFunction.name.asString() == labelName) {
target.bind(firFunction)
return@apply
}
}
}
}
target.bind(FirErrorFunction([email protected], psi, "Cannot bind label $labelName to a function"))
}
}
}
fun KtClassOrObject?.toDelegatedSelfType(firClass: FirRegularClass): FirTypeRef {
val typeParameters = firClass.typeParameters.map {
FirTypeParameterImpl(session, it.psi, FirTypeParameterSymbol(), it.name, Variance.INVARIANT, false).apply {
this.bounds += it.bounds
addDefaultBoundIfNecessary()
}
}
return FirResolvedTypeRefImpl(
this,
ConeClassTypeImpl(
firClass.symbol.toLookupTag(),
typeParameters.map { ConeTypeParameterTypeImpl(it.symbol.toLookupTag(), false) }.toTypedArray(),
false
)
)
}
fun typeParametersFromSelfType(delegatedSelfTypeRef: FirTypeRef): List {
return delegatedSelfTypeRef.coneTypeSafe()
?.typeArguments
?.map { ((it as ConeTypeParameterType).lookupTag.symbol as FirTypeParameterSymbol).fir }
?: emptyList()
}
fun T?.bangBangToWhen(parent: KtUnaryExpression?, convert: T?.(String) -> FirExpression): FirWhenExpression {
return this.convert("No operand").generateNotNullOrOther(
session,
FirThrowExpressionImpl(
parent.getPsiOrNull(), FirFunctionCallImpl(parent.getPsiOrNull()).apply {
calleeReference = FirSimpleNamedReference(parent, KNPE)
}
), "bangbang", parent
)
}
fun FirAbstractLoop.configure(generateBlock: () -> FirBlock): FirAbstractLoop {
label = context.firLabels.pop()
context.firLoops += this
block = generateBlock()
context.firLoops.removeLast()
return this
}
fun FirLoopJump.bindLabel(expression: T): FirLoopJump {
val labelName = expression.getLabelName()
target = FirLoopTarget(labelName)
val lastLoop = context.firLoops.lastOrNull()
if (labelName == null) {
if (lastLoop != null) {
target.bind(lastLoop)
} else {
target.bind(FirErrorLoop(expression.getPsiOrNull(), "Cannot bind unlabeled jump to a loop"))
}
} else {
for (firLoop in context.firLoops.asReversed()) {
if (firLoop.label?.name == labelName) {
target.bind(firLoop)
return this
}
}
target.bind(FirErrorLoop(expression.getPsiOrNull(), "Cannot bind label $labelName to a loop"))
}
return this
}
/**** Conversion utils ****/
private fun T.getPsiOrNull(): PsiElement? {
return if (this is PsiElement) this else null
}
fun generateConstantExpressionByLiteral(expression: T): FirExpression {
val type = expression.elementType
val text: String = expression.asText
val convertedText: Any? = when (type) {
INTEGER_CONSTANT, FLOAT_CONSTANT -> parseNumericLiteral(text, type)
BOOLEAN_CONSTANT -> parseBoolean(text)
else -> null
}
return when (type) {
INTEGER_CONSTANT ->
if (convertedText is Long &&
(hasLongSuffix(text) || hasUnsignedLongSuffix(text) || hasUnsignedSuffix(text) ||
convertedText > Int.MAX_VALUE || convertedText < Int.MIN_VALUE)
) {
FirConstExpressionImpl(
expression.getPsiOrNull(), IrConstKind.Long, convertedText, "Incorrect long: $text"
)
} else if (convertedText is Number) {
// TODO: support byte / short
FirConstExpressionImpl(
expression.getPsiOrNull(), IrConstKind.Int, convertedText.toInt(), "Incorrect int: $text"
)
} else {
FirErrorExpressionImpl(expression.getPsiOrNull(), reason = "Incorrect constant expression: $text")
}
FLOAT_CONSTANT ->
if (convertedText is Float) {
FirConstExpressionImpl(
expression.getPsiOrNull(), IrConstKind.Float, convertedText, "Incorrect float: $text"
)
} else {
FirConstExpressionImpl(
expression.getPsiOrNull(), IrConstKind.Double, convertedText as Double, "Incorrect double: $text"
)
}
CHARACTER_CONSTANT ->
FirConstExpressionImpl(
expression.getPsiOrNull(), IrConstKind.Char, text.parseCharacter(), "Incorrect character: $text"
)
BOOLEAN_CONSTANT ->
FirConstExpressionImpl(expression.getPsiOrNull(), IrConstKind.Boolean, convertedText as Boolean)
NULL ->
FirConstExpressionImpl(expression.getPsiOrNull(), IrConstKind.Null, null)
else ->
throw AssertionError("Unknown literal type: $type, $text")
}
}
fun Array.toInterpolatingCall(
base: KtStringTemplateExpression?,
convertTemplateEntry: T?.(String) -> FirExpression
): FirExpression {
val sb = StringBuilder()
var hasExpressions = false
var result: FirExpression? = null
var callCreated = false
L@ for (entry in this) {
if (entry == null) continue
val nextArgument = when (entry.elementType) {
OPEN_QUOTE, CLOSING_QUOTE -> continue@L
LITERAL_STRING_TEMPLATE_ENTRY -> {
sb.append(entry.asText)
FirConstExpressionImpl(entry.getPsiOrNull(), IrConstKind.String, entry.asText)
}
ESCAPE_STRING_TEMPLATE_ENTRY -> {
sb.append(entry.unescapedValue)
FirConstExpressionImpl(entry.getPsiOrNull(), IrConstKind.String, entry.unescapedValue)
}
SHORT_STRING_TEMPLATE_ENTRY, LONG_STRING_TEMPLATE_ENTRY -> {
hasExpressions = true
entry.convertTemplateEntry("Incorrect template argument")
}
else -> {
hasExpressions = true
FirErrorExpressionImpl(
entry.getPsiOrNull(), "Incorrect template entry: ${entry.asText}"
)
}
}
result = when {
result == null -> nextArgument
callCreated && result is FirStringConcatenationCallImpl -> result.apply {
arguments += nextArgument
}
else -> {
callCreated = true
FirStringConcatenationCallImpl(base).apply {
arguments += result!!
arguments += nextArgument
}
}
}
}
return if (hasExpressions) result!! else FirConstExpressionImpl(base, IrConstKind.String, sb.toString())
}
/**
* given:
* argument++
*
* result:
* {
* val = argument
* argument = .inc()
* ^
* }
*
* given:
* ++argument
*
* result:
* {
* val = argument
* argument = .inc()
* ^argument
* }
*
*/
// TODO: Refactor, support receiver capturing in case of a.b
fun generateIncrementOrDecrementBlock(
baseExpression: KtUnaryExpression?,
argument: T?,
callName: Name,
prefix: Boolean,
convert: T.() -> FirExpression
): FirExpression {
if (argument == null) {
return FirErrorExpressionImpl(argument, "Inc/dec without operand")
}
return FirBlockImpl(baseExpression).apply {
val tempName = Name.special("")
val temporaryVariable = generateTemporaryVariable([email protected], baseExpression, tempName, argument.convert())
statements += temporaryVariable
val resultName = Name.special("")
val resultInitializer = FirFunctionCallImpl(baseExpression).apply {
this.calleeReference = FirSimpleNamedReference(baseExpression?.operationReference, callName)
this.explicitReceiver = generateResolvedAccessExpression(baseExpression, temporaryVariable)
}
val resultVar = generateTemporaryVariable([email protected], baseExpression, resultName, resultInitializer)
val assignment = argument.generateAssignment(
baseExpression,
if (prefix && argument.elementType != REFERENCE_EXPRESSION)
generateResolvedAccessExpression(baseExpression, resultVar)
else
resultInitializer,
FirOperation.ASSIGN, convert
)
fun appendAssignment() {
if (assignment is FirBlock) {
statements += assignment.statements
} else {
statements += assignment
}
}
if (prefix) {
if (argument.elementType != REFERENCE_EXPRESSION) {
statements += resultVar
appendAssignment()
statements += generateResolvedAccessExpression(baseExpression, resultVar)
} else {
appendAssignment()
statements += generateAccessExpression(baseExpression, argument.getReferencedNameAsName())
}
} else {
appendAssignment()
statements += generateResolvedAccessExpression(baseExpression, temporaryVariable)
}
}
}
private fun FirModifiableQualifiedAccess<*>.initializeLValue(
left: T?,
convertQualified: T.() -> FirQualifiedAccess?
): FirReference {
val tokenType = left?.elementType
if (left != null) {
when (tokenType) {
REFERENCE_EXPRESSION -> {
return FirSimpleNamedReference(left.getPsiOrNull(), left.getReferencedNameAsName())
}
THIS_EXPRESSION -> {
return FirExplicitThisReference(left.getPsiOrNull(), left.getLabelName())
}
DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION -> {
val firMemberAccess = left.convertQualified()
return if (firMemberAccess != null) {
explicitReceiver = firMemberAccess.explicitReceiver
safe = firMemberAccess.safe
firMemberAccess.calleeReference
} else {
FirErrorNamedReference(
left.getPsiOrNull(), "Unsupported qualified LValue: ${left.asText}"
)
}
}
PARENTHESIZED -> {
return initializeLValue(left.getExpressionInParentheses(), convertQualified)
}
}
}
return FirErrorNamedReference(left.getPsiOrNull(), "Unsupported LValue: $tokenType")
}
fun T?.generateAssignment(
psi: PsiElement?,
value: FirExpression,
operation: FirOperation,
convert: T.() -> FirExpression
): FirStatement {
val tokenType = this?.elementType
if (tokenType == PARENTHESIZED) {
return this!!.getExpressionInParentheses().generateAssignment(psi, value, operation, convert)
}
if (tokenType == ARRAY_ACCESS_EXPRESSION) {
val firArrayAccess = this!!.convert() as FirFunctionCallImpl
val arraySet = if (operation != FirOperation.ASSIGN) {
FirArraySetCallImpl(psi, value, operation).apply {
indexes += firArrayAccess.arguments
}
} else {
return firArrayAccess.apply {
calleeReference = FirSimpleNamedReference(psi, OperatorNameConventions.SET)
arguments += value
}
}
val arrayExpression = this.getChildNodeByType(REFERENCE_EXPRESSION)
if (arrayExpression != null) {
return arraySet.apply {
lValue = initializeLValue(arrayExpression) { convert() as? FirQualifiedAccess }
}
}
val psiArrayExpression = firArrayAccess.explicitReceiver?.psi
return FirBlockImpl(psiArrayExpression).apply {
val name = Name.special("")
statements += generateTemporaryVariable(
[email protected], [email protected](), name, firArrayAccess.explicitReceiver!!
)
statements += arraySet.apply { lValue = FirSimpleNamedReference(psiArrayExpression, name) }
}
}
if (operation != FirOperation.ASSIGN &&
tokenType != REFERENCE_EXPRESSION && tokenType != THIS_EXPRESSION &&
((tokenType != DOT_QUALIFIED_EXPRESSION && tokenType != SAFE_ACCESS_EXPRESSION) || this.selectorExpression?.elementType != REFERENCE_EXPRESSION)
) {
return FirBlockImpl(this.getPsiOrNull()).apply {
val name = Name.special("")
statements += generateTemporaryVariable(
[email protected], [email protected](), name,
this@generateAssignment?.convert()
?: FirErrorExpressionImpl(this.getPsiOrNull(), "No LValue in assignment")
)
statements += FirVariableAssignmentImpl(psi, value, operation).apply {
lValue = FirSimpleNamedReference(this.getPsiOrNull(), name)
}
}
}
return FirVariableAssignmentImpl(psi, value, operation).apply {
lValue = initializeLValue(this@generateAssignment) { convert() as? FirQualifiedAccess }
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy