Repository: JetBrains/kotlin
File: compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt (Kotlin)

Code before:
    private class LocalFunctionContext(
        override val declaration: IrSimpleFunction,
        val index: Int,
        val ownerForLoweredDeclaration: IrDeclarationContainer
    ) :
        LocalContextWithClosureAsParameters() {
        lateinit var closure: Closure

        override lateinit var transformedDeclaration: IrSimpleFunction
    }

    private class LocalClassConstructorContext(override val declaration: IrConstructor, val inInlineFunctionScope: Boolean) :
        LocalContextWithClosureAsParameters() {
        override lateinit var transformedDeclaration: IrConstructor
    }

    private inner class LocalClassContext(
        val declaration: IrClass,
        val inInlineFunctionScope: Boolean,
        val constructorContext: LocalContext?
    ) : LocalContext() {
        lateinit var closure: Closure

        // NOTE: This map is iterated over in `rewriteClassMembers` and we're relying on
        // the deterministic iteration order that `mutableMapOf` provides.
        val capturedValueToField: MutableMap<IrValueDeclaration, Lazy<IrField>> = mutableMapOf()

        override fun irGet(startOffset: Int, endOffset: Int, valueDeclaration: IrValueDeclaration): IrExpression? {
            // On the JVM backend, `AnonymousObjectTransformer` in the bytecode inliner uses field assignment
            // instructions to find captured parameters. Most of the time this is unimportant, as captured
            // parameters are effectively indistinguishable from normal ones; the exception is ones that can
            // take an inline lambda value, as the parameter needs to be replaced with the lambda's capture.
            // Thus we cannot erase the field - the inliner won't handle the inline lambda without it.
            // (Most of the time this is inconsequential - if the object really is instantiated with an inline
            // lambda, the field will be erased completely regardless of whether it's used.)
            if (!forceFieldsForInlineCaptures || !valueDeclaration.isInlineDeclaration()) {
                // We're in the initializer scope, which will be moved to a primary constructor later.
                // Thus we can directly use that constructor's context and read from a parameter instead of a field.
                constructorContext?.irGet(startOffset, endOffset, valueDeclaration)?.let { return it }
            }

            val field = capturedValueToField[valueDeclaration]?.value ?: return null

            val receiver = declaration.thisReceiver!!
            return IrGetFieldImpl(
                startOffset, endOffset, field.symbol, field.type,
                receiver = IrGetValueImpl(startOffset, endOffset, receiver.type, receiver.symbol)
            )
        }

        private fun IrValueDeclaration.isInlineDeclaration() =

Review comment (suggestion):
This is minor, but maybe make it a bit more explicit, by using a custom class instead of `Lazy`? It'll make it easier to inspect where exactly the related logic is (searching for usages of Lazy is costly). E.g. (not sure about the names)
```
    private class FieldToBeGenerated(generate: () -> IrField) {
        private var generate = lazy(LazyThreadSafetyMode.NONE, generate)

        fun get(): IrField = generate.value

        val isAccessed: Boolean get() = generate.isInitialized()
    }
```

Code after:

    private class LocalFunctionContext(
        override val declaration: IrSimpleFunction,
        val index: Int,
        val ownerForLoweredDeclaration: IrDeclarationContainer
    ) :
        LocalContextWithClosureAsParameters() {
        lateinit var closure: Closure

        override lateinit var transformedDeclaration: IrSimpleFunction
    }

    private class LocalClassConstructorContext(override val declaration: IrConstructor, val inInlineFunctionScope: Boolean) :
        LocalContextWithClosureAsParameters() {
        override lateinit var transformedDeclaration: IrConstructor
    }

    private class PotentiallyUnusedField {
        var symbolIfUsed: IrFieldSymbol? = null
            private set

        val symbol: IrFieldSymbol
            get() = symbolIfUsed ?: IrFieldSymbolImpl().also { symbolIfUsed = it }
    }

    private inner class LocalClassContext(
        val declaration: IrClass,
        val inInlineFunctionScope: Boolean,
        val constructorContext: LocalContext?
    ) : LocalContext() {
        lateinit var closure: Closure

        // NOTE: This map is iterated over in `rewriteClassMembers` and we're relying on
        // the deterministic iteration order that `mutableMapOf` provides.
        val capturedValueToField: MutableMap<IrValueDeclaration, PotentiallyUnusedField> = mutableMapOf()

        override fun irGet(startOffset: Int, endOffset: Int, valueDeclaration: IrValueDeclaration): IrExpression? {
            // On the JVM backend, `AnonymousObjectTransformer` in the bytecode inliner uses field assignment
            // instructions to find captured parameters. Most of the time this is unimportant, as captured
            // parameters are effectively indistinguishable from normal ones; the exception is ones that can
            // take an inline lambda value, as the parameter needs to be replaced with the lambda's capture.
            // Thus we cannot erase the field - the inliner won't handle the inline lambda without it.
            // (Most of the time this is inconsequential - if the object really is instantiated with an inline
            // lambda, the field will be erased completely regardless of whether it's used.)
            if (!forceFieldsForInlineCaptures || !valueDeclaration.isInlineDeclaration()) {
                // We're in the initializer scope, which will be moved to a primary constructor later.
                // Thus we can directly use that constructor's context and read from a parameter instead of a field.
                constructorContext?.irGet(startOffset, endOffset, valueDeclaration)?.let { return it }
            }

            val field = capturedValueToField[valueDeclaration] ?: return null