All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.fir.backend.Fir2IrVisitor.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show 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.*
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.builders.declarations.UNDEFINED_PARAMETER_INDEX
import org.jetbrains.kotlin.ir.builders.primitiveOp2
import org.jetbrains.kotlin.ir.declarations.*
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.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)
        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 = irFactory.createExpressionBody(
                    IrEnumConstructorCallImpl(
                        startOffset, endOffset, irType,
                        constructor.symbol,
                        typeArgumentsCount = constructor.typeParameters.size,
                        valueArgumentsCount = constructor.valueParameters.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 = irFactory.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 = irFactory.createExpressionBody(
                    IrEnumConstructorCallImpl(
                        startOffset, endOffset, irType, constructor.symbol,
                        valueArgumentsCount = constructor.valueParameters.size,
                        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 ->
                        irFactory.createValueParameter(
                            startOffset, endOffset, origin, name, receiver.typeRef.toIrType(c), isAssignable = false,
                            IrValueParameterSymbolImpl(),
                            if (isSelf) UNDEFINED_PARAMETER_INDEX else index,
                            varargElementType = null, isCrossinline = false, isNoinline = false, isHidden = false
                        ).also {
                            it.parent = irScript
                        }
                    }
                if (isSelf) {
                    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) {
            val irBlock = codeFragment.block.convertToIrBlock(forceUnitType = false)
            irFunction.body = irFactory.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 = 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.map { 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 (boundSymbol !is FirClassSymbol || calleeReference.contextReceiverNumber == -1) {
            callGenerator.injectGetValueCall(thisReceiverExpression, calleeReference)?.let { return it }
        }

        when (boundSymbol) {
            is FirClassSymbol -> generateThisReceiverAccessForClass(thisReceiverExpression, boundSymbol)
            is FirScriptSymbol -> generateThisReceiverAccessForScript(thisReceiverExpression, boundSymbol)
            is FirCallableSymbol -> generateThisReceiverAccessForCallable(thisReceiverExpression, boundSymbol)
            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)

            if (calleeReference.contextReceiverNumber == -1) {
                return thisRef
            }

            val constructorForCurrentlyGeneratedDelegatedConstructor =
                conversionScope.getConstructorForCurrentlyGeneratedDelegatedConstructor(irClass.symbol)

            if (constructorForCurrentlyGeneratedDelegatedConstructor != null) {
                val constructorParameter =
                    constructorForCurrentlyGeneratedDelegatedConstructor.valueParameters[calleeReference.contextReceiverNumber]
                IrGetValueImpl(startOffset, endOffset, constructorParameter.type, constructorParameter.symbol)
            } else {
                val contextReceivers =
                    c.classifierStorage.getFieldsWithContextReceiversForClass(irClass, firClass)
                require(contextReceivers.size > calleeReference.contextReceiverNumber) {
                    "Not defined context receiver #${calleeReference.contextReceiverNumber} for $irClass. " +
                            "Context receivers found: $contextReceivers"
                }

                IrGetFieldImpl(
                    startOffset, endOffset, contextReceivers[calleeReference.contextReceiverNumber].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 receiverParameter =
            irScript.implicitReceiversParameters.find { it.index == calleeReference.contextReceiverNumber } ?: 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
        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 receiver = if (calleeReference.contextReceiverNumber != -1) {
            irFunction.valueParameters[calleeReference.contextReceiverNumber]
        } 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()
            irFactory.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(ConeNullability.NOT_NULL, session.typeContext)
            val irBranches = listOf(
                IrBranchImpl(
                    startOffset, endOffset,
                    primitiveOp2(
                        startOffset, endOffset, builtins.eqeqSymbol,
                        builtins.booleanType, IrStatementOrigin.EQEQ,
                        irGetLhsValue(),
                        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.type
                val irBranches = whenExpression.convertWhenBranchesTo(
                    mutableListOf(),
                    whenExpressionType,
                    flattenElse = origin == IrStatementOrigin.IF,
                )
                if (isProperlyExhaustive && whenExpression.branches.none { it.condition is FirElseIfTrueCondition }) {
                    val irResult = IrCallImpl(
                        startOffset, endOffset, builtins.nothingType,
                        builtins.noWhenBranchMatchedExceptionSymbol,
                        typeArgumentsCount = 0,
                        valueArgumentsCount = 0
                    )
                    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 ->
            IrThrowImpl(startOffset, endOffset, builtins.nothingType, convertToIrExpression(throwExpression.exception))
        }
    }

    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 ->
            IrCallImpl(
                startOffset, endOffset,
                checkNotNullCall.resolvedType.toIrType(c),
                builtins.checkNotNullSymbol,
                typeArgumentsCount = 1,
                valueArgumentsCount = 1,
                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? =
        (this?.lookupTag?.toSymbol(session) as? FirClassSymbol<*>)?.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 visitBinaryLogicExpression(binaryLogicExpression: FirBinaryLogicExpression, data: Any?): IrElement {
        return binaryLogicExpression.convertWithOffsets { startOffset, endOffset ->
            val leftOperand = binaryLogicExpression.leftOperand.accept(this, data) as IrExpression
            val rightOperand = binaryLogicExpression.rightOperand.accept(this, data) as IrExpression
            if (leftOperand.type is IrDynamicType) {
                IrDynamicOperatorExpressionImpl(
                    startOffset,
                    endOffset,
                    builtins.booleanType,
                    binaryLogicExpression.kind.toIrDynamicOperator(),
                ).apply {
                    receiver = leftOperand
                    arguments.add(rightOperand)
                }
            } else when (binaryLogicExpression.kind) {
                LogicOperationKind.AND -> {
                    IrIfThenElseImpl(startOffset, endOffset, builtins.booleanType, IrStatementOrigin.ANDAND).apply {
                        branches.add(IrBranchImpl(leftOperand, rightOperand))
                        branches.add(elseBranch(constFalse(rightOperand.startOffset, rightOperand.endOffset)))
                    }
                }
                LogicOperationKind.OR -> {
                    IrIfThenElseImpl(startOffset, endOffset, builtins.booleanType, IrStatementOrigin.OROR).apply {
                        branches.add(IrBranchImpl(leftOperand, constTrue(leftOperand.startOffset, leftOperand.endOffset)))
                        branches.add(elseBranch(rightOperand))
                    }
                }
            }
        }
    }
}

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 - 2024 Weber Informatics LLC | Privacy Policy