org.jetbrains.kotlin.fir.backend.Fir2IrVisitor.kt Maven / Gradle / Ivy
The newest version!
/*
* 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.backend
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.contracts.description.LogicOperationKind
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.isObject
import org.jetbrains.kotlin.diagnostics.findChildByType
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.backend.generators.ClassMemberGenerator
import org.jetbrains.kotlin.fir.backend.generators.OperatorExpressionGenerator
import org.jetbrains.kotlin.fir.backend.utils.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.SCRIPT_RECEIVER_NAME_PREFIX
import org.jetbrains.kotlin.fir.declarations.utils.isSealed
import org.jetbrains.kotlin.fir.declarations.utils.isSynthetic
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.deserialization.toQualifiedPropertyAccessExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.*
import org.jetbrains.kotlin.fir.extensions.extensionService
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.FirSuperReference
import org.jetbrains.kotlin.fir.references.toResolvedNamedFunctionSymbol
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrErrorClassImpl
import org.jetbrains.kotlin.ir.types.impl.IrErrorTypeImpl
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultConstructor
import org.jetbrains.kotlin.ir.util.defaultValueForType
import org.jetbrains.kotlin.ir.util.isSubtypeOfClass
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.applyIf
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.runUnless
import org.jetbrains.kotlin.utils.findIsInstanceAnd
class Fir2IrVisitor(
private val c: Fir2IrComponents,
private val conversionScope: Fir2IrConversionScope
) : Fir2IrComponents by c, FirDefaultVisitor() {
internal val implicitCastInserter = Fir2IrImplicitCastInserter(c)
private val memberGenerator = ClassMemberGenerator(c, this, conversionScope)
private val operatorGenerator = OperatorExpressionGenerator(c, this, conversionScope)
private var _annotationMode: Boolean = false
val annotationMode: Boolean
get() = _annotationMode
internal inline fun withAnnotationMode(enableAnnotationMode: Boolean = true, block: () -> T): T {
val oldAnnotationMode = _annotationMode
_annotationMode = enableAnnotationMode
try {
return block()
} finally {
_annotationMode = oldAnnotationMode
}
}
override fun visitElement(element: FirElement, data: Any?): IrElement {
error("Should not be here: ${element::class} ${element.render()}")
}
override fun visitField(field: FirField, data: Any?): IrField = whileAnalysing(session, field) {
require(field.isSynthetic) { "Non-synthetic field found during traversal of FIR tree: ${field.render()}" }
@OptIn(UnsafeDuringIrConstructionAPI::class)
return declarationStorage.getCachedIrFieldSymbolForSupertypeDelegateField(field)!!.owner.apply {
// If this is a property backing field, then it has no separate initializer,
// so we shouldn't convert it
if (correspondingPropertySymbol == null) {
memberGenerator.convertFieldContent(this, field)
}
}
}
override fun visitFile(file: FirFile, data: Any?): IrFile {
val irFile = declarationStorage.getIrFile(file)
conversionScope.withParent(irFile) {
file.declarations.forEach {
it.toIrDeclaration()
}
annotationGenerator.generate(this, file)
metadata = FirMetadataSource.File(file)
}
return irFile
}
private fun FirDeclaration.toIrDeclaration(): IrDeclaration =
accept(this@Fir2IrVisitor, null) as IrDeclaration
// ==================================================================================
override fun visitTypeAlias(typeAlias: FirTypeAlias, data: Any?): IrElement = whileAnalysing(session, typeAlias) {
val irTypeAlias = classifierStorage.getCachedTypeAlias(typeAlias)!!
annotationGenerator.generate(irTypeAlias, typeAlias)
return irTypeAlias
}
override fun visitEnumEntry(enumEntry: FirEnumEntry, data: Any?): IrElement = whileAnalysing(session, enumEntry) {
// At this point all IR for source enum entries should be created and bound to symbols
@OptIn(UnsafeDuringIrConstructionAPI::class)
val irEnumEntry = classifierStorage.getIrEnumEntrySymbol(enumEntry).owner
annotationGenerator.generate(irEnumEntry, enumEntry)
if (configuration.skipBodies) return irEnumEntry
val correspondingClass = irEnumEntry.correspondingClass
val initializer = enumEntry.initializer
val irType = enumEntry.returnTypeRef.toIrType(c)
val irParentEnumClass = irEnumEntry.parent as? IrClass
// If the enum entry has its own members, we need to introduce a synthetic class.
if (correspondingClass != null) {
declarationStorage.enterScope(irEnumEntry.symbol)
classifierStorage.putEnumEntryClassInScope(enumEntry, correspondingClass)
val anonymousObject = (enumEntry.initializer as FirAnonymousObjectExpression).anonymousObject
converter.processAnonymousObjectHeaders(anonymousObject, correspondingClass)
converter.processClassMembers(anonymousObject, correspondingClass)
conversionScope.withParent(correspondingClass) {
memberGenerator.convertClassContent(correspondingClass, anonymousObject)
// `correspondingClass` definitely is not a lazy class
@OptIn(UnsafeDuringIrConstructionAPI::class)
val constructor = correspondingClass.constructors.first()
irEnumEntry.initializerExpression = IrFactoryImpl.createExpressionBody(
IrEnumConstructorCallImpl(
startOffset, endOffset, irType,
constructor.symbol,
typeArgumentsCount = constructor.typeParameters.size,
)
)
}
declarationStorage.leaveScope(irEnumEntry.symbol)
} else if (initializer is FirAnonymousObjectExpression) {
// Otherwise, this is a default-ish enum entry, which doesn't need its own synthetic class.
// During raw FIR building, we put the delegated constructor call inside an anonymous object.
val delegatedConstructor = initializer.anonymousObject.primaryConstructorIfAny(session)?.fir?.delegatedConstructor
if (delegatedConstructor != null) {
with(memberGenerator) {
irEnumEntry.initializerExpression = IrFactoryImpl.createExpressionBody(
delegatedConstructor.toIrDelegatingConstructorCall()
)
}
}
} else if (irParentEnumClass != null && !irParentEnumClass.isExpect && initializer == null) {
// a default-ish enum entry whose initializer would be a delegating constructor call
// `irParentEnumClass` definitely is not a lazy class
@OptIn(UnsafeDuringIrConstructionAPI::class)
val constructor = irParentEnumClass.defaultConstructor
?: error("Assuming that default constructor should exist and be converted at this point")
enumEntry.convertWithOffsets { startOffset, endOffset ->
irEnumEntry.initializerExpression = IrFactoryImpl.createExpressionBody(
IrEnumConstructorCallImpl(
startOffset, endOffset, irType, constructor.symbol,
typeArgumentsCount = constructor.typeParameters.size
)
)
irEnumEntry
}
}
return irEnumEntry
}
override fun visitRegularClass(regularClass: FirRegularClass, data: Any?): IrElement = whileAnalysing(session, regularClass) {
if (regularClass.visibility == Visibilities.Local) {
val irParent = conversionScope.parentFromStack()
// NB: for implicit types it is possible that local class is already cached
val irClass = classifierStorage.getCachedIrLocalClass(regularClass)?.apply { this.parent = irParent }
if (irClass != null) {
conversionScope.withParent(irClass) {
memberGenerator.convertClassContent(irClass, regularClass)
}
return irClass
}
converter.processLocalClassAndNestedClasses(regularClass, irParent)
}
val irClass = classifierStorage.getIrClass(regularClass)
if (regularClass.isSealed) {
irClass.sealedSubclasses = regularClass.getIrSymbolsForSealedSubclasses(c)
}
conversionScope.withParent(irClass) {
memberGenerator.convertClassContent(irClass, regularClass)
}
return irClass
}
@OptIn(UnexpandedTypeCheck::class)
override fun visitScript(script: FirScript, data: Any?): IrElement {
return declarationStorage.getCachedIrScript(script)!!.also { irScript ->
irScript.parent = conversionScope.parentFromStack()
declarationStorage.enterScope(irScript.symbol)
irScript.explicitCallParameters = script.parameters.map { parameter ->
declarationStorage.createAndCacheIrVariable(
parameter,
irScript,
givenOrigin = IrDeclarationOrigin.SCRIPT_CALL_PARAMETER
)
}
// NOTE: index should correspond to one generated in the collectTowerDataElementsForScript
irScript.implicitReceiversParameters = script.receivers.mapIndexedNotNull { index, receiver ->
val isSelf = receiver.isBaseClassReceiver
val name =
if (isSelf) SpecialNames.THIS
else Name.identifier("${SCRIPT_RECEIVER_NAME_PREFIX}_$index")
val origin = if (isSelf) IrDeclarationOrigin.INSTANCE_RECEIVER else IrDeclarationOrigin.SCRIPT_IMPLICIT_RECEIVER
val irReceiver =
receiver.convertWithOffsets { startOffset, endOffset ->
IrFactoryImpl.createValueParameter(
startOffset, endOffset, origin, IrParameterKind.Regular, name, receiver.typeRef.toIrType(c),
isAssignable = false,
IrValueParameterSymbolImpl(),
varargElementType = null, isCrossinline = false, isNoinline = false, isHidden = false
).also {
it.parent = irScript
if (!isSelf) {
@OptIn(DelicateIrParameterIndexSetter::class)
it.indexInOldValueParameters = index
}
}
}
if (isSelf) {
irReceiver.kind = IrParameterKind.DispatchReceiver
irScript.thisReceiver = irReceiver
irScript.baseClass = irReceiver.type
null
} else irReceiver
}
conversionScope.withParent(irScript) {
val destructComposites = mutableMapOf, IrComposite>()
for (statement in script.declarations) {
if (statement !is FirAnonymousInitializer) {
val irStatement = when {
statement is FirProperty && statement.name == SpecialNames.UNDERSCORE_FOR_UNUSED_VAR -> {
continue
}
statement is FirProperty && statement.origin == FirDeclarationOrigin.ScriptCustomization.ResultProperty -> {
// Generating the result property only for expressions with a meaningful result type
// otherwise skip the property and convert the expression into the statement
if (statement.returnTypeRef.let { (it.isUnit || it.isNothing || it.isNullableNothing) }) {
statement.initializer!!.toIrStatement()
} else {
(statement.accept(this@Fir2IrVisitor, null) as? IrDeclaration)?.also {
irScript.resultProperty = (it as? IrProperty)?.symbol
}
}
}
statement is FirVariable && statement.isDestructuringDeclarationContainerVariable == true -> {
statement.convertWithOffsets { startOffset, endOffset ->
IrCompositeImpl(
startOffset, endOffset,
builtins.unitType, IrStatementOrigin.DESTRUCTURING_DECLARATION
).also { composite ->
composite.statements.add(
declarationStorage.createAndCacheIrVariable(statement, conversionScope.parentFromStack()).also {
it.initializer = statement.initializer?.toIrStatement() as? IrExpression
}
)
destructComposites[(statement).symbol] = composite
}
}
}
statement is FirProperty && statement.destructuringDeclarationContainerVariable != null -> {
(statement.accept(this@Fir2IrVisitor, null) as IrProperty).also {
val irComponentInitializer = IrSetFieldImpl(
it.startOffset, it.endOffset,
it.backingField!!.symbol,
builtins.unitType,
origin = null, superQualifierSymbol = null
).apply {
value = it.backingField!!.initializer!!.expression
receiver = null
}
val correspondingComposite = destructComposites[statement.destructuringDeclarationContainerVariable!!]!!
correspondingComposite.statements.add(irComponentInitializer)
it.backingField!!.initializer = null
}
}
statement is FirClass -> {
statement.accept(this@Fir2IrVisitor, null) as IrClass
}
else -> {
statement.accept(this@Fir2IrVisitor, null) as? IrDeclaration
}
}
irScript.statements.add(irStatement!!)
} else {
statement.body?.statements?.mapNotNull { it.toIrStatement() }?.let {
irScript.statements.addAll(it)
}
}
}
}
for (configurator in session.extensionService.fir2IrScriptConfigurators) {
with(configurator) {
irScript.configure(script) { declarationStorage.getCachedIrScript(it.fir)?.symbol }
}
}
declarationStorage.leaveScope(irScript.symbol)
}
}
override fun visitCodeFragment(codeFragment: FirCodeFragment, data: Any?): IrElement {
val irClass = classifierStorage.getCachedIrCodeFragment(codeFragment)!!
// class for code fragment definitely is not a lazy class
@OptIn(UnsafeDuringIrConstructionAPI::class)
val irFunction = irClass.declarations.firstIsInstance()
declarationStorage.enterScope(irFunction.symbol)
conversionScope.withParent(irFunction) {
irFunction.body = if (configuration.skipBodies) {
IrFactoryImpl.createExpressionBody(IrConstImpl.defaultValueForType(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.returnType))
} else {
val irBlock = codeFragment.block.convertToIrBlock(forceUnitType = false)
IrFactoryImpl.createExpressionBody(irBlock)
}
}
declarationStorage.leaveScope(irFunction.symbol)
return irFunction
}
override fun visitAnonymousObjectExpression(anonymousObjectExpression: FirAnonymousObjectExpression, data: Any?): IrElement {
return visitAnonymousObject(anonymousObjectExpression.anonymousObject, data)
}
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: Any?): IrElement = whileAnalysing(
session, anonymousObject
) {
val irParent = conversionScope.parentFromStack()
// NB: for implicit types it is possible that anonymous object is already cached
val irAnonymousObject = classifierStorage.getCachedIrLocalClass(anonymousObject)?.apply { this.parent = irParent }
?: converter.processLocalClassAndNestedClasses(anonymousObject, irParent)
conversionScope.withParent(irAnonymousObject) {
memberGenerator.convertClassContent(irAnonymousObject, anonymousObject)
}
val anonymousClassType = irAnonymousObject.thisReceiver!!.type
return anonymousObject.convertWithOffsets { startOffset, endOffset ->
IrBlockImpl(
startOffset, endOffset, anonymousClassType, IrStatementOrigin.OBJECT_LITERAL,
listOf(
irAnonymousObject,
IrConstructorCallImpl.fromSymbolOwner(
startOffset,
endOffset,
anonymousClassType,
// a class for an anonymous object definitely is not a lazy class
@OptIn(UnsafeDuringIrConstructionAPI::class)
irAnonymousObject.constructors.first().symbol,
irAnonymousObject.typeParameters.size,
origin = IrStatementOrigin.OBJECT_LITERAL
)
)
)
}
}
// ==================================================================================
override fun visitConstructor(constructor: FirConstructor, data: Any?): IrElement = whileAnalysing(session, constructor) {
@OptIn(UnsafeDuringIrConstructionAPI::class)
val irConstructor = declarationStorage.getCachedIrConstructorSymbol(constructor)!!.owner
return conversionScope.withFunction(irConstructor) {
memberGenerator.convertFunctionContent(irConstructor, constructor, containingClass = conversionScope.containerFirClass())
}
}
override fun visitAnonymousInitializer(
anonymousInitializer: FirAnonymousInitializer,
data: Any?
): IrElement = whileAnalysing(session, anonymousInitializer) {
val irAnonymousInitializer = declarationStorage.getIrAnonymousInitializer(anonymousInitializer)
declarationStorage.enterScope(irAnonymousInitializer.symbol)
conversionScope.withInitBlock(irAnonymousInitializer) {
irAnonymousInitializer.body =
if (configuration.skipBodies) IrFactoryImpl.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET)
else convertToIrBlockBody(anonymousInitializer.body!!)
}
declarationStorage.leaveScope(irAnonymousInitializer.symbol)
return irAnonymousInitializer
}
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: Any?): IrElement = whileAnalysing(session, simpleFunction) {
val irFunction = if (simpleFunction.visibility == Visibilities.Local) {
declarationStorage.createAndCacheIrFunction(
simpleFunction, irParent = conversionScope.parent(), predefinedOrigin = IrDeclarationOrigin.LOCAL_FUNCTION, isLocal = true
)
} else {
@OptIn(UnsafeDuringIrConstructionAPI::class)
declarationStorage.getCachedIrFunctionSymbol(simpleFunction)!!.owner
}
return conversionScope.withFunction(irFunction) {
memberGenerator.convertFunctionContent(
irFunction, simpleFunction, containingClass = conversionScope.containerFirClass()
)
}
}
override fun visitAnonymousFunctionExpression(anonymousFunctionExpression: FirAnonymousFunctionExpression, data: Any?): IrElement {
return visitAnonymousFunction(anonymousFunctionExpression.anonymousFunction, data)
}
override fun visitAnonymousFunction(
anonymousFunction: FirAnonymousFunction,
data: Any?
): IrElement = whileAnalysing(session, anonymousFunction) {
return anonymousFunction.convertWithOffsets { startOffset, endOffset ->
val irFunction = declarationStorage.createAndCacheIrFunction(
anonymousFunction,
irParent = conversionScope.parent(),
predefinedOrigin = IrDeclarationOrigin.LOCAL_FUNCTION,
isLocal = true
)
conversionScope.withFunction(irFunction) {
memberGenerator.convertFunctionContent(irFunction, anonymousFunction, containingClass = null)
}
val type = anonymousFunction.typeRef.toIrType(c)
IrFunctionExpressionImpl(
startOffset, endOffset, type, irFunction,
if (irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) IrStatementOrigin.LAMBDA
else IrStatementOrigin.ANONYMOUS_FUNCTION
)
}
}
private fun visitLocalVariable(variable: FirProperty): IrElement = whileAnalysing(session, variable) {
assert(variable.isLocal)
val delegate = variable.delegate
if (delegate != null) {
val irProperty = declarationStorage.createAndCacheIrLocalDelegatedProperty(variable, conversionScope.parentFromStack())
irProperty.delegate.initializer = convertToIrExpression(delegate, isDelegate = true)
conversionScope.withFunction(irProperty.getter) {
memberGenerator.convertFunctionContent(irProperty.getter, variable.getter, null)
}
irProperty.setter?.let {
conversionScope.withFunction(it) {
memberGenerator.convertFunctionContent(it, variable.setter, null)
}
}
return irProperty
}
val initializer = variable.initializer
val isNextVariable = initializer is FirFunctionCall &&
initializer.calleeReference.toResolvedNamedFunctionSymbol()?.callableId?.isIteratorNext() == true &&
variable.source?.isChildOfForLoop == true
val irVariable = declarationStorage.createAndCacheIrVariable(
variable, conversionScope.parentFromStack(),
if (isNextVariable) {
if (variable.name.isSpecial && variable.name == SpecialNames.DESTRUCT) {
IrDeclarationOrigin.IR_TEMPORARY_VARIABLE
} else {
IrDeclarationOrigin.FOR_LOOP_VARIABLE
}
} else {
null
}
)
if (initializer != null) {
irVariable.initializer =
convertToIrExpression(initializer)
.insertImplicitCast(initializer, initializer.resolvedType, variable.returnTypeRef.coneType)
}
annotationGenerator.generate(irVariable, variable)
return irVariable
}
private fun IrExpression.insertImplicitCast(
baseExpression: FirExpression,
valueType: ConeKotlinType,
expectedType: ConeKotlinType,
) =
with(implicitCastInserter) {
[email protected](baseExpression, valueType, expectedType)
}
override fun visitProperty(property: FirProperty, data: Any?): IrElement = whileAnalysing(session, property) {
if (property.isLocal) return visitLocalVariable(property)
@OptIn(UnsafeDuringIrConstructionAPI::class)
val irProperty = declarationStorage.getCachedIrPropertySymbol(property, fakeOverrideOwnerLookupTag = null)?.owner
?: return IrErrorExpressionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
IrErrorTypeImpl(null, emptyList(), Variance.INVARIANT),
"Stub for Enum.entries"
)
return conversionScope.withProperty(irProperty, property) {
memberGenerator.convertPropertyContent(irProperty, property)
}
}
// ==================================================================================
override fun visitReturnExpression(returnExpression: FirReturnExpression, data: Any?): IrElement {
val result = returnExpression.result
if (result is FirThrowExpression) {
// Note: in FIR we must have 'return' as the last statement
return convertToIrExpression(result)
}
val irTarget = conversionScope.returnTarget(returnExpression, declarationStorage)
return returnExpression.convertWithOffsets { startOffset, endOffset ->
// For implicit returns, use the expression endOffset to generate the expected line number for debugging.
val returnStartOffset = if (returnExpression.source?.kind is KtFakeSourceElementKind.ImplicitReturn) endOffset else startOffset
IrReturnImpl(returnStartOffset, endOffset, builtins.nothingType, irTarget, convertToIrExpression(result))
}.let {
returnExpression.accept(implicitCastInserter, it)
}
}
override fun visitWrappedArgumentExpression(wrappedArgumentExpression: FirWrappedArgumentExpression, data: Any?): IrElement {
// Note: we deal with specific arguments in CallAndReferenceGenerator
return convertToIrExpression(wrappedArgumentExpression.expression)
}
override fun visitSamConversionExpression(samConversionExpression: FirSamConversionExpression, data: Any?): IrElement {
return convertToIrExpression(samConversionExpression.expression)
}
override fun visitVarargArgumentsExpression(varargArgumentsExpression: FirVarargArgumentsExpression, data: Any?): IrElement {
return varargArgumentsExpression.convertWithOffsets { startOffset, endOffset ->
IrVarargImpl(
startOffset,
endOffset,
varargArgumentsExpression.resolvedType.toIrType(c),
varargArgumentsExpression.coneElementTypeOrNull?.toIrType(c)
?: error("Vararg expression has incorrect type"),
varargArgumentsExpression.arguments.mapNotNull {
if (isGetClassOfUnresolvedTypeInAnnotation(it)) null
else it.convertToIrVarargElement()
}
)
}
}
private fun FirExpression.convertToIrVarargElement(): IrVarargElement =
if (this is FirSpreadArgumentExpression) {
IrSpreadElementImpl(
source?.startOffset ?: UNDEFINED_OFFSET,
source?.endOffset ?: UNDEFINED_OFFSET,
convertToIrExpression(this)
)
} else convertToIrExpression(this)
private fun convertToIrCall(functionCall: FirFunctionCall): IrExpression {
if (functionCall.isCalleeDynamic &&
functionCall.calleeReference.name == OperatorNameConventions.SET &&
functionCall.calleeReference.source?.kind == KtFakeSourceElementKind.ArrayAccessNameReference
) {
return convertToIrArraySetDynamicCall(functionCall)
}
return convertToIrCall(functionCall, dynamicOperator = null)
}
private fun convertToIrCall(
functionCall: FirFunctionCall,
dynamicOperator: IrDynamicOperator?
): IrExpression {
val explicitReceiverExpression = convertToIrReceiverExpression(
functionCall.explicitReceiver,
functionCall
)
return callGenerator.convertToIrCall(
functionCall,
functionCall.resolvedType,
explicitReceiverExpression,
dynamicOperator
)
}
private fun convertToIrArraySetDynamicCall(functionCall: FirFunctionCall): IrExpression {
// `functionCall` has the form of `myDynamic.set(key1, key2, ..., newValue)`.
// The resulting IR expects something like `myDynamic.ARRAY_ACCESS(key1, key2, ...).EQ(newValue)`.
// Instead of constructing a `FirFunctionCall` for `get()` (the true `ARRAY_ACCESS`), and a new
// call for `set()` (`EQ`), let's convert the whole thing as `ARRAY_ACCESS`, including
// `newValue`, and then manually move it to a newly constructed EQ call.
val arraySetAsGenericDynamicAccess = convertToIrCall(functionCall, IrDynamicOperator.ARRAY_ACCESS) as? IrDynamicOperatorExpression
?: error("Converting dynamic array access should have resulted in IrDynamicOperatorExpression")
val arraySetNewValue = arraySetAsGenericDynamicAccess.arguments.removeLast()
return IrDynamicOperatorExpressionImpl(
arraySetAsGenericDynamicAccess.startOffset,
arraySetAsGenericDynamicAccess.endOffset,
arraySetAsGenericDynamicAccess.type,
IrDynamicOperator.EQ,
).apply {
receiver = arraySetAsGenericDynamicAccess
arguments.add(arraySetNewValue)
}
}
override fun visitFunctionCall(functionCall: FirFunctionCall, data: Any?): IrExpression = whileAnalysing(session, functionCall) {
return convertToIrCall(functionCall = functionCall)
}
override fun visitSafeCallExpression(
safeCallExpression: FirSafeCallExpression,
data: Any?
): IrElement = whileAnalysing(session, safeCallExpression) {
val explicitReceiverExpression = convertToIrExpression(safeCallExpression.receiver)
val (receiverVariable, variableSymbol) = conversionScope.createTemporaryVariableForSafeCallConstruction(
explicitReceiverExpression
)
return conversionScope.withSafeCallSubject(receiverVariable) {
val afterNotNullCheck =
(safeCallExpression.selector as? FirExpression)?.let(::convertToIrExpression)
?: safeCallExpression.selector.accept(this, data) as IrExpression
c.createSafeCallConstruction(receiverVariable, variableSymbol, afterNotNullCheck)
}
}
override fun visitCheckedSafeCallSubject(checkedSafeCallSubject: FirCheckedSafeCallSubject, data: Any?): IrElement {
val lastSubjectVariable = conversionScope.lastSafeCallSubject()
return checkedSafeCallSubject.convertWithOffsets { startOffset, endOffset ->
IrGetValueImpl(startOffset, endOffset, lastSubjectVariable.type, lastSubjectVariable.symbol)
}
}
override fun visitAnnotation(annotation: FirAnnotation, data: Any?): IrElement {
return callGenerator.convertToIrConstructorCall(annotation)
}
override fun visitAnnotationCall(annotationCall: FirAnnotationCall, data: Any?): IrElement = whileAnalysing(session, annotationCall) {
return callGenerator.convertToIrConstructorCall(annotationCall)
}
override fun visitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression, data: Any?): IrElement {
return convertQualifiedAccessExpression(qualifiedAccessExpression)
}
override fun visitPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression, data: Any?): IrElement {
return convertQualifiedAccessExpression(propertyAccessExpression)
}
private fun convertQualifiedAccessExpression(
qualifiedAccessExpression: FirQualifiedAccessExpression,
): IrExpression = whileAnalysing(session, qualifiedAccessExpression) {
val explicitReceiverExpression = convertToIrReceiverExpression(
qualifiedAccessExpression.explicitReceiver, qualifiedAccessExpression
)
return callGenerator.convertToIrCall(
qualifiedAccessExpression, qualifiedAccessExpression.resolvedType, explicitReceiverExpression
)
}
// Note that this mimics psi2ir [StatementGenerator#shouldGenerateReceiverAsSingletonReference].
private fun shouldGenerateReceiverAsSingletonReference(irClassSymbol: IrClassSymbol): Boolean {
val scopeOwner = conversionScope.parent()
// For anonymous initializers
if ((scopeOwner as? IrDeclaration)?.symbol == irClassSymbol) return false
// Members of object
return when (scopeOwner) {
is IrFunction, is IrProperty, is IrField -> {
val parent = (scopeOwner as IrDeclaration).parent as? IrDeclaration
parent?.symbol != irClassSymbol
}
else -> true
}
}
override fun visitThisReceiverExpression(
thisReceiverExpression: FirThisReceiverExpression,
data: Any?
): IrElement = whileAnalysing(session, thisReceiverExpression) {
val calleeReference = thisReceiverExpression.calleeReference
val boundSymbol = calleeReference.boundSymbol
// If not a context receiver of a class
// TODO: after KT-72994 injectGetValueCall can be used unconditionally
// TODO: add tests, currently can be replaced with if (false) without breaking anything
if (boundSymbol !is FirValueParameterSymbol || boundSymbol.containingDeclarationSymbol.fir !is FirClass) {
callGenerator.injectGetValueCall(thisReceiverExpression, calleeReference)?.let { return it }
}
when (val declarationSymbol = calleeReference.referencedMemberSymbol) {
is FirClassSymbol -> generateThisReceiverAccessForClass(thisReceiverExpression, declarationSymbol)
is FirScriptSymbol -> generateThisReceiverAccessForScript(thisReceiverExpression, declarationSymbol)
is FirCallableSymbol -> generateThisReceiverAccessForCallable(thisReceiverExpression, declarationSymbol)
else -> null
} ?: visitQualifiedAccessExpression(thisReceiverExpression, data)
}
private fun generateThisReceiverAccessForClass(
thisReceiverExpression: FirThisReceiverExpression,
firClassSymbol: FirClassSymbol<*>,
): IrElement? {
// Object case
val calleeReference = thisReceiverExpression.calleeReference
val firClass = firClassSymbol.fir
val irClassSymbol = if (firClass.origin.fromSource || firClass.origin.generated) {
// We anyway can use 'else' branch as fallback, but
// this is an additional check of FIR2IR invariants
// (source classes should be already built when we analyze bodies)
classifierStorage.getIrClass(firClass).symbol
} else {
/*
* The only case when we can refer to non-source this is resolution to companion object of parent
* class in some constructor scope:
*
* // MODULE: lib
* abstract class Base {
* companion object {
* fun foo(): Int = 1
* }
* }
*
* // MODULE: app(lib)
* class Derived(
* val x: Int = foo() // this: Base.Companion
* ) : Base()
*/
classifierStorage.getIrClassSymbol(firClassSymbol)
}
if (firClass.classKind.isObject && shouldGenerateReceiverAsSingletonReference(irClassSymbol)) {
return thisReceiverExpression.convertWithOffsets { startOffset, endOffset ->
val irType = firClassSymbol.defaultType().toIrType(c)
IrGetObjectValueImpl(startOffset, endOffset, irType, irClassSymbol)
}
}
val irClass = conversionScope.findDeclarationInParentsStack(irClassSymbol)
val dispatchReceiver = conversionScope.dispatchReceiverParameter(irClass) ?: return null
return thisReceiverExpression.convertWithOffsets { startOffset, endOffset ->
val thisRef = callGenerator.findInjectedValue(calleeReference)?.let {
callGenerator.useInjectedValue(it, calleeReference, startOffset, endOffset)
} ?: IrGetValueImpl(startOffset, endOffset, dispatchReceiver.type, dispatchReceiver.symbol)
val referencedFir = calleeReference.boundSymbol?.fir
if (referencedFir !is FirValueParameter) {
return thisRef
}
// TODO(KT-72994) remove everything below when context receivers are removed
val contextParameterNumber = (firClass as FirRegularClass).contextParameters.indexOf(referencedFir)
val constructorForCurrentlyGeneratedDelegatedConstructor =
conversionScope.getConstructorForCurrentlyGeneratedDelegatedConstructor(irClass.symbol)
if (constructorForCurrentlyGeneratedDelegatedConstructor != null) {
val constructorParameter =
constructorForCurrentlyGeneratedDelegatedConstructor.valueParameters[contextParameterNumber]
IrGetValueImpl(startOffset, endOffset, constructorParameter.type, constructorParameter.symbol)
} else {
val contextReceivers =
c.classifierStorage.getFieldsWithContextReceiversForClass(irClass, firClass)
require(contextReceivers.size > contextParameterNumber) {
"Not defined context receiver #$contextParameterNumber for $irClass. " +
"Context receivers found: $contextReceivers"
}
IrGetFieldImpl(
startOffset, endOffset, contextReceivers[contextParameterNumber].symbol,
thisReceiverExpression.resolvedType.toIrType(c),
thisRef,
)
}
}
}
private fun generateThisReceiverAccessForScript(
thisReceiverExpression: FirThisReceiverExpression,
firScriptSymbol: FirScriptSymbol
): IrElement {
val calleeReference = thisReceiverExpression.calleeReference
val firScript = firScriptSymbol.fir
val irScript = declarationStorage.getCachedIrScript(firScript) ?: error("IrScript for ${firScript.name} not found")
val contextParameterNumber = firScriptSymbol.fir.receivers.indexOf(calleeReference.boundSymbol?.fir)
val receiverParameter =
irScript.implicitReceiversParameters.find { it.indexInOldValueParameters == contextParameterNumber } ?: irScript.thisReceiver
if (receiverParameter != null) {
return thisReceiverExpression.convertWithOffsets { startOffset, endOffset ->
IrGetValueImpl(startOffset, endOffset, receiverParameter.type, receiverParameter.symbol)
}
} else {
error("No script receiver found") // TODO: check if any valid situations possible here
}
}
private fun generateThisReceiverAccessForCallable(
thisReceiverExpression: FirThisReceiverExpression,
firCallableSymbol: FirCallableSymbol<*>
): IrElement? {
val calleeReference = thisReceiverExpression.calleeReference
callGenerator.injectGetValueCall(thisReceiverExpression, calleeReference)?.let { return it }
val irFunction = when (firCallableSymbol) {
is FirFunctionSymbol -> {
val functionSymbol = declarationStorage.getIrFunctionSymbol(firCallableSymbol)
conversionScope.findDeclarationInParentsStack(functionSymbol)
}
is FirPropertySymbol -> {
val property = declarationStorage.getIrPropertySymbol(firCallableSymbol) as? IrPropertySymbol
property?.let { conversionScope.parentAccessorOfPropertyFromStack(it) }
}
else -> null
} ?: return null
val contextParameterNumber = firCallableSymbol.fir.contextParameters.indexOf(calleeReference.boundSymbol?.fir)
val receiver = if (contextParameterNumber != -1) {
irFunction.valueParameters[contextParameterNumber]
} else {
irFunction.extensionReceiverParameter
} ?: return null
return thisReceiverExpression.convertWithOffsets { startOffset, endOffset ->
IrGetValueImpl(startOffset, endOffset, receiver.type, receiver.symbol)
}
}
override fun visitInaccessibleReceiverExpression(
inaccessibleReceiverExpression: FirInaccessibleReceiverExpression,
data: Any?,
): IrElement {
return inaccessibleReceiverExpression.convertWithOffsets { startOffset, endOffset ->
IrErrorExpressionImpl(
startOffset, endOffset,
inaccessibleReceiverExpression.resolvedType.toIrType(c),
"Receiver is inaccessible"
)
}
}
override fun visitSmartCastExpression(smartCastExpression: FirSmartCastExpression, data: Any?): IrElement {
// Generate the expression with the original type and then cast it to the smart cast type.
val value = convertToIrExpression(smartCastExpression.originalExpression)
return implicitCastInserter.visitSmartCastExpression(smartCastExpression, value)
}
override fun visitCallableReferenceAccess(callableReferenceAccess: FirCallableReferenceAccess, data: Any?): IrElement {
return whileAnalysing(session, callableReferenceAccess) {
convertCallableReferenceAccess(callableReferenceAccess, false)
}
}
private fun convertCallableReferenceAccess(callableReferenceAccess: FirCallableReferenceAccess, isDelegate: Boolean): IrElement {
val explicitReceiverExpression = convertToIrReceiverExpression(
callableReferenceAccess.explicitReceiver, callableReferenceAccess
)
return callGenerator.convertToIrCallableReference(
callableReferenceAccess,
explicitReceiverExpression,
isDelegate = isDelegate
)
}
override fun visitVariableAssignment(
variableAssignment: FirVariableAssignment,
data: Any?
): IrExpression = whileAnalysing(session, variableAssignment) {
val explicitReceiverExpression = variableAssignment.explicitReceiver?.let { receiverExpression ->
convertToIrReceiverExpression(
receiverExpression, variableAssignment.unwrapLValue()!!
)
}
return callGenerator.convertToIrSetCall(variableAssignment, explicitReceiverExpression)
}
override fun visitDesugaredAssignmentValueReferenceExpression(
desugaredAssignmentValueReferenceExpression: FirDesugaredAssignmentValueReferenceExpression,
data: Any?
): IrElement {
return desugaredAssignmentValueReferenceExpression.expressionRef.value.accept(this, null)
}
override fun visitLiteralExpression(literalExpression: FirLiteralExpression, data: Any?): IrElement {
return literalExpression.toIrConst(literalExpression.resolvedType.toIrType(c))
}
// ==================================================================================
private fun FirStatement.toIrStatement(): IrStatement? {
if (this is FirTypeAlias) return null
if (this is FirUnitExpression) return runUnless(source?.kind is KtFakeSourceElementKind.ImplicitUnit.IndexedAssignmentCoercion) {
convertToIrExpression(this)
}
if (this is FirContractCallBlock) return null
if (this is FirBlock) return convertToIrExpression(this)
if (this is FirProperty && name == SpecialNames.UNDERSCORE_FOR_UNUSED_VAR) return null
return accept(this@Fir2IrVisitor, null) as IrStatement
}
internal fun convertToIrExpression(
expression: FirExpression,
isDelegate: Boolean = false,
// This argument is used for a corner case with deserialized empty array literals
// These array literals normally have a type of Array,
// so FIR2IR should instead use a type of corresponding property
// See also KT-62598
expectedType: ConeKotlinType? = null,
): IrExpression {
return when (expression) {
is FirBlock -> {
val origin = when (expression.source?.kind) {
is KtFakeSourceElementKind.DesugaredForLoop -> IrStatementOrigin.FOR_LOOP
is KtFakeSourceElementKind.DesugaredAugmentedAssign ->
augmentedAssignSourceKindToIrStatementOrigin[expression.source?.kind]
is KtFakeSourceElementKind.DesugaredIncrementOrDecrement -> incOrDecSourceKindToIrStatementOrigin[expression.source?.kind]
else -> null
}
expression.convertToIrExpressionOrBlock(origin)
}
is FirUnitExpression -> expression.convertWithOffsets { _, endOffset ->
IrGetObjectValueImpl(
endOffset, endOffset, builtins.unitType, this.builtins.unitClass
)
}
else -> {
when (val unwrappedExpression = expression.unwrapArgument()) {
is FirCallableReferenceAccess -> convertCallableReferenceAccess(unwrappedExpression, isDelegate)
is FirArrayLiteral -> convertToArrayLiteral(unwrappedExpression, expectedType)
else -> expression.accept(this, null) as IrExpression
}
}
}.let {
expression.accept(implicitCastInserter, it) as IrExpression
}
}
internal fun convertToIrReceiverExpression(
receiver: FirExpression?,
selector: FirQualifiedAccessExpression,
): IrExpression? {
val selectorCalleeReference = selector.calleeReference
val irReceiver = when (receiver) {
null -> return null
is FirResolvedQualifier -> callGenerator.convertToGetObject(receiver, selector as? FirCallableReferenceAccess)
is FirFunctionCall,
is FirThisReceiverExpression,
is FirCallableReferenceAccess,
is FirSmartCastExpression -> convertToIrExpression(receiver)
is FirQualifiedAccessExpression -> when (receiver.explicitReceiver) {
null -> {
val variableAsFunctionMode = selectorCalleeReference is FirResolvedNamedReference &&
selectorCalleeReference.name != OperatorNameConventions.INVOKE &&
(selectorCalleeReference.resolvedSymbol as? FirCallableSymbol)?.callableId?.callableName == OperatorNameConventions.INVOKE
callGenerator.convertToIrCall(
receiver, receiver.resolvedType, explicitReceiverExpression = null,
variableAsFunctionMode = variableAsFunctionMode
)
}
else -> convertToIrExpression(receiver)
}
else -> convertToIrExpression(receiver)
} ?: return null
if (receiver is FirQualifiedAccessExpression && receiver.calleeReference is FirSuperReference) return irReceiver
return implicitCastInserter.implicitCastFromReceivers(
irReceiver, receiver, selector,
conversionScope.defaultConversionTypeOrigin()
)
}
private fun List.mapToIrStatements(recognizePostfixIncDec: Boolean = true): List {
var index = 0
val result = ArrayList(size)
while (index < size) {
var irStatement: IrStatement? = null
if (recognizePostfixIncDec) {
val statementsOrigin = getStatementsOrigin(index)
if (statementsOrigin != null) {
val subStatements = this.subList(index, index + 3).mapNotNull { it.toIrStatement() }
irStatement = IrBlockImpl(
subStatements[0].startOffset,
subStatements[2].endOffset,
(subStatements[0] as IrVariable).type,
statementsOrigin,
subStatements
)
index += 3
}
}
if (irStatement == null) {
irStatement = this[index].toIrStatement()
index++
}
result.add(irStatement)
}
return result
}
private fun List.getStatementsOrigin(index: Int): IrStatementOrigin? {
val incrementStatement = getOrNull(index + 1)
if (incrementStatement !is FirVariableAssignment) return null
return incrementStatement.getIrPrefixPostfixOriginIfAny()
}
internal fun convertToIrBlockBody(block: FirBlock): IrBlockBody {
return block.convertWithOffsets { startOffset, endOffset ->
val irStatements = block.statements.mapToIrStatements()
IrFactoryImpl.createBlockBody(
startOffset, endOffset,
if (irStatements.isNotEmpty()) {
irStatements.filterNotNull().takeIf { it.isNotEmpty() }
?: listOf(IrBlockImpl(startOffset, endOffset, builtins.unitType, null, emptyList()))
} else {
emptyList()
}
).also {
with(implicitCastInserter) {
it.insertImplicitCasts()
}
}
}
}
private val IrStatementOrigin.isLoop: Boolean
get() {
return this == IrStatementOrigin.DO_WHILE_LOOP || this == IrStatementOrigin.WHILE_LOOP || this == IrStatementOrigin.FOR_LOOP
}
private fun extractOperationFromDynamicSetCall(functionCall: FirFunctionCall) =
functionCall.dynamicVarargArguments?.lastOrNull() as? FirFunctionCall
private fun FirStatement.unwrapDesugaredAssignmentValueReference(): FirStatement =
(this as? FirDesugaredAssignmentValueReferenceExpression)?.expressionRef?.value ?: this
/**
* This function tries to "sugar back" `FirBlock`s generated in
* [org.jetbrains.kotlin.fir.builder.AbstractRawFirBuilder.generateIncrementOrDecrementBlockForArrayAccess] and
* [org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformIncrementDecrementExpression]
*/
private fun FirBlock.tryConvertDynamicIncrementOrDecrementToIr(): IrExpression? {
// Key observations:
// 1. For postfix operations `` is always present and is returned in referenced as the last statement
// 2. The second to last statement is always either a `set()` call or an assignment
val unary = statements.findIsInstanceAnd { it.name == SpecialNames.UNARY }
// The thing `++` or `--` is called for.
// This expression may reference things like `` or `` if it's an array access,
// but it's definitely going to be something `++` or `--` could assign a value to
val operationReceiver = (unary?.initializer ?: statements.lastOrNull())
?.unwrapDesugaredAssignmentValueReference() as? FirQualifiedAccessExpression
?: return null
if (operationReceiver.resolvedType !is ConeDynamicType) {
return null
}
// A node representing either `++` or `--`
val operationCall = when (val it = statements[statements.lastIndex - 1]) {
is FirVariableAssignment -> it.rValue as? FirFunctionCall ?: return null
is FirFunctionCall -> extractOperationFromDynamicSetCall(it) ?: return null
else -> return null
}
// `operationReceiver` can look like `s`, `r.s` or `r[s]`.
// To generate a proper assignment, the block may want to save `r` to a separate variable
val operationReceiverReceiver = statements
.findIsInstanceAnd { it.name == SpecialNames.RECEIVER || it.name == SpecialNames.ARRAY }?.initializer
?: (operationReceiver as? FirQualifiedAccessExpression)?.explicitReceiver
// If `operationReceiver` is an array access, let's ignore its `` arguments and
// later manually convert them and put into the ir expression
val isArray = statements.find { it is FirProperty && it.name == SpecialNames.ARRAY } != null
val convertedOperationReceiver = callGenerator.convertToIrCall(
operationReceiver, operationReceiver.resolvedType,
convertToIrReceiverExpression(operationReceiverReceiver, operationReceiver),
noArguments = isArray,
).applyIf(isArray) {
require(this is IrDynamicOperatorExpression)
val arrayAccess = operationReceiver as? FirFunctionCall ?: return null
val originalVararg = arrayAccess.resolvedArgumentMapping?.keys?.filterIsInstance()?.firstOrNull()
originalVararg?.arguments?.forEach {
val indexNVariable = (it as? FirPropertyAccessExpression)?.calleeReference?.toResolvedPropertySymbol()?.fir
val initializer = indexNVariable?.initializer ?: return@forEach
arguments.add(convertToIrExpression(initializer))
}
this
}
return callGenerator.convertToIrCall(
operationCall, operationCall.resolvedType,
convertedOperationReceiver,
)
}
private fun FirBlock.convertToIrExpressionOrBlock(origin: IrStatementOrigin? = null): IrExpression {
if (this.source?.kind is KtFakeSourceElementKind.DesugaredIncrementOrDecrement) {
tryConvertDynamicIncrementOrDecrementToIr()?.let {
return it
}
}
if (this is FirSingleExpressionBlock) {
when (val stmt = statement) {
is FirExpression -> return convertToIrExpression(stmt)
!is FirDeclaration -> return stmt.accept(this@Fir2IrVisitor, null) as IrExpression
}
}
if (source?.kind is KtRealSourceElementKind) {
val lastStatement = statements.lastOrNull() as? FirExpression
val lastStatementHasNothingType = lastStatement?.resolvedType?.fullyExpandedType(session)?.isNothing == true
return statements.convertToIrBlock(source, origin, forceUnitType = origin?.isLoop == true || lastStatementHasNothingType)
}
return statements.convertToIrExpressionOrBlock(source, origin)
}
private fun FirBlock.convertToIrBlock(forceUnitType: Boolean): IrExpression {
return statements.convertToIrBlock(source, null, forceUnitType)
}
private fun List.convertToIrExpressionOrBlock(
source: KtSourceElement?,
origin: IrStatementOrigin?
): IrExpression {
if (size == 1) {
val firStatement = single()
if (firStatement is FirExpression &&
(firStatement !is FirBlock || firStatement.source?.kind != KtFakeSourceElementKind.DesugaredForLoop)
) {
return convertToIrExpression(firStatement)
}
}
return convertToIrBlock(source, origin, forceUnitType = origin?.isLoop == true)
}
private fun List.convertToIrBlock(
source: KtSourceElement?,
origin: IrStatementOrigin?,
forceUnitType: Boolean,
): IrExpression {
val type = if (forceUnitType)
builtins.unitType
else
(lastOrNull() as? FirExpression)?.resolvedType?.toIrType(c) ?: builtins.unitType
return source.convertWithOffsets { startOffset, endOffset ->
if (origin == IrStatementOrigin.DO_WHILE_LOOP) {
IrCompositeImpl(
startOffset, endOffset, type, null,
mapToIrStatements(recognizePostfixIncDec = false).filterNotNull()
)
} else {
val irStatements = mapToIrStatements()
val singleStatement = irStatements.singleOrNull()
if (singleStatement is IrBlock && (!forceUnitType || singleStatement.type.isUnit()) &&
(singleStatement.origin == IrStatementOrigin.POSTFIX_INCR || singleStatement.origin == IrStatementOrigin.POSTFIX_DECR)
) {
singleStatement
} else {
val blockOrigin = if (forceUnitType && origin != IrStatementOrigin.FOR_LOOP) null else origin
IrBlockImpl(startOffset, endOffset, type, blockOrigin, irStatements.filterNotNull())
}
}
}
}
override fun visitErrorExpression(errorExpression: FirErrorExpression, data: Any?): IrElement {
return errorExpression.convertWithOffsets { startOffset, endOffset ->
IrErrorExpressionImpl(
startOffset, endOffset,
errorExpression.resolvedType.toIrType(c),
errorExpression.diagnostic.reason
)
}
}
override fun visitEnumEntryDeserializedAccessExpression(
enumEntryDeserializedAccessExpression: FirEnumEntryDeserializedAccessExpression,
data: Any?
): IrElement {
return visitPropertyAccessExpression(enumEntryDeserializedAccessExpression.toQualifiedPropertyAccessExpression(session), data)
}
override fun visitElvisExpression(elvisExpression: FirElvisExpression, data: Any?): IrElement {
return elvisExpression.convertWithOffsets { startOffset, endOffset ->
val irLhsVariable = conversionScope.scope().createTemporaryVariable(
irExpression = elvisExpression.lhs.accept(this, null) as IrExpression,
nameHint = "elvis_lhs",
startOffset = startOffset,
endOffset = endOffset
)
fun irGetLhsValue(): IrGetValue =
IrGetValueImpl(startOffset, endOffset, irLhsVariable.type, irLhsVariable.symbol)
val originalType = elvisExpression.lhs.resolvedType
val notNullType = originalType.withNullability(nullable = false, session.typeContext)
val irBranches = listOf(
IrBranchImpl(
startOffset, endOffset,
IrCallImplWithShape(
startOffset = startOffset,
endOffset = endOffset,
type = builtins.booleanType,
symbol = builtins.eqeqSymbol,
typeArgumentsCount = 0,
valueArgumentsCount = 2,
contextParameterCount = 0,
hasDispatchReceiver = false,
hasExtensionReceiver = false,
origin = IrStatementOrigin.EQEQ,
).apply {
putValueArgument(0, irGetLhsValue())
putValueArgument(1, IrConstImpl.constNull(startOffset, endOffset, builtins.nothingNType))
},
convertToIrExpression(elvisExpression.rhs)
.insertImplicitCast(elvisExpression, elvisExpression.rhs.resolvedType, elvisExpression.resolvedType)
),
IrElseBranchImpl(
IrConstImpl.boolean(startOffset, endOffset, builtins.booleanType, true),
if (notNullType == originalType) {
irGetLhsValue()
} else {
Fir2IrImplicitCastInserter.implicitCastOrExpression(
irGetLhsValue(),
originalType.toFirResolvedTypeRef().resolvedTypeFromPrototype(notNullType).toIrType(c)
)
}
)
)
generateWhen(
startOffset, endOffset, IrStatementOrigin.ELVIS,
irLhsVariable, irBranches,
elvisExpression.resolvedType.toIrType(c)
)
}
}
override fun visitWhenExpression(whenExpression: FirWhenExpression, data: Any?): IrElement {
val subjectVariable = generateWhenSubjectVariable(whenExpression)
val origin = when (whenExpression.source?.elementType) {
KtNodeTypes.WHEN -> IrStatementOrigin.WHEN
KtNodeTypes.IF -> IrStatementOrigin.IF
KtNodeTypes.BINARY_EXPRESSION -> when (whenExpression.source?.operationToken) {
KtTokens.OROR -> IrStatementOrigin.OROR
KtTokens.ANDAND -> IrStatementOrigin.ANDAND
else -> null
}
KtNodeTypes.POSTFIX_EXPRESSION -> IrStatementOrigin.EXCLEXCL
else -> null
}
return conversionScope.withWhenSubject(subjectVariable) {
whenExpression.convertWithOffsets { startOffset, endOffset ->
if (whenExpression.branches.isEmpty()) {
return@convertWithOffsets IrBlockImpl(startOffset, endOffset, builtins.unitType, origin)
}
val isProperlyExhaustive = whenExpression.isDeeplyProperlyExhaustive()
val whenExpressionType =
if (isProperlyExhaustive) whenExpression.resolvedType else session.builtinTypes.unitType.coneType
val irBranches = whenExpression.convertWhenBranchesTo(
mutableListOf(),
whenExpressionType,
flattenElse = origin == IrStatementOrigin.IF,
)
if (isProperlyExhaustive && whenExpression.branches.none { it.condition is FirElseIfTrueCondition }) {
val irResult = IrCallImplWithShape(
startOffset, endOffset, builtins.nothingType,
builtins.noWhenBranchMatchedExceptionSymbol,
typeArgumentsCount = 0,
valueArgumentsCount = 0,
contextParameterCount = 0,
hasDispatchReceiver = false,
hasExtensionReceiver = false,
)
irBranches += IrElseBranchImpl(
IrConstImpl.boolean(startOffset, endOffset, builtins.booleanType, true), irResult
)
}
generateWhen(startOffset, endOffset, origin, subjectVariable, irBranches, whenExpressionType.toIrType(c))
}
}.also {
whenExpression.accept(implicitCastInserter, it)
}
}
/**
* TODO this shouldn't be required anymore once KT-65997 is fixed.
*/
private fun FirWhenExpression.isDeeplyProperlyExhaustive(): Boolean {
if (!isProperlyExhaustive) {
return false
}
val nestedElseIfExpression = branches.lastOrNull()?.nestedElseIfOrNull() ?: return true
return nestedElseIfExpression.isDeeplyProperlyExhaustive()
}
/**
* Converts the branches to [IrBranch]es.
*
* If [flattenElse] is `true` and the else branch contains another [FirWhenExpression] that's built from an `if`,
* its branches will be added directly to the [result] list instead.
*
* TODO this shouldn't be required anymore once KT-65997 is fixed.
*/
private fun FirWhenExpression.convertWhenBranchesTo(
result: MutableList,
whenExpressionType: ConeKotlinType,
flattenElse: Boolean,
): MutableList {
for (branch in branches) {
if (flattenElse) {
val elseIfExpression = branch.nestedElseIfOrNull()
if (elseIfExpression != null) {
elseIfExpression.convertWhenBranchesTo(result, whenExpressionType, flattenElse = true)
break
}
}
result.add(branch.toIrWhenBranch(whenExpressionType))
}
return result
}
private fun FirWhenBranch.nestedElseIfOrNull(): FirWhenExpression? {
if (condition is FirElseIfTrueCondition) {
val elseWhenExpression = (result as? FirSingleExpressionBlock)?.statement as? FirWhenExpression
if (elseWhenExpression != null && elseWhenExpression.source?.elementType == KtNodeTypes.IF) {
return elseWhenExpression
}
}
return null
}
private fun generateWhen(
startOffset: Int,
endOffset: Int,
origin: IrStatementOrigin?,
subjectVariable: IrVariable?,
branches: List,
resultType: IrType
): IrExpression {
// Note: ELVIS origin is set only on wrapping block
val irWhen = IrWhenImpl(startOffset, endOffset, resultType, origin.takeIf { it != IrStatementOrigin.ELVIS }, branches)
return if (subjectVariable == null) {
irWhen
} else {
IrBlockImpl(startOffset, endOffset, irWhen.type, origin, listOf(subjectVariable, irWhen))
}
}
private fun generateWhenSubjectVariable(whenExpression: FirWhenExpression): IrVariable? {
val subjectVariable = whenExpression.subjectVariable
val subjectExpression = whenExpression.subject
return when {
subjectVariable != null -> subjectVariable.accept(this, null) as IrVariable
subjectExpression != null ->
conversionScope.scope().createTemporaryVariable(convertToIrExpression(subjectExpression), "subject")
else -> null
}
}
private fun FirWhenBranch.toIrWhenBranch(whenExpressionType: ConeKotlinType): IrBranch {
return convertWithOffsets { startOffset, _ ->
val condition = condition
val irResult = convertToIrExpression(result).insertImplicitCast(result, result.resolvedType, whenExpressionType)
if (condition is FirElseIfTrueCondition) {
IrElseBranchImpl(IrConstImpl.boolean(irResult.startOffset, irResult.endOffset, builtins.booleanType, true), irResult)
} else {
IrBranchImpl(startOffset, irResult.endOffset, convertToIrExpression(condition), irResult)
}
}
}
override fun visitWhenSubjectExpression(whenSubjectExpression: FirWhenSubjectExpression, data: Any?): IrElement {
val lastSubjectVariable = conversionScope.lastWhenSubject()
return whenSubjectExpression.convertWithOffsets { startOffset, endOffset ->
IrGetValueImpl(startOffset, endOffset, lastSubjectVariable.type, lastSubjectVariable.symbol)
}
}
private val loopMap = mutableMapOf()
override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: Any?): IrElement {
val irLoop = doWhileLoop.convertWithOffsets { startOffset, endOffset ->
IrDoWhileLoopImpl(
startOffset, endOffset, builtins.unitType,
IrStatementOrigin.DO_WHILE_LOOP
).apply {
loopMap[doWhileLoop] = this
label = doWhileLoop.label?.name
body = runUnless(doWhileLoop.block is FirEmptyExpressionBlock) {
Fir2IrImplicitCastInserter.coerceToUnitIfNeeded(
doWhileLoop.block.convertToIrExpressionOrBlock(origin),
builtins
)
}
condition = convertToIrExpression(doWhileLoop.condition)
loopMap.remove(doWhileLoop)
}
}.also {
doWhileLoop.accept(implicitCastInserter, it)
}
return IrBlockImpl(irLoop.startOffset, irLoop.endOffset, builtins.unitType).apply {
statements.add(irLoop)
}
}
override fun visitWhileLoop(whileLoop: FirWhileLoop, data: Any?): IrElement {
return whileLoop.convertWithOffsets { startOffset, endOffset ->
val isForLoop = whileLoop.source?.elementType == KtNodeTypes.FOR
val origin = if (isForLoop) IrStatementOrigin.FOR_LOOP_INNER_WHILE else IrStatementOrigin.WHILE_LOOP
val firLoopBody = whileLoop.block
IrWhileLoopImpl(startOffset, endOffset, builtins.unitType, origin).apply {
loopMap[whileLoop] = this
label = whileLoop.label?.name
condition = convertToIrExpression(whileLoop.condition)
body = runUnless(firLoopBody is FirEmptyExpressionBlock) {
if (isForLoop) {
/*
* for loops in IR must have their body in the exact following form
* because some of the lowerings (e.g. `ForLoopLowering`) expect it:
*
* for (x in list) { ...body...}
*
* IR (loop body):
* IrBlock:
* x = .next()
* ... possible destructured loop variables, in case iterator is a tuple: `for ((a,b,c) in list) { ...body...}` ...
* IrBlock:
* ...body...
*/
firLoopBody.convertWithOffsets { innerStartOffset, innerEndOffset ->
val loopBodyStatements = firLoopBody.statements
val firLoopVarStmt = loopBodyStatements.firstOrNull()
?: error("Unexpected shape of for loop body: missing body statements")
val (destructuredLoopVariables, realStatements) = loopBodyStatements.drop(1).partition {
it is FirProperty && it.initializer is FirComponentCall
}
val firExpression = realStatements.singleOrNull() as? FirExpression
?: error("Unexpected shape of for loop body: must be single real loop statement, but got ${realStatements.size}")
val irStatements = buildList {
addIfNotNull(firLoopVarStmt.toIrStatement())
destructuredLoopVariables.forEach { addIfNotNull(it.toIrStatement()) }
if (firExpression !is FirEmptyExpressionBlock) {
add(convertToIrExpression(firExpression))
}
}
IrBlockImpl(
innerStartOffset,
innerEndOffset,
builtins.unitType,
origin,
irStatements,
)
}
} else {
Fir2IrImplicitCastInserter.coerceToUnitIfNeeded(
firLoopBody.convertToIrExpressionOrBlock(origin),
builtins
)
}
}
loopMap.remove(whileLoop)
}
}.also {
whileLoop.accept(implicitCastInserter, it)
}
}
private fun FirJump.convertJumpWithOffsets(
f: (startOffset: Int, endOffset: Int, irLoop: IrLoop, label: String?) -> IrBreakContinue
): IrExpression {
return convertWithOffsets { startOffset, endOffset ->
val firLoop = target.labeledElement
val irLoop = loopMap[firLoop]
if (irLoop == null) {
IrErrorExpressionImpl(startOffset, endOffset, builtins.nothingType, "Unbound loop: ${render()}")
} else {
f(startOffset, endOffset, irLoop, irLoop.label.takeIf { target.labelName != null })
}
}
}
override fun visitBreakExpression(breakExpression: FirBreakExpression, data: Any?): IrElement {
return breakExpression.convertJumpWithOffsets { startOffset, endOffset, irLoop, label ->
IrBreakImpl(startOffset, endOffset, builtins.nothingType, irLoop).apply {
this.label = label
}
}
}
override fun visitContinueExpression(continueExpression: FirContinueExpression, data: Any?): IrElement {
return continueExpression.convertJumpWithOffsets { startOffset, endOffset, irLoop, label ->
IrContinueImpl(startOffset, endOffset, builtins.nothingType, irLoop).apply {
this.label = label
}
}
}
override fun visitThrowExpression(throwExpression: FirThrowExpression, data: Any?): IrElement {
return throwExpression.convertWithOffsets { startOffset, endOffset ->
val expression = convertToIrExpression(throwExpression.exception)
val expressionWithCast = expression.applyIf(!expression.type.isSubtypeOfClass(builtins.throwableClass)) {
Fir2IrImplicitCastInserter.implicitCastOrExpression(this, builtins.throwableType)
}
IrThrowImpl(startOffset, endOffset, builtins.nothingType, expressionWithCast)
}
}
override fun visitTryExpression(tryExpression: FirTryExpression, data: Any?): IrElement {
// Always generate a block for try, catch and finally blocks. When leaving the finally block in the debugger
// for both Java and Kotlin there is a step on the end brace. For that to happen we need the block with
// that line number for the finally block.
return tryExpression.convertWithOffsets { startOffset, endOffset ->
IrTryImpl(
startOffset, endOffset, tryExpression.resolvedType.toIrType(c),
tryExpression.tryBlock.convertToIrBlock(forceUnitType = false),
tryExpression.catches.map { it.accept(this, data) as IrCatch },
tryExpression.finallyBlock?.convertToIrBlock(forceUnitType = true)
)
}.also {
tryExpression.accept(implicitCastInserter, it)
}
}
override fun visitCatch(catch: FirCatch, data: Any?): IrElement {
return catch.convertWithOffsets { startOffset, endOffset ->
val catchParameter = declarationStorage.createAndCacheIrVariable(
catch.parameter, conversionScope.parentFromStack(), IrDeclarationOrigin.CATCH_PARAMETER
)
IrCatchImpl(startOffset, endOffset, catchParameter, catch.block.convertToIrBlock(forceUnitType = false))
}
}
override fun visitComparisonExpression(comparisonExpression: FirComparisonExpression, data: Any?): IrElement =
operatorGenerator.convertComparisonExpression(comparisonExpression)
override fun visitStringConcatenationCall(
stringConcatenationCall: FirStringConcatenationCall,
data: Any?
): IrElement = whileAnalysing(session, stringConcatenationCall) {
return stringConcatenationCall.convertWithOffsets { startOffset, endOffset ->
val arguments = mutableListOf()
val sb = StringBuilder()
var startArgumentOffset = -1
var endArgumentOffset = -1
for (firArgument in stringConcatenationCall.arguments) {
val argument = convertToIrExpression(firArgument)
if (argument is IrConst && argument.kind == IrConstKind.String) {
if (sb.isEmpty()) {
startArgumentOffset = argument.startOffset
}
sb.append(argument.value)
endArgumentOffset = argument.endOffset
} else {
if (sb.isNotEmpty()) {
arguments += IrConstImpl.string(startArgumentOffset, endArgumentOffset, builtins.stringType, sb.toString())
sb.clear()
}
arguments += argument
}
}
if (sb.isNotEmpty()) {
arguments += IrConstImpl.string(startArgumentOffset, endArgumentOffset, builtins.stringType, sb.toString())
}
IrStringConcatenationImpl(startOffset, endOffset, builtins.stringType, arguments)
}
}
override fun visitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall, data: Any?): IrElement {
return typeOperatorCall.convertWithOffsets { startOffset, endOffset ->
val irTypeOperand = typeOperatorCall.conversionTypeRef.toIrType(c)
val (irType, irTypeOperator) = when (typeOperatorCall.operation) {
FirOperation.IS -> builtins.booleanType to IrTypeOperator.INSTANCEOF
FirOperation.NOT_IS -> builtins.booleanType to IrTypeOperator.NOT_INSTANCEOF
FirOperation.AS -> irTypeOperand to IrTypeOperator.CAST
FirOperation.SAFE_AS -> irTypeOperand.makeNullable() to IrTypeOperator.SAFE_CAST
else -> TODO("Should not be here: ${typeOperatorCall.operation} in type operator call")
}
IrTypeOperatorCallImpl(
startOffset, endOffset, irType, irTypeOperator, irTypeOperand,
convertToIrExpression(typeOperatorCall.argument)
)
}
}
override fun visitEqualityOperatorCall(equalityOperatorCall: FirEqualityOperatorCall, data: Any?): IrElement {
return whileAnalysing(session, equalityOperatorCall) {
operatorGenerator.convertEqualityOperatorCall(equalityOperatorCall)
}
}
override fun visitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall, data: Any?): IrElement = whileAnalysing(
session, checkNotNullCall
) {
return checkNotNullCall.convertWithOffsets { startOffset, endOffset ->
IrCallImplWithShape(
startOffset, endOffset,
checkNotNullCall.resolvedType.toIrType(c),
builtins.checkNotNullSymbol,
typeArgumentsCount = 1,
valueArgumentsCount = 1,
contextParameterCount = 0,
hasDispatchReceiver = false,
hasExtensionReceiver = false,
origin = IrStatementOrigin.EXCLEXCL
).apply {
putTypeArgument(0, checkNotNullCall.argument.resolvedType.toIrType(c).makeNotNull())
putValueArgument(0, convertToIrExpression(checkNotNullCall.argument))
}
}
}
override fun visitGetClassCall(getClassCall: FirGetClassCall, data: Any?): IrElement = whileAnalysing(session, getClassCall) {
val argument = getClassCall.argument
val irType = getClassCall.resolvedType.toIrType(c)
val irClassType =
if (argument is FirClassReferenceExpression) {
argument.classTypeRef.toIrType(c)
} else {
argument.resolvedType.toIrType(c)
}
val irClassReferenceSymbol = when (argument) {
is FirResolvedReifiedParameterReference -> {
classifierStorage.getIrTypeParameterSymbol(argument.symbol, ConversionTypeOrigin.DEFAULT)
}
is FirResolvedQualifier -> {
when (val symbol = argument.symbol) {
is FirClassSymbol -> {
classifierStorage.getIrClassSymbol(symbol)
}
is FirTypeAliasSymbol -> {
symbol.fir.fullyExpandedConeType(session).toIrClassSymbol()
}
else ->
return getClassCall.convertWithOffsets { startOffset, endOffset ->
IrErrorCallExpressionImpl(
startOffset, endOffset, irType, "Resolved qualifier ${argument.render()} does not have correct symbol"
)
}
}
}
is FirClassReferenceExpression -> {
(argument.classTypeRef.coneType.lowerBoundIfFlexible() as? ConeClassLikeType)?.toIrClassSymbol()
// A null value means we have some unresolved code, possibly in a binary dependency that's missing a transitive dependency,
// see KT-60181.
// Returning null will lead to convertToIrExpression(argument) being called below which leads to a crash.
// Instead, we return an error symbol.
?: IrErrorClassImpl.symbol
}
else -> null
}
return getClassCall.convertWithOffsets { startOffset, endOffset ->
if (irClassReferenceSymbol != null) {
IrClassReferenceImpl(startOffset, endOffset, irType, irClassReferenceSymbol, irClassType)
} else {
IrGetClassImpl(startOffset, endOffset, irType, convertToIrExpression(argument))
}
}
}
private fun ConeClassLikeType?.toIrClassSymbol(): IrClassSymbol? {
return this?.lookupTag?.toClassSymbol(session)?.let {
classifierStorage.getIrClassSymbol(it)
}
}
private fun convertToArrayLiteral(
arrayLiteral: FirArrayLiteral,
// See comment to convertToIrExpression
expectedType: ConeKotlinType?,
): IrVararg {
return arrayLiteral.convertWithOffsets { startOffset, endOffset ->
val arrayType = (expectedType ?: arrayLiteral.resolvedType).toIrType(c)
val elementType = arrayType.getArrayElementType(builtins)
IrVarargImpl(
startOffset, endOffset,
type = arrayType,
varargElementType = elementType,
elements = arrayLiteral.arguments.map { it.convertToIrVarargElement() }
)
}
}
override fun visitIndexedAccessAugmentedAssignment(
indexedAccessAugmentedAssignment: FirIndexedAccessAugmentedAssignment,
data: Any?
): IrElement = whileAnalysing(session, indexedAccessAugmentedAssignment) {
return indexedAccessAugmentedAssignment.convertWithOffsets { startOffset, endOffset ->
IrErrorCallExpressionImpl(
startOffset, endOffset, builtins.unitType,
"FirIndexedAccessAugmentedAssignment (resolve isn't supported yet)"
)
}
}
override fun visitResolvedQualifier(resolvedQualifier: FirResolvedQualifier, data: Any?): IrElement {
return callGenerator.convertToGetObject(resolvedQualifier)
}
override fun visitErrorResolvedQualifier(errorResolvedQualifier: FirErrorResolvedQualifier, data: Any?): IrElement {
// Support for error suppression case
return visitResolvedQualifier(errorResolvedQualifier, data)
}
private fun LogicOperationKind.toIrDynamicOperator() = when (this) {
LogicOperationKind.AND -> IrDynamicOperator.ANDAND
LogicOperationKind.OR -> IrDynamicOperator.OROR
}
override fun visitBooleanOperatorExpression(booleanOperatorExpression: FirBooleanOperatorExpression, data: Any?): IrElement {
return booleanOperatorExpression.convertWithOffsets { startOffset, endOffset ->
val leftOperand = booleanOperatorExpression.leftOperand.accept(this, data) as IrExpression
val rightOperand = booleanOperatorExpression.rightOperand.accept(this, data) as IrExpression
if (leftOperand.type is IrDynamicType) {
IrDynamicOperatorExpressionImpl(
startOffset,
endOffset,
builtins.booleanType,
booleanOperatorExpression.kind.toIrDynamicOperator(),
).apply {
receiver = leftOperand
arguments.add(rightOperand)
}
} else when (booleanOperatorExpression.kind) {
LogicOperationKind.AND -> {
IrWhenImpl(startOffset, endOffset, builtins.booleanType, IrStatementOrigin.ANDAND).apply {
branches.add(IrBranchImpl(leftOperand, rightOperand))
branches.add(elseBranch(constFalse(rightOperand.startOffset, rightOperand.endOffset)))
}
}
LogicOperationKind.OR -> {
IrWhenImpl(startOffset, endOffset, builtins.booleanType, IrStatementOrigin.OROR).apply {
branches.add(IrBranchImpl(leftOperand, constTrue(leftOperand.startOffset, leftOperand.endOffset)))
branches.add(elseBranch(rightOperand))
}
}
}
}
}
internal fun isGetClassOfUnresolvedTypeInAnnotation(expression: FirExpression): Boolean =
// In kapt mode, skip `Unresolved::class` in annotation arguments, because it cannot be handled by IrInterpreter,
// and because this replicates K1 behavior (see `ConstantExpressionEvaluatorVisitor.visitClassLiteralExpression`).
configuration.skipBodies && annotationMode &&
expression is FirGetClassCall && expression.argument.resolvedType is ConeErrorType
}
val KtSourceElement.isChildOfForLoop: Boolean
get() =
if (this is KtPsiSourceElement) psi.parent is KtForExpression
else treeStructure.getParent(lighterASTNode)?.tokenType == KtNodeTypes.FOR
val KtSourceElement.operationToken: IElementType?
get() {
assert(elementType == KtNodeTypes.BINARY_EXPRESSION)
return if (this is KtPsiSourceElement) (psi as? KtBinaryExpression)?.operationToken
else treeStructure.findChildByType(lighterASTNode, KtNodeTypes.OPERATION_REFERENCE)?.tokenType
?: error("No operation reference for binary expression: $lighterASTNode")
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy