Skip to content

KotlinInterface2.kt

src/jvmMain/kotlin/afield/runtime/kotlin/KotlinInterface2.kt · 3737 lines · cited by DataFrames in a Box

package and imports — 131 lines, from line 1
package afield.runtime.kotlin

import afield.animation.keyframes.Handles
import afield.app.diagnostics.named
import afield.app.protocol.Completion
import afield.app.protocol.Message
import afield.app.server.app
import afield.app.server.sessions
import afield.data.table.div
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.ref
import afield.data.table.rem
import afield.data.table.times
import afield.document.graph.attic
import afield.document.graph.makeKI
import afield.document.workspace.Entry
import afield.runtime.completion.CompletionAaE2EProbe
import afield.runtime.execution.plus
import afield.runtime.lifecycle.Framer
import afield.runtime.lifecycle.forever
import afield.runtime.lifecycle.plugins
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.runtime.lifecycle.then
import afield.ui.notation.Mark
import afield.ui.notation.string
import afield.ui.output.history
import afield.ui.output.logs
import afield.ui.output.tree
import afield.ui.visualization.typed

import afield.app.diagnostics.CodeUseTelemetryService
import afield.app.diagnostics.LatencySpans
import afield.app.diagnostics.ProfileExempt
import afield.app.notifications.notify
import afield.app.protocol.put
import afield.app.settings.PersonalLayer
import afield.app.settings.needsRerun
import afield.audio.synth.*
import afield.data.table.get
import afield.document.graph.Box
import afield.document.graph.HasProperties
import afield.document.graph.box
import afield.document.graph.doResync
import afield.document.graph.intro
import afield.document.graph.introImports
import afield.document.graph.metaroot
import afield.document.graph.preamble
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.completion.AaCompletionClient
import afield.runtime.lifecycle.Runloop
import afield.runtime.lifecycle.map
import afield.ui.output.templates.filter
import afield.ui.output.update
import afield.runtime.completion.KotlinSourceIndex
import afield.app.diagnostics.logging.formatMillis
import afield.app.diagnostics.logging.lblock
import afield.app.diagnostics.logging.lprintln
import afield.runtime.macros.ContainingFunction
import afield.runtime.macros.Insert
import afield.runtime.macros.MacroRegistry
import afield.runtime.macros.PatternRewrite
import afield.runtime.macros.gatherPreambleUpwards
import afield.runtime.macros.searchUpwards
import afield.runtime.macros.resolvePatternRewriteAi
import afield.graphics.shader.applyInsertToPreamble
import afield.graphics.shader.normalizePreambleText
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.com.intellij.lang.Language
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.K2ReplCompiler
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.K2ReplEvaluator
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptDiagnosticsMessageCollector
import org.jetbrains.kotlin.scripting.compiler.plugin.services.FirReplHistoryProviderImpl
import org.jetbrains.kotlin.scripting.compiler.plugin.services.firReplHistoryProvider
import org.jetbrains.kotlin.scripting.compiler.plugin.services.isReplSnippetSource
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.lang.reflect.Proxy
import java.net.URL
import java.net.URLClassLoader
import java.util.Collections
import java.util.IdentityHashMap
import java.util.WeakHashMap
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.reflect.*
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.kotlinFunction
import kotlin.reflect.jvm.kotlinProperty
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.*
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
import kotlin.script.experimental.host.with
import kotlin.script.experimental.impl._languageVersion
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvm.util.determineSep
import kotlin.script.experimental.jvm.util.toSourceCodePosition
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.system.measureNanoTime

The K2 implementation of KotlinScriptEngine: the engine that compiles and runs a box's Kotlin.

Each box gets its own KotlinInterface2, with its own REPL history and snippet classloader. It compiles snippets with the K2 compiler, evaluates them, tracks the variables and functions they declare, and answers completion requests in the editor. Redeclaring a plain var resets older mutable bindings of the same name and Kotlin type after evaluation; captured vals stay fixed.

class KotlinInterface2

class KotlinInterface2(
    val propertyClasses: Map<String, KClass<*>> = emptyMap(),
    val properties: Map<String, Any?> = emptyMap(),
    override val implicitReceivers: List<KotlinType> = emptyList(),
    override var classLoader: ClassLoader? = null,
    val filePrefix: String = "EVAL_"
) : KotlinScriptEngine {

override val compilationDispatcher get() = Dispatchers.Default

override var sourceProperty: String = "code"

internal inner class SourceCodeImpl(number: Int, override val text: String) : SourceCode

override val name: String? = fieldSourceName(filePrefix, sourceProperty, number)

override val locationId: String? = "LOCATION_$number"

override fun equals(other: Any?): Boolean

        override fun equals(other: Any?): Boolean {
            // `this === other`, not `==`: `==` is a call to this same equals, and the self-call
            // recursed until the stack ran out. Latent until a compiler that compares SourceCode
            // instances came along (2.4.20 does), and then EVERY compile died in here.
            return this === other || (other as? SourceCodeImpl)?.let { text == it.text } == true
        }

override fun hashCode(): Int

        override fun hashCode(): Int {
            return text.hashCode()
        }
    }

var evaluator: K2ReplEvaluator

var compiler: K2ReplCompiler

override var compilerConfig: ScriptCompilationConfiguration

val contextUpdater: ContextUpdater

override val context: KotlinContext

override var associatedBox: Any? = null

Reference to the box this KotlinInterface is associated with (for @exported support)

Disposable for the K2 REPL compilation state

    private val rootDisposable: Disposable = Disposer.newDisposable("K2ReplCompilationState")

Message collector for the K2 REPL compiler

    private val messageCollector = ScriptDiagnosticsMessageCollector(null)

The K2 compilation state (holds FIR sessions, project environment, etc.)

    private val compilationState: org.jetbrains.kotlin.scripting.compiler.plugin.impl.K2ReplCompilationState

PSI project reused from the K2 compiler's environment (compilerContext is internal, so accessed via reflection)

    private val psiProject by lazy {
        val field = compilationState::class.java.getDeclaredField("compilerContext")
        field.isAccessible = true
        val ctx = field.get(compilationState)
        val envField = ctx::class.java.getDeclaredField("environment")
        envField.isAccessible = true
        val env = envField.get(ctx) as KotlinCoreEnvironment
        env.project
    }

K2 can compile different REPL states concurrently, but a single K2ReplCompilationState is not safe to enter from overlapping snippets.

    private val compileMutex = Mutex()

Readiness gate: open once the stdlib intro has evaluated. Every ordinary exec/evaluate awaits this first, so no snippet evaluates before the intro that defines the K2 REPL ReplState — the launch setup's inherited preamble, box code, the execute path, connect-triggered re-execs, etc. all funnel through here regardless of which thread or caller drives them.

It starts open: an engine created and driven directly (tests, ad-hoc snippets) has no async bootstrap and must not block. makeKI calls armBootstrapGate to make it pending before scheduling the async intro; bootstrap and releaseBootstrapGate open it again.

Awaiting is deadlock-free: the bootstrap itself uses the *Internal variants to bypass the gate, and eval-time re-entry into the engine (sourceBlock / function-rewrite / ai macro compile, or a re-entrant exec) happens after the gate is open, so await returns immediately.

    @Volatile
    private var bootstrapReady: CompletableDeferred<Unit> = CompletableDeferred<Unit>().apply { complete(Unit) }

    private val codeUseSnippetIds = Collections.synchronizedMap(WeakHashMap<Any, String>())

    private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())

init

body — 69 lines, from line 224
    init {
        assert(properties.size == propertyClasses.size)
        assert(properties.keys.containsAll(propertyClasses.keys))
        if (classLoader == null) classLoader = Thread.currentThread().contextClassLoader

        if (sharedClassLoader == null) {
            // Parent through [PersonalLayer] so src/personal is visible to box code even though it
            // isn't on the app -cp. Both sides come along for free: execution by ordinary parent
            // delegation, and compilation because dependenciesFromClassloader(wholeClasspath) below
            // walks this chain and picks up the layer's URLs. A no-op when there is no personal code.
            sharedClassLoader = URLClassLoader(arrayOf(), PersonalLayer.wrap(classLoader!!))
        }

        classLoader = sharedClassLoader
        MavenClasspathManager.attachResolvedTo(classLoader)

        val myHostConfiguration = defaultJvmScriptingHostConfiguration.with {
            jvm {
                baseClassLoader(classLoader!!)
            }
            repl {
                firReplHistoryProvider(FirReplHistoryProviderImpl())
                isReplSnippetSource { _, _ -> true }
            }
        }

        // EXPERIMENT: locate the Wither compiler-plugin jar on the classpath so we can hand it
        // to the REPL compiler via -Xplugin. Loading it registers Wither's FIR receiver-injection
        // + IR extensions into the CompilerConfiguration at createCompilationState time — the same
        // config that both the compile path and resolveTypesViaFir read registrars back out of.
        compilerConfig = ScriptCompilationConfiguration {
            defaultImports(DependsOn::class, Repository::class, exported::class, rootExported::class, metarootExported::class)
            hostConfiguration.update {
                myHostConfiguration
            }
            this@KotlinInterface2.implicitReceivers.forEach { implicitReceivers.append(it) }

            jvm {
                jvmTarget.put("24")
                _languageVersion.put(fieldKotlinLanguageVersion)
                // Every independent K2 compilation state needs the plugin argument, but locating
                // the process-wide jar (and announcing it) only needs to happen once per JVM.
                compilerOptions.put(fieldKotlinJvmCompilerOptions + compilerPluginArgs)

                dependenciesFromClassloader(
                    classLoader = classLoader!!, wholeClasspath = true, unpackJarCollections = true
                )
                providedProperties(*propertyClasses.map { it.key to it.value }.toTypedArray())
            }

            refineConfiguration {
                onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
            }
        }

        // Create K2 REPL compilation state
        compilationState = K2ReplCompiler.createCompilationState(
            messageCollector,
            rootDisposable,
            compilerConfig,
            myHostConfiguration
        )

        compiler = K2ReplCompiler(compilationState)
        evaluator = K2ReplEvaluator()

        context = KotlinContext()
        contextUpdater = ContextUpdater(context, evaluator, this)
    }

fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration>

    fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
        notify("configuring dependencies in script", "execution")
        val annotations =
            context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
                ?: return context.compilationConfiguration.asSuccess()
        return runBlocking {
            notify("running resolver for dependencies", "execution")
            resolver.resolveFromScriptSourceAnnotations(annotations)
        }.onSuccess {
            notify("finished running resolver", "execution")
            context.compilationConfiguration.with {
                try {
                    it.forEach {
                        val target = (classLoader!! as URLClassLoader)
                        lprintln("warning,kotlin", "sketchy url classloader front running ${it}")
                        val m = URLClassLoader::class.java.getDeclaredMethod("addURL", URL::class.java)
                        m.isAccessible = true
                        m.invoke(target, it.toURI().toURL())
                    }
                } catch (e: Throwable) {
                    lprintln("error,kotlin", "sketchy url classloader front running threw")
                    e.printStackTrace()
                }

                jvm {
                    dependenciesFromClassloader(
                        classLoader = classLoader!!, wholeClasspath = true, unpackJarCollections = true
                    )
                }
            }.asSuccess()
        }
    }

var id = 0

internal val rerunCache = RerunCompileCache()

Compile once, re-evaluate on rerun; see RerunCompileCache for what it refuses and why.

var compileCount

Snippets this engine has really compiled — each one grows the compiler's REPL history.

    @Volatile
    var compileCount = 0
        private set

var compileNanos

Total wall time of those compiles, in nanoseconds.

    @Volatile
    var compileNanos = 0L
        private set

val rerunCacheHits: Int get() = rerunCache.hits

Reruns served by rerunCache instead of a compile.

Field's own compiler warnings (ShadowedSnippetNameDiagnostics) by rerun-cache key, so a cache hit shows them again.

    private val fieldWarningsByKey = HashMap<String, List<ScriptDiagnostic>>()

Sends this compile's Field warnings to the box's editor, through the output its run wired up.

    private fun publishFieldWarnings(warnings: List<ScriptDiagnostic>) {
        val box = associatedBox as? Box ?: return
        warnings.forEach { d ->
            val l = d.location
            box.output.warningProxy(d.message, l?.start?.line ?: 0, l?.start?.col ?: -1, l?.end?.line ?: -1, l?.end?.col ?: -1)
        }
    }

    private fun fieldWarnings(r: ResultWithDiagnostics<*>): List<ScriptDiagnostic> =
        if (r !is ResultWithDiagnostics.Success) emptyList()
        else (r.reports + messageCollector.diagnostics)
            .filter { it.severity == ScriptDiagnostic.Severity.WARNING && it.message.contains(ShadowedSnippetNameDiagnostics.MESSAGE_MARK) }
            .distinctBy { Triple(it.message, it.location?.start?.line, it.location?.start?.col) }

compile, except that an exact rerun of a source this engine already compiled — same expanded text, source property and implicit receivers, nothing it binds to redeclared since — returns the snippet it already has. Only execInternal uses this: compile itself, and compileSnippet's prepared snippets, always compile.

source — 117 lines, from line 369
    private suspend fun compileOrReuse(s: String, implicits: List<Any>): ResultWithDiagnostics<LinkedSnippet<CompiledScript>> {
        if (!KotlinEngineConfig.rerunCompileCache || !rerunCache.cacheable(s)) return compile(s)
        val key = sourceProperty + "\u0000" + implicits.joinToString(",") { it.javaClass.name } + "\u0000" + s
        return compileMutex.withLock {
            usingCompilationState(onDisposed = { compileAfterDisposal(s) }) {
                var reuse = rerunCache.lookup(key)
                if (reuse != null && evaluator.lastEvaluatedSnippet?.get()?.compiledSnippet === reuse.snippet.get()) {
                    // Measured: re-evaluating the snippet evaluated last reuses its loaded class, and the body does not run.
                    rerunCache.refuse(key, "it is the snippet evaluated last")
                    reuse = null
                }
                if (reuse != null) {
                    val hitAt = System.nanoTime()
                    LatencySpans.record("kotlin.compile.cacheHit", hitAt, hitAt, box = (associatedBox as? Box)?.id, detail = sourceProperty)
                    lprintln(
                        "info,kotlin,perf",
                        "\t\t compile cache hit engine=KotlinInterface2 filePrefix=$filePrefix property=$sourceProperty hits=${rerunCache.hits} misses=${rerunCache.misses} compiles=$compileCount preview=${compileSnippetPreview(s)}"
                    )
                    ResultWithDiagnostics.Success(reuse.snippet).also { publishFieldWarnings(fieldWarningsByKey[key].orEmpty()) }
                } else {
                    lprintln(
                        "info,kotlin,perf",
                        "\t\t compile cache miss reason=${rerunCache.lastMissReason} filePrefix=$filePrefix property=$sourceProperty"
                    )
                    compileLocked(s, cacheKey = key)
                }
            }
        }
    }

    private fun compileSnippetKind(s: String): String {
        return when {
            s == intro -> "field-startup-imports"
            s.isBlank() -> "blank"
            s.trimStart().startsWith("import ") -> "imports-or-preamble"
            else -> "user-code"
        }
    }

    private fun compileSnippetPreview(s: String, maxLength: Int = 120): String {
        val compact = s.lineSequence()
            .map { it.trim() }
            .filter { it.isNotEmpty() }
            .take(3)
            .joinToString(" | ")
        return if (compact.length <= maxLength) compact else compact.take(maxLength) + "..."
    }

    private fun resolveBoxContext(implicits: List<Any> = emptyList()): Box? {
        val fromImplicits = implicits.firstOrNull { it is Box } as? Box
        if (fromImplicits != null) return fromImplicits
        val fromAssociated = associatedBox as? Box
        if (fromAssociated != null) return fromAssociated
        return Box.context.get()
    }

    private fun Throwable.causeChain(): List<Throwable> {
        val out = mutableListOf<Throwable>()
        val seen = Collections.newSetFromMap(IdentityHashMap<Throwable, Boolean>())
        var current: Throwable? = this
        while (current != null && seen.add(current)) {
            out += current
            current = current.cause
        }
        return out
    }

    private fun firstMessageLine(throwable: Throwable): String {
        return throwable.message
            ?.lineSequence()
            ?.firstOrNull { it.isNotBlank() }
            ?.trim()
            ?: throwable::class.qualifiedName
            ?: throwable.javaClass.name
    }

    private fun compilerExceptionDiagnostic(throwable: Throwable, source: String): ScriptDiagnostic {
        val chain = throwable.causeChain()
        val boxMemberInClass = SnippetClassReceiverCrash.matches(chain.map { it.message })
        val site = if (boxMemberInClass) SnippetClassReceiverCrash.locate(source) else null
        val root = chain.lastOrNull() ?: throwable
        val hasImplicitReceiverFailure = chain.any {
            it.message?.contains("SCRIPT_IMPLICIT_RECEIVER") == true ||
                    it.message?.contains("\$script_receiver_") == true
        }

        val message = buildString {
            append("Kotlin compiler exception: ")
            append(throwable::class.qualifiedName ?: throwable.javaClass.name)
            append(": ")
            append(firstMessageLine(throwable))

            if (root !== throwable) {
                append("\nCaused by: ")
                append(root::class.qualifiedName ?: root.javaClass.name)
                append(": ")
                append(firstMessageLine(root))
            }

            if (boxMemberInClass) append(SnippetClassReceiverCrash.explain(site))

            if (hasImplicitReceiverFailure) {
                append("\n\nK2 could not codegen a named function that captures the script's implicit Box receiver. ")
                append("Make that receiver explicit, for example `fun Box.show() { ... }`, ")
                append("or pass the Box explicitly.")
            }
        }

        return ScriptDiagnostic(
            ScriptDiagnostic.unspecifiedException,
            message,
            ScriptDiagnostic.Severity.ERROR,
            null,
            site?.let { SourceCode.Location(SourceCode.Position(it.line, it.column)) },
            throwable
        )
    }

suspend fun compile(s: String): ResultWithDiagnostics<LinkedSnippet<CompiledScript>>

    suspend fun compile(s: String): ResultWithDiagnostics<LinkedSnippet<CompiledScript>> {
        return compileMutex.withLock {
            usingCompilationState(onDisposed = { compileAfterDisposal(s) }) { compileLocked(s) }
        }
    }

Caller holds compileMutex and a usingCompilationState lease.

source — 73 lines, from line 494
    private suspend fun compileLocked(s: String, cacheKey: String? = null): ResultWithDiagnostics<LinkedSnippet<CompiledScript>> {
        compileCount++
        val code = SourceCodeImpl(id++, s)
        val snippetId = CodeUseTelemetryService.newSnippetKey()
        val snippetKind = compileSnippetKind(s)
        val telemetryContext = CodeUseTelemetryService.captureBoxContext(
            associatedBox as? Box,
            property = sourceProperty,
            mode = "compile:$snippetKind"
        )
        val snapshot = context.debugScripts.recordCompile(code.name ?: "snippet_${id - 1}", s)

        lprintln(
            "info,kotlin,perf",
            "\t\t compile request engine=KotlinInterface2 filePrefix=$filePrefix source=${code.name} kind=${
                snippetKind
            } length=${s.length} preview=${compileSnippetPreview(s)}"
        )
        lprintln("info,kotlin,perf", "\t\t compile code $s")

        // Clear diagnostics from previous snippet. The collector is shared by this K2 REPL state.
        messageCollector.clear()

        var r: ResultWithDiagnostics<LinkedSnippet<CompiledScript>>? = null
        val compileStart = System.nanoTime()
        val ns = measureNanoTime {
            try {
                if (KotlinEngineConfig.offloadCompilation) {
                    // Offload the expensive compiler work, but keep this REPL state single-entered.
                    r = withContext(compilationDispatcher) {
                        compiler.compile(code, compilerConfig)
                    }
                } else {
                    r = compiler.compile(code, compilerConfig)
                }
            } catch (t: Throwable) {
                context.debugScripts.discard(snapshot)
                lprintln("error,kotlin", "compiler exception while compiling snippet")
                lprintln("error,kotlin", t.stackTraceToString())
                r = ResultWithDiagnostics.Failure(compilerExceptionDiagnostic(t, s))
            }
        }
        compileNanos += ns
        LatencySpans.record("kotlin.compile", compileStart, compileStart + ns, box = (associatedBox as? Box)?.id, detail = "$snippetKind length=${s.length}")
        lprintln("info,kotlin,compile", "compile=${formatMillis(ns)}")
        if (r is ResultWithDiagnostics.Failure) {
            context.debugScripts.discard(snapshot)
        }
        val result = r!!
        val warnings = fieldWarnings(result)
        if (cacheKey != null) { if (warnings.isEmpty()) fieldWarningsByKey.remove(cacheKey) else fieldWarningsByKey[cacheKey] = warnings }
        publishFieldWarnings(warnings)
        if (result is ResultWithDiagnostics.Success) {
            codeUseSnippetIds[result.value] = snippetId
            // Every successful compile is registered, cached or not: its declarations may shadow a cached entry's.
            try {
                rerunCache.recordCompile(cacheKey, s, result.value)
            } catch (t: Throwable) {
                lprintln("warning,kotlin", "rerun compile cache could not index snippet: $t")
            }
        }
        CodeUseTelemetryService.recordCompileResult(
            snippetId = snippetId,
            source = s,
            sourceName = code.name ?: "",
            context = telemetryContext,
            success = result is ResultWithDiagnostics.Success,
            diagnostics = result.reports.map { "${it.severity}: ${it.message} ${it.location ?: ""}".trim() },
            elapsedMs = ns / 1_000_000.0
        )

        return result
    }

How a stall report names this engine's box (see afield.runtime.lifecycle.BoxInterrupt).

    private fun interruptLabel(): String =
        (associatedBox as? Box)?.let { "'${it.name}' (${it.id})" } ?: "box code"

suspend fun evaluate

body — 50 lines, from line 573
    suspend fun evaluate(
        e: LinkedSnippet<out CompiledScript>, updateContext: Boolean = false
    ): ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>> {
        return Box.withBoxContext(resolveBoxContext()) {
            var r: ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>>
            var runtimeClass = ""
            var returnType = ""
            var exception: Throwable? = null
            val evalStart = System.nanoTime()
            val ns = measureNanoTime {

                val evalConfig = ScriptEvaluationConfiguration {
                    jvm {
                        baseClassLoader(classLoader!!)
                    }
                }

                afield.runtime.lifecycle.BoxInterrupt.instrument(e.get(), interruptLabel())
                r = evaluator.eval(e, evalConfig)
                if (r is ResultWithDiagnostics.Success) {
                    val evalResult = r.value.get().result
                    val scriptInstance = evalResult.scriptInstance
                    returnType = runtimeReturnType(evalResult)
                    if (evalResult is ResultValue.Error) exception = evalResult.error
                    runtimeClass = scriptInstance?.javaClass?.name ?: evalResult.javaClass.name
                    context.debugScripts.attachLatestRuntimeClass(scriptInstance?.javaClass?.name)
                }
                if (updateContext) contextUpdater.update(
                    when (val evaluated = r) {
                        is ResultWithDiagnostics.Success -> evaluated.value.get().result
                        else -> null
                    }
                )
            }
            lprintln("info,kotlin,evaluate", "evaluate=${formatMillis(ns)}")
            LatencySpans.record("kotlin.eval", evalStart, evalStart + ns, box = (associatedBox as? Box)?.id)
            val success = r is ResultWithDiagnostics.Success && r.value.get().result !is ResultValue.Error
            CodeUseTelemetryService.recordEvaluationResult(
                snippetId = codeUseSnippetIds[e] ?: CodeUseTelemetryService.newSnippetKey(),
                context = CodeUseTelemetryService.captureBoxContext(associatedBox as? Box, property = sourceProperty, mode = "evaluate"),
                success = success,
                runtimeClass = runtimeClass,
                returnType = returnType,
                exception = exception,
                diagnostics = r.reports.map { "${it.severity}: ${it.message} ${it.location ?: ""}".trim() },
                elapsedMs = ns / 1_000_000.0
            )
            r
        }
    }

suspend fun evaluate

    suspend fun evaluate(
        e: LinkedSnippet<out CompiledScript>, updateContext: Boolean = true, implicits: List<Any>
    ): ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>> {
        // Never evaluate before the stdlib intro has (see [bootstrapReady]).
        bootstrapReady.await()
        return evaluateInternal(e, updateContext, implicits)
    }

Caller is responsible for the bootstrap gate. Used directly by bootstrap (to run the intro without waiting on itself) and by execInternal (which has already gated in exec).

source — 61 lines, from line 634
    private suspend fun evaluateInternal(
        e: LinkedSnippet<out CompiledScript>, updateContext: Boolean, implicits: List<Any>
    ): ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>> {
        return Box.withBoxContext(resolveBoxContext(implicits)) {
            var r: ResultWithDiagnostics<LinkedSnippet<KJvmEvaluatedSnippet>>
            var runtimeClass = ""
            var returnType = ""
            var exception: Throwable? = null
            val evalStart = System.nanoTime()
            val ns = measureNanoTime {

                val evalConfig = ScriptEvaluationConfiguration {
                    implicits.forEach { implicitReceivers.append(it) }
                    jvm {
                        baseClassLoader(classLoader!!)
                    }
                }

                afield.runtime.lifecycle.BoxInterrupt.instrument(e.get(), interruptLabel())
                r = evaluator.eval(e, evalConfig)
                if (r is ResultWithDiagnostics.Success) {
                    val evalResult = r.value.get().result
                    val scriptInstance = evalResult.scriptInstance
                    returnType = runtimeReturnType(evalResult)
                    if (evalResult is ResultValue.Error) exception = evalResult.error
                    runtimeClass = scriptInstance?.javaClass?.name ?: evalResult.javaClass.name
                    context.debugScripts.attachLatestRuntimeClass(scriptInstance?.javaClass?.name)
                }
                if (updateContext) contextUpdater.update(
                    when (val evaluated = r) {
                        is ResultWithDiagnostics.Success -> evaluated.value.get().result
                        else -> null
                    }
                )
            }
            lprintln("info,kotlin,evaluate", "evaluate=${formatMillis(ns)}")
            LatencySpans.record("kotlin.eval", evalStart, evalStart + ns, box = (associatedBox as? Box)?.id)
            val boxContext = (implicits.firstOrNull { it is Box } as? Box) ?: associatedBox as? Box
            val success = r is ResultWithDiagnostics.Success && r.value.get().result !is ResultValue.Error
            CodeUseTelemetryService.recordEvaluationResult(
                snippetId = codeUseSnippetIds[e] ?: CodeUseTelemetryService.newSnippetKey(),
                context = CodeUseTelemetryService.captureBoxContext(boxContext, property = sourceProperty, mode = "evaluate"),
                success = success,
                runtimeClass = runtimeClass,
                returnType = returnType,
                exception = exception,
                diagnostics = r.reports.map { "${it.severity}: ${it.message} ${it.location ?: ""}".trim() },
                elapsedMs = ns / 1_000_000.0
            )
            r
        }
    }

    private fun runtimeReturnType(value: ResultValue?): String =
        when (value) {
            is ResultValue.Value -> value.type.ifBlank { value.value?.javaClass?.name.orEmpty() }
            is ResultValue.Unit -> "kotlin.Unit"
            is ResultValue.Error -> value.error.javaClass.name
            is ResultValue.NotEvaluated -> "not-evaluated"
            null -> ""
        }

fun String.findNthSubstring(s: String, n: Int, start: Int = 0): Int

    fun String.findNthSubstring(s: String, n: Int, start: Int = 0): Int {
        if (n < 1 || start == -1) return -1

        var i = start

        for (k in 1..n) {
            i = indexOf(s, i)
            if (i == -1) return -1
            i += s.length
        }

        return i - s.length
    }

fun Int.toSourceCodePositionWithNewAbsolute(code: SourceCode, newCode: SourceCode): SourceCode.Position?

    fun Int.toSourceCodePositionWithNewAbsolute(code: SourceCode, newCode: SourceCode): SourceCode.Position? {
        val pos = toSourceCodePosition(code)
        val sep = code.text.determineSep()
        val absLineStart = if (pos.line == 1) 0
        else newCode.text.findNthSubstring(sep, pos.line - 1) + sep.length

        var nextNewLinePos = newCode.text.indexOf(sep, absLineStart)
        if (nextNewLinePos == -1) nextNewLinePos = newCode.text.length

        val abs = absLineStart + pos.col - 1
        if (abs > nextNewLinePos) return null

        return SourceCode.Position(pos.line, abs - absLineStart + 1, abs)
    }

var lastCompletionBackend: KotlinEngineConfig.CompletionBackend?

Which backend actually served the most recent complete call, and the AaCompletionClient.Outcome detail that decided it. complete logs both; reading them here lets a test assert which backend answered, which a test of the AA chain needs: a K1 fallback answers a name position with thousands of names, so "the candidate I wanted is in the list" can be satisfied by the fallback alone and still read as a green AA result. CompletionAaE2EProbe asserts it.

    @Volatile
    var lastCompletionBackend: KotlinEngineConfig.CompletionBackend? = null
        private set

var lastCompletionDetail: String

    @Volatile
    var lastCompletionDetail: String = ""
        private set

override suspend fun complete(s: String, cursor: Int): CompletionResult

Completion delegates to the K1 IDE-services path. K2 remains the execution backend, but autocomplete needs compiler-grade language semantics rather than partial handwritten Analysis API coverage.

    override suspend fun complete(s: String, cursor: Int): CompletionResult {
        // Stage C: when the AA completion backend is selected, generate candidates out-of-process on the
        // K2 Analysis API (sidecar /complete), supplying the box's inherited preamble + implicit receivers
        // so completion resolves against the same REPL context K1 sees. Any failure/timeout/absent-sidecar/
        // empty result falls through to the compiler-grade K1 delegate — K1 stays the safety net.
        // Every request logs which backend actually served it (channel "completion") so the source of a
        // given completion is never a mystery.
        val box = resolveBoxContext()
        val boxLabel = box?.let { "'${it.name.ifBlank { "Untitled" }}'(${it.id})" } ?: "none"
        if (KotlinEngineConfig.activeCompletionBackend == KotlinEngineConfig.CompletionBackend.AA) {
            val completionPreamble = box?.let { normalizePreambleText(gatherPreambleUpwards(it)) }.orEmpty()
            val receiverTypes = implicitReceivers.map { it.typeName }
            val startNs = System.nanoTime()
            val outcome = withContext(Dispatchers.IO) {
                // `introImports` is the scope every box gets for free. The sidecar analyses the snippet
                // in another process and cannot see it, so it must be told; before this it fell back to
                // its own hand-written copy, which had drifted. See the KDoc on `introImports`.
                AaCompletionClient.complete(
                    s, cursor, completionPreamble, receiverTypes, replImports = introImports,
                )
            }
            val ms = (System.nanoTime() - startNs) / 1_000_000
            if (outcome.served) {
                lastCompletionBackend = KotlinEngineConfig.CompletionBackend.AA
                lastCompletionDetail = outcome.detail
                lprintln("info,completion", "completion backend=AA box=$boxLabel ${ms}ms (${outcome.detail})")
                return outcome.result!!
            }
            lastCompletionBackend = KotlinEngineConfig.CompletionBackend.K1
            lastCompletionDetail = "AA→K1: ${outcome.detail}"
            lprintln("info,completion", "completion backend=K1 box=$boxLabel ${ms}ms (AA→K1: ${outcome.detail})")
        } else {
            lastCompletionBackend = KotlinEngineConfig.CompletionBackend.K1
            lastCompletionDetail = "backend=k1"
            lprintln("info,completion", "completion backend=K1 box=$boxLabel (backend=k1)")
        }
        return k1CompletionDelegate().complete(s, cursor)
    }

override suspend fun warmCompletion()

Build the K1 completion delegate ahead of the first real completion. The bootstrap is a full intro+preamble compile (~seconds, longer the colder the JVM's K1 infra). If it instead fires lazily from inside a runloop-pumping wait it re-enters the K1 engine mid-compile and corrupts its analysis state (a "Rewrite at slice PACKAGE_TO_FILES" error). Warming here, on the non-reentrant runloop before any such wait, keeps every later completion light and safe.

    override suspend fun warmCompletion() {
        k1CompletionDelegate()
    }

override suspend fun warmCompletionBackend(timeoutMs: Long): String

Poll the AA sidecar with the real request shape until it actually serves, so the next completion is the one a user gets rather than the K1 fallback.

One request is not enough: the sidecar builds its Analysis-API session in the background and, until it exists, returns success-with-no-candidates and the message "session building (warming up; using K1 meanwhile)" — which complete correctly treats as a fall-through to K1. That is right for a keystroke and wrong for a docs capture, which would otherwise photograph the fallback. Deliberately routed through AaCompletionClient with this box's own preamble, receivers and imports, so what is warmed is the session complete will hit and not a differently-shaped neighbour.

body — 49 lines, from line 808
    override suspend fun warmCompletionBackend(timeoutMs: Long): String {
        if (KotlinEngineConfig.activeCompletionBackend != KotlinEngineConfig.CompletionBackend.AA) {
            return "backend=k1 (nothing to warm)"
        }
        // The box's preamble and receivers are read on the runloop, the way [complete] reads them — this
        // is called from off-runloop callers (the docs `drive` op) and walking a box's parents while the
        // main thread edits them is not a race worth taking for a warm-up.
        //
        // …but only when there is a runloop to hop onto, and when we are not already on it. `Framer` only
        // queues; `Runloop.start()` drains it, and `Runloop.start()` is the sole writer of
        // [Runloop.mainThread] — so a null there means nothing will EVER run the dispatched block. A
        // `run-jvm-main` subprocess is exactly that case: it constructs a Runloop and never starts one, so
        // this hopped into a queue with no consumer and suspended for good. `CompletionAaE2EProbe` hit it
        // and the symptom was thoroughly misleading — the probe hung at the warm-up until the runner's
        // timeout killed it, which reads as a sidecar that never finished building its session. Being on
        // the runloop thread already is the same trap one step in: a `runBlocking` caller there blocks the
        // very frame that would have run the block.
        val readPreambleHere = Runloop.mainThread.let { it == null || it == Thread.currentThread() }
        val completionPreamble = if (readPreambleHere) {
            resolveBoxContext()?.let { normalizePreambleText(gatherPreambleUpwards(it)) }.orEmpty()
        } else withContext(Runloop.runLoop.main) {
            resolveBoxContext()?.let { normalizePreambleText(gatherPreambleUpwards(it)) }.orEmpty()
        }
        val receiverTypes = implicitReceivers.map { it.typeName }
        // A receiver whose members exist on any classpath, so "served" means the session works rather
        // than that this particular type resolved.
        val snippet = "val __warm = \"\"\nval __warmed = __warm."
        val deadline = System.nanoTime() + timeoutMs.coerceAtLeast(0) * 1_000_000
        var attempts = 0
        while (true) {
            attempts++
            val outcome = withContext(Dispatchers.IO) {
                AaCompletionClient.complete(
                    snippet, snippet.length, completionPreamble, receiverTypes, replImports = introImports,
                )
            }
            val detail = outcome.detail
            if (outcome.served) return "served after $attempts attempt(s) ($detail)"
            // A missing sidecar or an unreachable one will not become reachable by asking again.
            if (detail == "no-sidecar-url" || detail == "no-classpath" || detail.startsWith("error ")) {
                return "gave up after $attempts attempt(s) ($detail)"
            }
            if (System.nanoTime() >= deadline) return "timed out after $attempts attempt(s) ($detail)"
            delay(500)
        }
    }

    @Volatile
    private var completionWarmInFlight = false

override fun warmCompletionAsync()

Schedule warmCompletion on the main runloop (the same non-reentrant dispatcher complete uses) so the ~1s K1 bootstrap is paid ahead of the first real completion/error-suggestion request instead of stalling it. The completionWarmInFlight guard collapses the burst of edit events into a single scheduled warm; once warm for the current preamble, warmCompletion returns near-instantly (mutex + fingerprint check), and it re-bootstraps automatically when the preamble changes.

    @OptIn(DelicateCoroutinesApi::class)
    override fun warmCompletionAsync() {
        if (completionWarmInFlight) return
        completionWarmInFlight = true
        GlobalScope.launch(Runloop.runLoop.main) {
            try {
                warmCompletion()
            } catch (t: Throwable) {
                lprintln(
                    "warning,completion",
                    "background warmCompletion failed: ${t.javaClass.simpleName}: ${t.message}"
                )
            } finally {
                completionWarmInFlight = false
            }
        }
    }

    private var k1CompletionDelegate: KotlinInterface? = null

internal fun k1CompletionDelegateForTest(): KotlinInterface? = k1CompletionDelegate

The completion delegate built so far, for KotlinEngineReleaseSmokeTest.

    private var k1CompletionDelegateBootstrapped = false
    private var k1CompletionDelegatePreambleFingerprint: Int? = null
    private val k1CompletionDelegateMutex = Mutex()

internal fun handCompletionDelegateTo(next: KotlinInterface2)

Give this engine's K1 completion delegate to next, the same box's next engine, instead of disposing it with this one. The delegate holds the intro and the box's preamble and nothing of this engine's history, so it serves next as it is (and is rebuilt there if the preamble has changed). Otherwise every run of a box built a new one: a K1 compile of the whole intro, ~130 ms of CPU beside the run's own compile.

Asynchronous, because the delegate is usually still being built when the box's next run replaces this engine (an edit warms completion just before the run): the handover waits for that build. Call before dispose, which then leaves the delegate alone. The delegate is disposed instead when its build failed, or next has built its own meanwhile or is disposed.

body — 128 lines, from line 904
    @OptIn(DelicateCoroutinesApi::class)
    internal fun handCompletionDelegateTo(next: KotlinInterface2) {
        if (next === this) return
        completionDelegateHandedOn = true
        GlobalScope.launch(Dispatchers.Default) {
            val (delegate, fingerprint) = k1CompletionDelegateMutex.withLock {
                val d = k1CompletionDelegate
                k1CompletionDelegate = null
                if (d != null && (!k1CompletionDelegateBootstrapped || d.isDisposed)) {
                    d.dispose()
                    null to null
                } else d to k1CompletionDelegatePreambleFingerprint
            }
            if (delegate == null) return@launch
            val kept = next.k1CompletionDelegateMutex.withLock {
                if (next.k1CompletionDelegate != null || next.disposed) return@withLock false
                next.k1CompletionDelegate = delegate
                next.k1CompletionDelegateBootstrapped = true
                next.k1CompletionDelegatePreambleFingerprint = fingerprint
                delegate.associatedBox = next.associatedBox
                true
            }
            if (!kept) delegate.dispose()
            lprintln("info,ki,startup", "K1 completion delegate for $filePrefix ${if (kept) "handed to the next engine" else "disposed: the next engine has its own"}")
        }
    }

    @Volatile
    private var completionDelegateHandedOn = false

    private suspend fun k1CompletionDelegate(): KotlinInterface {
        return k1CompletionDelegateMutex.withLock {
            val existing = k1CompletionDelegate
            val box = resolveBoxContext()
            val completionPreamble = box?.let { normalizePreambleText(gatherPreambleUpwards(it)) }.orEmpty()
            val preambleFingerprint = completionPreamble.hashCode()
            if (existing != null &&
                k1CompletionDelegateBootstrapped &&
                k1CompletionDelegatePreambleFingerprint == preambleFingerprint
            ) {
                existing.associatedBox = associatedBox
                return@withLock existing
            }

            val boxLabel = box?.let { "'${it.name.ifBlank { "Untitled" }}' (${it.id})" } ?: "none"
            val resetReason = when {
                existing == null -> "create"
                !k1CompletionDelegateBootstrapped -> "bootstrap"
                else -> "preamble changed"
            }
            lprintln(
                "info,ki,startup",
                "request K1 completion delegate for K2 filePrefix=$filePrefix box=$boxLabel reason=$resetReason preambleLength=${completionPreamble.length}"
            )

            existing?.dispose()
            val created = KotlinInterface(
                propertyClasses = propertyClasses,
                properties = properties,
                implicitReceivers = implicitReceivers,
                classLoader = classLoader,
                filePrefix = "${filePrefix}_completion"
            )
            created.associatedBox = associatedBox
            k1CompletionDelegate = created
            k1CompletionDelegateBootstrapped = false
            // Asked for after this engine was disposed: it compiles nothing, and holds no project.
            if (disposed) created.dispose()

            if (!k1CompletionDelegateBootstrapped) {
                val implicits = if (box != null) listOf(box) else emptyList()
                lprintln(
                    "info,ki,startup",
                    "bootstrap K1 completion delegate filePrefix=${filePrefix}_completion box=$boxLabel introLength=${intro.length} preambleLength=${completionPreamble.length}"
                )
                val completionSetupCompileError: (List<ScriptDiagnostic>) -> Unit = { diagnostics ->
                    diagnostics.forEach {
                        lprintln("warning,completion", "K1 completion setup compile: ${it.message} ${it.location}")
                    }
                }
                val completionSetupEvalError: (List<ScriptDiagnostic>) -> Unit = { diagnostics ->
                    diagnostics.forEach {
                        lprintln("warning,completion", "K1 completion setup eval: ${it.message} ${it.location}")
                    }
                }
                val completionSetupEvalException: (Throwable) -> Unit = { throwable ->
                    lprintln(
                        "warning,completion",
                        "K1 completion setup exception: ${throwable.javaClass.simpleName}: ${throwable.message}"
                    )
                }
                // The editor's interpreter, not the box's code: keep it out of the box's profile lanes.
                withContext(ProfileExempt) {
                    if (box != null) {
                        Box.withBoxContext(box) {
                            created.exec(
                                intro,
                                onCompileError = completionSetupCompileError,
                                onEvalError = completionSetupEvalError,
                                onEvalException = completionSetupEvalException,
                                implicits = implicits
                            )
                            if (completionPreamble.isNotBlank()) {
                                created.exec(
                                    completionPreamble,
                                    onCompileError = completionSetupCompileError,
                                    onEvalError = completionSetupEvalError,
                                    onEvalException = completionSetupEvalException,
                                    implicits = implicits
                                )
                            }
                        }
                    } else {
                        created.exec(
                            intro,
                            onCompileError = completionSetupCompileError,
                            onEvalError = completionSetupEvalError,
                            onEvalException = completionSetupEvalException
                        )
                    }
                }
                k1CompletionDelegateBootstrapped = true
                k1CompletionDelegatePreambleFingerprint = preambleFingerprint
            }

            created
        }
    }

Build a source text suitable for FIR analysis: prepend REPL imports and insert a dummy identifier at the cursor so incomplete expressions parse cleanly.

source — 160 lines, from line 1037
    private fun buildFirSourceText(s: String, cursor: Int): String {
        // Extract import lines from the REPL intro preamble so FIR can resolve stdlib symbols
        val introImports = intro.lines()
            .filter { it.trimStart().startsWith("import ") }
            .joinToString("\n")

        // Insert a dummy identifier at cursor to make incomplete expressions valid for parsing.
        // e.g. "q." becomes "q.COMPLETION_PLACEHOLDER" which is syntactically valid.
        val patched = StringBuilder(s).insert(cursor, "COMPLETION_PLACEHOLDER").toString()

        return introImports + "\n" + patched
    }

    private fun buildFirReceiverContextSource(s: String, cursor: Int): String {
        val introImports = intro.lines()
            .filter { it.trimStart().startsWith("import ") }
            .joinToString("\n")

        val prefix = extractPrefix(s, cursor)
        val beforePrefix = cursor - prefix.length
        val dotOffset = if (beforePrefix >= 2 && s[beforePrefix - 1] == '.' && s[beforePrefix - 2] == '?') {
            beforePrefix - 2
        } else if (beforePrefix >= 1 && s[beforePrefix - 1] == '.') {
            beforePrefix - 1
        } else {
            cursor
        }
        val context = s.substring(0, dotOffset.coerceIn(0, s.length))
        return introImports + "\n" + context
    }

    private fun computeK2Completion(s: String, cursor: Int): List<SourceCodeCompletionVariant> {
        val results = mutableListOf<SourceCodeCompletionVariant>()

        // Parse the completion site to determine what kind of completion we need
        val completionSite = parseCompletionSite(s, cursor)

        // Build FIR-friendly source text with imports and a dummy at cursor
        val firSource = buildFirSourceText(s, cursor)
        val receiverFirSource by lazy { buildFirReceiverContextSource(s, cursor) }

        val nameFilter: (String) -> Boolean = { name ->
            completionSite.prefix.isEmpty() || name.startsWith(completionSite.prefix, ignoreCase = true)
        }

        fun resolveCompletionReceiver(receiverText: String): KClass<*>? {
            val simpleResolved = resolveReceiverType(receiverText, receiverFirSource)
            if (simpleResolved != null && simpleResolved != Any::class) return simpleResolved
            val probeResolved = resolveExpressionTypeViaFir(receiverText, s, cursor)
            return probeResolved ?: simpleResolved
        }

        when (completionSite) {
            is CompletionSite.DotCompletion -> {
                val receiverType = resolveCompletionReceiver(completionSite.receiverText)
                if (receiverType != null) {
                    addMembersFromClass(receiverType, completionSite.prefix, results)
                }
                addStaticMembersIfPackageOrClass(completionSite.receiverText, completionSite.prefix, results)
            }

            is CompletionSite.SafeDotCompletion -> {
                val receiverType = resolveCompletionReceiver(completionSite.receiverText)
                if (receiverType != null) {
                    addMembersFromClass(receiverType, completionSite.prefix, results)
                }
            }

            is CompletionSite.NameCompletion -> {
                // Variables from REPL context
                for ((name, info) in context.vars) {
                    if (!nameFilter(name)) continue
                    if (name.contains("$")) continue
                    val type = try {
                        shortenType(info.descriptor.returnType.toString())
                    } catch (_: Exception) {
                        "Any?"
                    }
                    val kind = if (info.descriptor is KMutableProperty<*>) "var" else "val"
                    results.add(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "$kind $name", tail = ": $type",
                            icon = kind,
                        )
                    )
                }

                // Functions from REPL context — with full parameter signatures
                for ((name, info) in context.functions) {
                    if (!nameFilter(name)) continue
                    if (name.contains("$") || name == "main") continue
                    val params = info.function.parameters.filter { it.kind == KParameter.Kind.VALUE }
                    val paramsStr = formatKotlinParams(params)
                    val returnType = try {
                        shortenType(info.function.returnType.toString())
                    } catch (_: Exception) {
                        "Any?"
                    }
                    results.add(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "fun $name$paramsStr", tail = ": $returnType",
                            icon = "method",
                        )
                    )
                }

                // Members from implicit receivers (e.g., Box members are callable as top-level in the REPL)
                for (kotlinType in implicitReceivers) {
                    val klass = try {
                        kotlinType.fromClass
                    } catch (_: Exception) {
                        null
                    } ?: continue
                    addMembersFromClass(klass, completionSite.prefix, results)
                }

                // Members from lambda receiver types (e.g., inside `fline { }`, FLine methods are available)
                for (receiverClass in detectLambdaReceiverTypes(s, cursor)) {
                    addMembersFromClass(receiverClass, completionSite.prefix, results)
                }

                // Keywords
                val keywords = listOf(
                    "val", "var", "fun", "class", "object", "interface", "if", "else", "when",
                    "for", "while", "do", "return", "break", "continue", "throw", "try", "catch",
                    "finally", "import", "package", "is", "as", "in", "true", "false", "null",
                    "this", "super", "data", "sealed", "enum", "abstract", "open", "override",
                    "private", "protected", "internal", "public", "companion", "lateinit", "lazy",
                    "suspend", "inline", "infix", "operator", "tailrec", "external", "annotation",
                    "crossinline", "noinline", "reified", "typealias", "typeof", "by", "where",
                )
                for (kw in keywords) {
                    if (nameFilter(kw)) {
                        results.add(
                            SourceCodeCompletionVariant(
                                text = kw,
                                displayText = kw,
                                tail = "",
                                icon = "keyword"
                            )
                        )
                    }
                }

                // Declarations from the current editor text that haven't been executed yet (via FIR resolution)
                addFirSourceDeclarations(firSource, nameFilter, results)

                // Top-level functions and extension functions on implicit receivers from imported packages
                val existingNames = results.map { it.text }.toSet()
                addImportedTopLevelFunctions(completionSite.prefix, results, existingNames)

                // Class names from classpath — provides constructor completion for types like FLine, Vec3, etc.
                if (completionSite.prefix.length >= 2) {
                    addClasspathClasses(completionSite.prefix, results)
                }
            }
        }

        return results.distinctBy { it.text }.sortedWith(completionComparator(completionSite.prefix))
    }

Sort completions: exact case-prefix match first, then by sortGroup, then shorter names first, then alphabetical.

    private fun completionComparator(prefix: String) = compareBy<SourceCodeCompletionVariant>(
        { if (prefix.isNotEmpty() && it.text.startsWith(prefix)) 0 else 1 },
        { (it.extraInfo.get("sortGroup") as? Int) ?: 0 },
        { it.text.length },
        { it.text.lowercase() }
    )

Kotlin parameter formatting helpers

source — 71 lines, from line 1211
    private fun formatKotlinParams(params: List<KParameter>): String {
        if (params.isEmpty()) return "()"
        return "(${params.joinToString(", ") { p -> formatKotlinParam(p) }})"
    }

    private fun formatKotlinParam(param: KParameter): String {
        val name = param.name
        val type = try {
            shortenType(param.type.toString())
        } catch (_: Exception) {
            "Any?"
        }
        val sb = StringBuilder()
        if (param.isVararg) sb.append("vararg ")
        // If the name is null or synthetic (arg0, p0, etc.), show just the type
        if (name != null && !name.matches(Regex("^(arg|p)\\d+$"))) {
            sb.append("$name: $type")
        } else {
            sb.append(type)
        }
        if (param.isOptional) sb.append(" = ...")
        return sb.toString()
    }

    private fun formatJavaMethodParams(method: java.lang.reflect.Method): String {
        val kfunc = try {
            method.kotlinFunction
        } catch (_: Exception) {
            null
        }
        if (kfunc != null) {
            val valueParams = kfunc.parameters.filter { it.kind == KParameter.Kind.VALUE }
            return formatKotlinParams(valueParams)
        }
        // Fallback to Java reflection
        val params = method.parameters
        if (params.isEmpty()) return "()"
        return "(${
            params.joinToString(", ") { p ->
                val name = p.name
                val type = shortenType(p.type.kotlin.simpleName ?: p.type.simpleName)
                // If the name is synthetic (arg0, p0, etc.), show just the type
                if (name.matches(Regex("^(arg|p)\\d+$"))) type else "$name: $type"
            }
        })"
    }

    private fun formatMethodReturnType(method: java.lang.reflect.Method): String {
        val kfunc = try {
            method.kotlinFunction
        } catch (_: Exception) {
            null
        }
        return try {
            kfunc?.let { shortenType(it.returnType.toString()) }
                ?: shortenType(method.returnType.kotlin.simpleName ?: method.returnType.simpleName)
        } catch (_: Exception) {
            "Any?"
        }
    }

    private fun isDeprecated(method: java.lang.reflect.Method): DeprecationLevel? {
        if (method.isAnnotationPresent(Deprecated::class.java)) return DeprecationLevel.WARNING
        val kfunc = try {
            method.kotlinFunction
        } catch (_: Exception) {
            null
        }
        if (kfunc?.annotations?.any { it is Deprecated } == true) return DeprecationLevel.WARNING
        return null
    }

Completion site parsing

source — 62 lines, from line 1285
    private sealed class CompletionSite {
        abstract val prefix: String

        data class NameCompletion(override val prefix: String) : CompletionSite()
        data class DotCompletion(val receiverText: String, override val prefix: String) : CompletionSite()
        data class SafeDotCompletion(val receiverText: String, override val prefix: String) : CompletionSite()
    }

    private fun parseCompletionSite(s: String, cursor: Int): CompletionSite {
        // Extract the prefix (identifier being typed at cursor)
        val prefix = extractPrefix(s, cursor)
        val beforePrefix = cursor - prefix.length

        if (beforePrefix <= 0) return CompletionSite.NameCompletion(prefix)

        // Check for ?. (safe call)
        val isSafeDot = beforePrefix >= 2 && s[beforePrefix - 1] == '.' && s[beforePrefix - 2] == '?'
        val isDot = !isSafeDot && s[beforePrefix - 1] == '.'

        if (!isDot && !isSafeDot) return CompletionSite.NameCompletion(prefix)

        // Extract receiver text (everything before the dot, going back through chained expressions)
        val dotPos = if (isSafeDot) beforePrefix - 2 else beforePrefix - 1
        val receiverText = extractReceiverExpression(s, dotPos)

        return if (isSafeDot) CompletionSite.SafeDotCompletion(receiverText, prefix)
        else CompletionSite.DotCompletion(receiverText, prefix)
    }

    private fun extractReceiverExpression(s: String, endPos: Int): String {
        // Walk backwards from endPos to find the start of the receiver expression
        // Handles: simple names, chained dots (a.b.c), function calls (a.foo()), indexing (a[0])
        var pos = endPos - 1
        var parenDepth = 0
        var bracketDepth = 0

        while (pos >= 0) {
            val c = s[pos]
            when {
                c == ')' -> {
                    parenDepth++; pos--
                }

                c == '(' && parenDepth > 0 -> {
                    parenDepth--; pos--
                }

                c == ']' -> {
                    bracketDepth++; pos--
                }

                c == '[' && bracketDepth > 0 -> {
                    bracketDepth--; pos--
                }

                parenDepth > 0 || bracketDepth > 0 -> pos--
                c.isLetterOrDigit() || c == '_' || c == '.' || c == '?' -> pos--
                else -> break
            }
        }
        return s.substring(pos + 1, endPos).trim()
    }

FIR-based type resolution for un-executed code

Cache of FIR-resolved types from the last analysis run. Maps variable/function name -> resolved KClass. Invalidated when source text changes.

    private var firTypeCache: Map<String, KClass<*>> = emptyMap()
    private var firFunctionCache: Map<String, KClass<*>> = emptyMap()
    private var firCacheSourceHash: Int = 0

Resolve types from source code using the K2 compiler's FIR pipeline.

This mirrors the first half of K2ReplCompiler.compileImpl: 1. Create a KtFile from the source text 2. Create a temporary FIR source session 3. Build raw FIR from the KtFile 4. Run full type resolution on the FIR 5. Walk the resolved FIR tree to extract variable/function types 6. Clean up: remove the temporary module data from history

Returns a pair of (variableTypes, functionReturnTypes) where each maps name -> KClass.

source — 193 lines, from line 1372
    private fun resolveTypesViaFir(sourceText: String): Pair<Map<String, KClass<*>>, Map<String, KClass<*>>> {
        val hash = sourceText.hashCode()
        if (hash == firCacheSourceHash && firTypeCache.isNotEmpty()) {
            return firTypeCache to firFunctionCache
        }

        try {
            // Access compilation state internals via reflection
            val stateClass = compilationState::class.java

            val moduleDataProvider = stateClass.getDeclaredField("moduleDataProvider").let {
                it.isAccessible = true; it.get(compilationState)
            }
            val sessionFactoryContext = stateClass.getDeclaredField("sessionFactoryContext").let {
                it.isAccessible = true; it.get(compilationState)
            }
            val sharedLibrarySession = stateClass.getDeclaredField("sharedLibrarySession").let {
                it.isAccessible = true; it.get(compilationState)
            }
            val compilerContext = stateClass.getDeclaredField("compilerContext").let {
                it.isAccessible = true; it.get(compilationState)
            }

            // Get CompilerConfiguration from the environment
            val environment = compilerContext::class.java.getMethod("getEnvironment").invoke(compilerContext)
            val compilerConfiguration = environment::class.java.getMethod("getConfiguration").invoke(environment)
            val configCopy = compilerConfiguration::class.java.getMethod("copy").invoke(compilerConfiguration)

            // Create KtFile from the source text via getScriptKtFile
            val project = psiProject
            val sourceCode = SourceCodeImpl(id, sourceText)

            val getScriptKtFileMethod = Class.forName(
                "org.jetbrains.kotlin.scripting.compiler.plugin.impl.JvmCompilationUtilKt"
            ).getMethod(
                "getScriptKtFile",
                Class.forName("kotlin.script.experimental.api.SourceCode"),
                Class.forName("kotlin.script.experimental.api.ScriptCompilationConfiguration"),
                Class.forName("org.jetbrains.kotlin.com.intellij.openapi.project.Project"),
                Class.forName("org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptDiagnosticsMessageCollector")
            )

            val ktFileResult = getScriptKtFileMethod.invoke(null, sourceCode, compilerConfig, project, messageCollector)

            // Extract KtFile from ResultWithDiagnostics<KtFile>
            val ktFile = if (ktFileResult is ResultWithDiagnostics.Success<*>) {
                ktFileResult.value as KtFile
            } else {
                lprintln("debug,kotlin", "FIR analysis: failed to create KtFile: $ktFileResult")
                return emptyMap<String, KClass<*>>() to emptyMap()
            }

            // Mark script as REPL snippet
            ktFile.script?.let { script ->
                try {
                    val markMethod = script::class.java.getMethod("markAsReplSnippet")
                    markMethod.invoke(script)
                } catch (_: Exception) {
                }
            }

            // Get extension registrars from compiler configuration
            val firExtensionRegistrarCompanion = Class.forName(
                "org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar"
            ).getDeclaredField("Companion").get(null)
            val getExtensionsMethod = Class.forName(
                "org.jetbrains.kotlin.compiler.plugin.FirExtensionRegistrarConfigurationUtilKt"
            ).methods.first { it.name == "getCompilerExtensions" }
            val extensionRegistrars =
                getExtensionsMethod.invoke(null, configCopy, firExtensionRegistrarCompanion) as List<*>

            // Record the module data history size before we add our temporary entry
            val moduleDataHistory = moduleDataProvider::class.java.getMethod("getModuleDataHistory")
                .invoke(moduleDataProvider) as MutableList<*>
            val historySize = moduleDataHistory.size

            // Create temporary module data
            val nameClass = Class.forName("org.jetbrains.kotlin.name.Name")
            val nameSpecial = nameClass.getMethod("special", String::class.java)
            val snippetName = nameSpecial.invoke(null, "<completion-analysis>")

            val addSnippetMethod = moduleDataProvider::class.java.getMethod("addNewSnippetModuleData", nameClass)
            val moduleData = addSnippetMethod.invoke(moduleDataProvider, snippetName)

            try {
                // Create source session via FirJvmSessionFactory.INSTANCE.createSourceSession(...)
                val sessionFactoryClass = Class.forName("org.jetbrains.kotlin.fir.session.FirJvmSessionFactory")
                val factoryInstance = sessionFactoryClass.getDeclaredField("INSTANCE").get(null)

                // Get EMPTY search scope
                val emptyScope = Class.forName(
                    "org.jetbrains.kotlin.fir.session.environment.AbstractProjectFileSearchScope\$EMPTY"
                ).getDeclaredField("INSTANCE").get(null)

                // Null-returning lambda for incremental compilation providers
                val incrementalProvidersLambda = java.lang.reflect.Proxy.newProxyInstance(
                    classLoader,
                    arrayOf(kotlin.Function1::class.java)
                ) { _, _, _ -> null }

                // No-op lambda for session configurator init
                val initLambda = java.lang.reflect.Proxy.newProxyInstance(
                    classLoader,
                    arrayOf(kotlin.Function1::class.java)
                ) { _, _, _ -> kotlin.Unit }

                // The method signature is:
                // createSourceSession(moduleData, scope, incrementalProviders, extensionRegistrars,
                //                     config, context, needRegisterJavaElementFinder, isForLeafHmppModule, init)
                // All 9 params are explicit in the bytecode (Kotlin default param generates a separate $default bridge)
                val createSourceSessionMethod = sessionFactoryClass.methods.firstOrNull {
                    it.name == "createSourceSession" && it.parameterCount == 9
                } ?: sessionFactoryClass.methods.first {
                    // Fallback: try the $default synthetic bridge if present
                    it.name == "createSourceSession" && it.parameterCount > 0
                }

                val sourceSession = if (createSourceSessionMethod.parameterCount == 9) {
                    createSourceSessionMethod.invoke(
                        factoryInstance,
                        moduleData,                // FirModuleData
                        emptyScope,                // AbstractProjectFileSearchScope
                        incrementalProvidersLambda, // Function1<FirSession, FirJvmIncrementalCompilationSymbolProviders?>
                        extensionRegistrars,       // List<FirExtensionRegistrar>
                        configCopy,                // CompilerConfiguration
                        sessionFactoryContext,     // FirJvmSessionFactory.Context
                        true,                      // needRegisterJavaElementFinder
                        false,                     // isForLeafHmppModule
                        initLambda                 // Function1<FirSessionConfigurator, Unit>
                    )
                } else {
                    // Unexpected param count — try best effort
                    createSourceSessionMethod.invoke(
                        factoryInstance, moduleData, emptyScope, incrementalProvidersLambda,
                        extensionRegistrars, configCopy, sessionFactoryContext, true, false
                    )
                }

                if (sourceSession == null) {
                    lprintln("debug,kotlin", "FIR analysis: createSourceSession returned null")
                    return emptyMap<String, KClass<*>>() to emptyMap()
                }

                // Build FIR from KtFile: FirUtilsKt.buildFirFromKtFiles(session, listOf(ktFile))
                val buildFirMethod = Class.forName("org.jetbrains.kotlin.fir.pipeline.FirUtilsKt")
                    .getMethod(
                        "buildFirFromKtFiles",
                        Class.forName("org.jetbrains.kotlin.fir.FirSession"),
                        Collection::class.java
                    )
                val rawFir = buildFirMethod.invoke(null, sourceSession, listOf(ktFile)) as List<*>

                lprintln("debug,kotlin", "FIR analysis: buildFirFromKtFiles returned ${rawFir.size} files")

                // Run resolution: AnalyseKt.runResolution(session, rawFir)
                val runResolutionMethod = Class.forName("org.jetbrains.kotlin.fir.pipeline.AnalyseKt")
                    .getMethod(
                        "runResolution",
                        Class.forName("org.jetbrains.kotlin.fir.FirSession"),
                        List::class.java
                    )
                val result = runResolutionMethod.invoke(null, sourceSession, rawFir) as kotlin.Pair<*, *>
                val firFiles = result.second as List<*>

                lprintln("debug,kotlin", "FIR analysis: runResolution returned ${firFiles.size} files")

                // Walk resolved FIR tree to extract types
                val varTypes = mutableMapOf<String, KClass<*>>()
                val funcTypes = mutableMapOf<String, KClass<*>>()
                extractTypesFromFir(firFiles, varTypes, funcTypes)

                lprintln("debug,kotlin", "FIR analysis: extracted ${varTypes.size} var types: ${varTypes.keys}")
                lprintln("debug,kotlin", "FIR analysis: extracted ${funcTypes.size} func types: ${funcTypes.keys}")

                firTypeCache = varTypes
                firFunctionCache = funcTypes
                firCacheSourceHash = hash

                return varTypes to funcTypes
            } finally {
                // Clean up: remove the temporary module data from history
                if (moduleDataHistory.size > historySize) {
                    moduleDataHistory.removeAt(moduleDataHistory.size - 1)
                }
            }
        } catch (e: Exception) {
            lprintln("warning,kotlin", "FIR type resolution failed: ${e::class.simpleName}: ${e.message}")
            if (e.cause != null) {
                lprintln("debug,kotlin", "  caused by: ${e.cause?.message}")
            }
            return emptyMap<String, KClass<*>>() to emptyMap()
        }
    }

Walk the resolved FIR tree to extract variable types, function return types, and lambda parameter types (like implicit it). FirFile → FirScript → declarations → FirProperty / FirNamedFunction Also walks into property initializers to find FirAnonymousFunction parameters.

source — 65 lines, from line 1572
    private fun extractTypesFromFir(
        firFiles: List<*>,
        varTypes: MutableMap<String, KClass<*>>,
        funcTypes: MutableMap<String, KClass<*>>
    ) {
        val firScriptClass = Class.forName("org.jetbrains.kotlin.fir.declarations.FirScript")
        val firPropertyClass = Class.forName("org.jetbrains.kotlin.fir.declarations.FirProperty")
        val firNamedFunctionClass = Class.forName("org.jetbrains.kotlin.fir.declarations.FirNamedFunction")
        val firResolvedTypeRefClass = Class.forName("org.jetbrains.kotlin.fir.types.FirResolvedTypeRef")
        val coneClassLikeTypeClass = Class.forName("org.jetbrains.kotlin.fir.types.ConeClassLikeType")
        val firAnonymousFunctionClass = try {
            Class.forName("org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction")
        } catch (_: Exception) {
            null
        }
        val firAnonymousFunctionExpressionClass = try {
            Class.forName("org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression")
        } catch (_: Exception) {
            null
        }
        val firValueParameterClass = try {
            Class.forName("org.jetbrains.kotlin.fir.declarations.FirValueParameter")
        } catch (_: Exception) {
            null
        }

        for (firFile in firFiles) {
            if (firFile == null) continue
            val fileDecls = firFile::class.java.getMethod("getDeclarations").invoke(firFile) as? List<*> ?: continue

            lprintln("debug,kotlin", "FIR walk: FirFile has ${fileDecls.size} top-level declarations")

            for (decl in fileDecls) {
                if (decl == null) continue

                lprintln("debug,kotlin", "FIR walk: top-level decl type: ${decl::class.java.simpleName}")

                if (firScriptClass.isInstance(decl)) {
                    val scriptDecls = firScriptClass.getMethod("getDeclarations").invoke(decl) as? List<*> ?: continue

                    lprintln("debug,kotlin", "FIR walk: FirScript has ${scriptDecls.size} declarations")

                    for (scriptDecl in scriptDecls) {
                        if (scriptDecl == null) continue

                        lprintln("debug,kotlin", "FIR walk: script decl type: ${scriptDecl::class.java.simpleName}")

                        processDeclaration(
                            scriptDecl, firPropertyClass, firNamedFunctionClass,
                            firResolvedTypeRefClass, coneClassLikeTypeClass, varTypes, funcTypes
                        )

                        // Walk into property initializers to extract lambda parameters (e.g. `it`)
                        if (firPropertyClass.isInstance(scriptDecl) && firAnonymousFunctionClass != null) {
                            extractLambdaParameters(
                                scriptDecl, firAnonymousFunctionClass,
                                firAnonymousFunctionExpressionClass, firValueParameterClass,
                                firResolvedTypeRefClass, coneClassLikeTypeClass, varTypes
                            )
                        }
                    }
                }
            }
        }
    }

Walk into a FirProperty's initializer to find FirAnonymousFunction value parameters. This handles cases like: var q : (Vec3) -> Unit = { it. } where it is an implicit lambda parameter that needs to be available for completion.

source — 44 lines, from line 1643
    private fun extractLambdaParameters(
        property: Any,
        firAnonymousFunctionClass: Class<*>,
        firAnonymousFunctionExpressionClass: Class<*>?,
        firValueParameterClass: Class<*>?,
        firResolvedTypeRefClass: Class<*>,
        coneClassLikeTypeClass: Class<*>,
        varTypes: MutableMap<String, KClass<*>>
    ) {
        try {
            val initializer = property::class.java.getMethod("getInitializer").invoke(property) ?: return

            // The initializer might be a FirAnonymousFunctionExpression wrapping a FirAnonymousFunction
            val anonFunction = when {
                firAnonymousFunctionClass.isInstance(initializer) -> initializer
                firAnonymousFunctionExpressionClass != null && firAnonymousFunctionExpressionClass.isInstance(
                    initializer
                ) ->
                    firAnonymousFunctionExpressionClass.getMethod("getAnonymousFunction").invoke(initializer)

                else -> null
            } ?: return

            val valueParams = firAnonymousFunctionClass.getMethod("getValueParameters").invoke(anonFunction) as? List<*>
                ?: return

            for (param in valueParams) {
                if (param == null || firValueParameterClass == null) continue
                if (!firValueParameterClass.isInstance(param)) continue

                val name = firValueParameterClass.getMethod("getName").invoke(param)
                val nameStr = name?.toString() ?: continue
                if (nameStr.startsWith("<")) continue  // skip synthetic names

                val klass = extractKClassFromReturnType(param, firResolvedTypeRefClass, coneClassLikeTypeClass)
                if (klass != null) {
                    lprintln("debug,kotlin", "FIR walk: lambda param '$nameStr' -> ${klass.simpleName}")
                    varTypes[nameStr] = klass
                }
            }
        } catch (e: Exception) {
            lprintln("debug,kotlin", "FIR walk: error extracting lambda params: ${e.message}")
        }
    }

Process a single FIR declaration and extract its type.

    private fun processDeclaration(
        decl: Any,
        firPropertyClass: Class<*>,
        firNamedFunctionClass: Class<*>,
        firResolvedTypeRefClass: Class<*>,
        coneClassLikeTypeClass: Class<*>,
        varTypes: MutableMap<String, KClass<*>>,
        funcTypes: MutableMap<String, KClass<*>>
    ) {
        try {
            if (firPropertyClass.isInstance(decl)) {
                val name = firPropertyClass.getMethod("getName").invoke(decl)
                val nameStr = name?.toString() ?: return
                if (nameStr.contains("$") || nameStr.startsWith("<")) return

                val klass = extractKClassFromReturnType(decl, firResolvedTypeRefClass, coneClassLikeTypeClass)
                lprintln("debug,kotlin", "FIR walk: property '$nameStr' -> ${klass?.simpleName ?: "UNRESOLVED"}")
                if (klass != null) {
                    varTypes[nameStr] = klass
                }
            } else if (firNamedFunctionClass.isInstance(decl)) {
                val name = firNamedFunctionClass.getMethod("getName").invoke(decl)
                val nameStr = name?.toString() ?: return
                if (nameStr.contains("$") || nameStr == "main" || nameStr.startsWith("<")) return

                val klass = extractKClassFromReturnType(decl, firResolvedTypeRefClass, coneClassLikeTypeClass)
                if (klass != null) {
                    funcTypes[nameStr] = klass
                }
            }
        } catch (e: Exception) {
            lprintln("debug,kotlin", "Error extracting type from FIR declaration: ${e.message}")
        }
    }

Extract a KClass from a FIR declaration's return type ref. Chain: decl.returnTypeRef → FirResolvedTypeRef.coneType → ConeClassLikeType.lookupTag.classId → Class.forName

    private fun extractKClassFromReturnType(
        decl: Any,
        firResolvedTypeRefClass: Class<*>,
        coneClassLikeTypeClass: Class<*>
    ): KClass<*>? {
        val returnTypeRef = decl::class.java.getMethod("getReturnTypeRef").invoke(decl) ?: run {
            lprintln("debug,kotlin", "FIR type: returnTypeRef is null")
            return null
        }
        if (!firResolvedTypeRefClass.isInstance(returnTypeRef)) {
            lprintln(
                "debug,kotlin",
                "FIR type: returnTypeRef is ${returnTypeRef::class.java.simpleName}, not FirResolvedTypeRef"
            )
            return null
        }

        val coneType = firResolvedTypeRefClass.getMethod("getConeType").invoke(returnTypeRef) ?: return null
        if (!coneClassLikeTypeClass.isInstance(coneType)) {
            lprintln("debug,kotlin", "FIR type: coneType is ${coneType::class.java.simpleName}, not ConeClassLikeType")
            return null
        }

        val lookupTag = coneClassLikeTypeClass.getMethod("getLookupTag").invoke(coneType) ?: return null
        val classId = lookupTag::class.java.getMethod("getClassId").invoke(lookupTag) ?: return null
        val fqName = classId::class.java.getMethod("asFqNameString").invoke(classId) as? String ?: return null

        // Convert slash-separated FQ name to dot-separated: "kotlin/Int" -> "kotlin.Int"
        val dotName = fqName.replace('/', '.')

        lprintln("debug,kotlin", "FIR type: resolved to '$dotName'")

        return resolveTypeName(dotName)
    }

FIR-based expression type resolution for complex receivers

Resolve the type of an arbitrary expression by synthesizing a probe variable and running FIR type resolution on it.

This handles cases where the receiver is a complex expression like (a + b), if (c) x else y, listOf(1,2,3).map { ... }, etc. that can't be resolved by simple name/chain lookup in resolveReceiverType.

The approach: 1. Take the original code up to the dot as context (variable/function declarations) 2. Add synthetic declarations for REPL context variables not in the current code 3. Append val __completion_probe__ = <receiverExpression> 4. Run FIR resolution and read the type of __completion_probe__

    private fun resolveExpressionTypeViaFir(receiverText: String, s: String, cursor: Int): KClass<*>? {
        try {
            val introImports = intro.lines()
                .filter { it.trimStart().startsWith("import ") }
                .joinToString("\n")

            // Find the dot position: cursor is after any typed prefix, dot is just before that
            val prefix = extractPrefix(s, cursor)
            val beforePrefix = cursor - prefix.length
            // Determine if safe dot (?.) or regular dot (.)
            val dotOffset = if (beforePrefix >= 2 && s[beforePrefix - 1] == '.' && s[beforePrefix - 2] == '?')
                beforePrefix - 2
            else
                beforePrefix - 1

            // Code context: everything before the dot (includes receiver expr and prior declarations)
            val codeContext = if (dotOffset > 0) s.substring(0, dotOffset) else ""

            // Include synthetic declarations for REPL context variables not declared in current code
            val contextDecls = buildContextDeclarationsForFir(codeContext)

            val probeSource = "$introImports\n$contextDecls\n$codeContext\nval __completion_probe__ = $receiverText\n"

            lprintln("debug,kotlin", "FIR expression probe for receiver '$receiverText'")

            val (varTypes, _) = resolveTypesViaFir(probeSource)
            val resolved = varTypes["__completion_probe__"]
            if (resolved != null) {
                lprintln("debug,kotlin", "FIR expression probe resolved: ${resolved.simpleName}")
            }
            return resolved
        } catch (e: Exception) {
            lprintln("debug,kotlin", "FIR expression type resolution failed: ${e.message}")
            return null
        }
    }

Detect lambda receiver types at the cursor position using a FIR probe.

When the cursor is inside a lambda with a receiver (e.g. fline { moveTo^ }), the compiler resolves this to the receiver type of the lambda. We synthesize val __this_probe__ = this at the cursor and run FIR resolution to discover what this refers to.

Returns a list of KClass receiver types that should contribute members to name completion (excluding the REPL-level implicit receivers which are already handled separately).

source — 41 lines, from line 1830
    private fun detectLambdaReceiverTypes(s: String, cursor: Int): List<KClass<*>> {
        try {
            val introImports = intro.lines()
                .filter { it.trimStart().startsWith("import ") }
                .joinToString("\n")

            // Code context: everything before the cursor
            val codeContext = s.substring(0, cursor)

            // Include synthetic declarations for REPL context variables
            val contextDecls = buildContextDeclarationsForFir(codeContext)

            // Insert a probe that captures `this` — if we're inside a receiver lambda,
            // FIR will resolve `this` to the lambda's receiver type
            val probeSource =
                "$introImports\n$contextDecls\n${codeContext}val __this_probe__ = this\n${s.substring(cursor)}\n"

            val (varTypes, _) = resolveTypesViaFir(probeSource)
            val resolved = varTypes["__this_probe__"] ?: return emptyList()

            // Filter out the REPL-level implicit receivers since they're already included
            val implicitReceiverClasses = implicitReceivers.mapNotNull { kotlinType ->
                try {
                    kotlinType.fromClass
                } catch (_: Exception) {
                    null
                }
            }.toSet()

            if (resolved in implicitReceiverClasses) return emptyList()

            // Skip uninteresting types
            if (resolved == Any::class || resolved == Nothing::class || resolved == Unit::class) return emptyList()

            lprintln("debug,kotlin", "Detected lambda receiver type: ${resolved.simpleName}")
            return listOf(resolved)
        } catch (e: Exception) {
            lprintln("debug,kotlin", "Lambda receiver detection failed: ${e.message}")
            return emptyList()
        }
    }

Build synthetic val declarations for REPL context variables that are not already declared in the given code text. This allows FIR to resolve expressions that reference variables defined in earlier REPL evaluations.

source — 44 lines, from line 1877
    private fun buildContextDeclarationsForFir(codeContext: String): String {
        val sb = StringBuilder()
        for ((name, info) in context.vars) {
            if (name.contains("$") || name.startsWith("<")) continue
            // Skip if already declared in the current code context
            if (Regex("""\b(val|var)\s+${Regex.escape(name)}\b""").containsMatchIn(codeContext)) continue
            val type = try {
                val t = info.descriptor.returnType.toString()
                // Skip internal/error types
                if (t.contains("$") || t.contains("<error>") || t.contains("<unresolved>")) continue
                t
            } catch (_: Exception) {
                // Fallback to runtime class name
                info.value?.let { it::class.qualifiedName ?: it::class.java.name } ?: continue
            }
            sb.appendLine("val $name: $type = TODO()")
        }
        // Also emit function declarations for REPL context functions
        for ((name, info) in context.functions) {
            if (name.contains("$") || name == "main" || name.startsWith("<")) continue
            if (Regex("""\bfun\s+${Regex.escape(name)}\b""").containsMatchIn(codeContext)) continue
            try {
                val params = info.function.parameters.filter { it.kind == KParameter.Kind.VALUE }
                val paramsDecl = params.joinToString(", ") { p ->
                    val pName = p.name ?: "p${p.index}"
                    val pType = try {
                        p.type.toString()
                    } catch (_: Exception) {
                        "Any?"
                    }
                    "$pName: $pType"
                }
                val returnType = try {
                    info.function.returnType.toString()
                } catch (_: Exception) {
                    "Any?"
                }
                if (returnType.contains("$") || returnType.contains("<error>")) continue
                sb.appendLine("fun $name($paramsDecl): $returnType = TODO()")
            } catch (_: Exception) { /* skip functions we can't introspect */
            }
        }
        return sb.toString()
    }

Receiver type resolution (reflection-based + FIR fallback)

source — 97 lines, from line 1924
    private fun resolveReceiverType(receiverText: String, sourceText: String = ""): KClass<*>? {
        // Split chained access: "foo.bar.baz" -> ["foo", "bar", "baz"]
        val chain = splitDotChain(receiverText)
        if (chain.isEmpty()) return null

        // Resolve root element type
        val rootElement = chain[0]
        var currentValue: Any? = null
        var currentType: KClass<*>? = null
        val isCall = rootElement.contains("(")

        if (isCall) {
            val funcName = rootElement.substringBefore("(").trim()
            if (funcName.isEmpty()) return null

            // Constructor call: starts with uppercase
            if (funcName.first().isUpperCase()) {
                currentType = resolveTypeName(funcName)
            } else {
                // Function call: check runtime context first
                val funcInfo = context.functions[funcName]
                if (funcInfo != null) {
                    currentType = try {
                        funcInfo.function.returnType.classifier as? KClass<*>
                    } catch (_: Exception) {
                        null
                    }
                }
                // Fallback: FIR-resolved function return types
                if (currentType == null && sourceText.isNotEmpty()) {
                    val (_, funcTypes) = resolveTypesViaFir(sourceText)
                    currentType = funcTypes[funcName]
                }
            }
        } else {
            // Simple name: check runtime context first
            val varInfo = context.vars[rootElement]
            if (varInfo != null) {
                currentValue = varInfo.value
                currentType = currentValue?.let { it::class }
                    ?: runCatching { varInfo.descriptor.returnType.classifier as? KClass<*> }.getOrNull()
            }
            // Fallback: FIR-resolved variable types
            if (currentType == null && sourceText.isNotEmpty()) {
                val (varTypes, _) = resolveTypesViaFir(sourceText)
                currentType = varTypes[rootElement]
            }
        }

        if (currentType == null) return null

        // Walk the chain, resolving each step via reflection
        for (i in 1 until chain.size) {
            val step = chain[i]
            val memberName = step.removeSuffix("?").let {
                if (it.contains("(")) it.substringBefore("(") else it
            }
            if (memberName.isEmpty()) continue
            val isStepCall = step.contains("(")

            // Try property first
            val prop = currentType!!.memberProperties.firstOrNull { it.name == memberName }
            if (prop != null) {
                if (currentValue != null) {
                    prop.isAccessible = true
                    currentValue = runCatching { prop.getter.call(currentValue) }.getOrNull()
                    currentType = currentValue?.let { it::class }
                        ?: runCatching { prop.returnType.classifier as? KClass<*> }.getOrNull()
                } else {
                    currentType = runCatching { prop.returnType.classifier as? KClass<*> }.getOrNull()
                    currentValue = null
                }
                if (currentType == null) return null
                continue
            }

            // Try as a method call
            if (isStepCall) {
                val method = currentType.java.methods.firstOrNull { it.name == memberName }
                if (method != null) {
                    currentType = try {
                        method.returnType.kotlin
                    } catch (_: Exception) {
                        null
                    }
                    currentValue = null
                    if (currentType == null) return null
                    continue
                }
            }

            // No property or method found — can't resolve further
            return null
        }

        return currentType
    }

Resolve a type name to KClass

source — 63 lines, from line 2024
    private fun resolveTypeName(typeName: String): KClass<*>? {
        if (typeName.isEmpty()) return null
        val baseName = typeName.substringBefore('<').trim()
        if (baseName.isEmpty()) return null

        // Built-in Kotlin types
        when (baseName) {
            "Int", "kotlin.Int" -> return Int::class
            "Long", "kotlin.Long" -> return Long::class
            "Double", "kotlin.Double" -> return Double::class
            "Float", "kotlin.Float" -> return Float::class
            "Boolean", "kotlin.Boolean" -> return Boolean::class
            "String", "kotlin.String" -> return String::class
            "Char", "kotlin.Char" -> return Char::class
            "Byte", "kotlin.Byte" -> return Byte::class
            "Short", "kotlin.Short" -> return Short::class
            "Unit", "kotlin.Unit" -> return Unit::class
            "Any", "kotlin.Any" -> return Any::class
            "Nothing", "kotlin.Nothing" -> return Nothing::class
            "List", "MutableList",
            "kotlin.collections.List", "kotlin.collections.MutableList" -> return List::class

            "Set", "MutableSet",
            "kotlin.collections.Set", "kotlin.collections.MutableSet" -> return Set::class

            "Map", "MutableMap",
            "kotlin.collections.Map", "kotlin.collections.MutableMap" -> return Map::class

            "Array", "kotlin.Array" -> return Array::class
            "IntArray", "kotlin.IntArray" -> return IntArray::class
            "DoubleArray", "kotlin.DoubleArray" -> return DoubleArray::class
            "FloatArray", "kotlin.FloatArray" -> return FloatArray::class
            "LongArray", "kotlin.LongArray" -> return LongArray::class
            "BooleanArray", "kotlin.BooleanArray" -> return BooleanArray::class
            "Pair", "kotlin.Pair" -> return Pair::class
            "Triple", "kotlin.Triple" -> return Triple::class
            "Regex", "kotlin.text.Regex" -> return Regex::class
            "IntRange", "kotlin.ranges.IntRange" -> return IntRange::class
            "LongRange", "kotlin.ranges.LongRange" -> return LongRange::class
        }

        // Direct classloader lookup
        try {
            return classLoader?.loadClass(baseName)?.kotlin
        } catch (_: Exception) {
        }

        // Try common packages
        val commonPackages = listOf(
            "kotlin.", "kotlin.collections.", "kotlin.ranges.", "kotlin.text.",
            "java.lang.", "java.util.", "java.io.",
        ) + introImports.mapNotNull { statement ->
            statement.removePrefix("import ").takeIf { it.endsWith(".*") }?.removeSuffix("*")
        }
        for (pkg in commonPackages) {
            try {
                return classLoader?.loadClass("$pkg$baseName")?.kotlin
            } catch (_: Exception) {
            }
        }

        return null
    }

Add variable, function, and class declarations from the current source text that aren't yet in the runtime context, using FIR resolution for type info.

source — 115 lines, from line 2092
    private fun addFirSourceDeclarations(
        sourceText: String,
        nameFilter: (String) -> Boolean,
        results: MutableList<SourceCodeCompletionVariant>
    ) {
        val existingNames = results.map { it.text }.toSet()

        // Use FIR resolution to get typed declarations
        val (varTypes, funcReturnTypes) = resolveTypesViaFir(sourceText)

        // Also do a lightweight PSI scan for declaration names and structure (params, var/val)
        // that FIR resolution doesn't easily give us without more tree walking
        try {
            val factory = PsiFileFactory.getInstance(psiProject)
            val lang = Language.findLanguageByID("kotlin")!!
            val ktFile = factory.createFileFromText("_decl_scan_.kts", lang, sourceText) as KtFile

            // Variables
            ktFile.forEachDescendantOfType<KtProperty> { prop ->
                val name = prop.name ?: return@forEachDescendantOfType
                if (!nameFilter(name) || name in existingNames || name.contains("$")) return@forEachDescendantOfType

                val typeStr = varTypes[name]?.simpleName
                    ?: prop.typeReference?.text
                    ?: "?"
                val kind = if (prop.isVar) "var" else "val"
                results.add(
                    SourceCodeCompletionVariant(
                        text = name, displayText = "$kind $name", tail = ": $typeStr",
                        icon = kind,
                    )
                )
            }

            // Functions
            ktFile.forEachDescendantOfType<KtNamedFunction> { func ->
                val name = func.name ?: return@forEachDescendantOfType
                if (!nameFilter(name) || name in existingNames || name.contains("$") || name == "main") return@forEachDescendantOfType

                val params = func.valueParameters.joinToString(", ") { p ->
                    "${p.name ?: "p"}: ${p.typeReference?.text ?: "?"}"
                }
                val returnType = funcReturnTypes[name]?.simpleName
                    ?: func.typeReference?.text
                    ?: "?"
                results.add(
                    SourceCodeCompletionVariant(
                        text = name, displayText = "fun $name($params)", tail = ": $returnType",
                        icon = "method",
                    )
                )
            }

            // Classes
            ktFile.forEachDescendantOfType<KtClass> { cls ->
                val name = cls.name ?: return@forEachDescendantOfType
                if (!nameFilter(name) || name in existingNames || name.contains("$")) return@forEachDescendantOfType

                val kind = when {
                    cls.isData() -> "data class"
                    cls.isEnum() -> "enum class"
                    cls.isSealed() -> "sealed class"
                    cls.isInterface() -> "interface"
                    else -> "class"
                }
                results.add(
                    SourceCodeCompletionVariant(
                        text = name, displayText = "$kind $name", tail = "",
                        icon = "class",
                    )
                )
            }
        } catch (e: Exception) {
            lprintln("debug,kotlin", "FIR source declaration scan failed: ${e.message}")
        }
    }

    private fun splitDotChain(text: String): List<String> {
        // Split on dots, but respect parentheses: "foo.bar(x).baz" -> ["foo", "bar(x)", "baz"]
        val parts = mutableListOf<String>()
        var current = StringBuilder()
        var parenDepth = 0
        var bracketDepth = 0
        for (c in text) {
            when {
                c == '(' -> {
                    parenDepth++; current.append(c)
                }

                c == ')' -> {
                    parenDepth--; current.append(c)
                }

                c == '[' -> {
                    bracketDepth++; current.append(c)
                }

                c == ']' -> {
                    bracketDepth--; current.append(c)
                }

                c == '.' && parenDepth == 0 && bracketDepth == 0 -> {
                    if (current.isNotEmpty()) parts.add(current.toString())
                    current = StringBuilder()
                }

                c == '?' && parenDepth == 0 -> { /* skip safe-call ? */
                }

                else -> current.append(c)
            }
        }
        if (current.isNotEmpty()) parts.add(current.toString())
        return parts
    }

Member enumeration from a KClass (reflection-based)

Add class names from the classpath that match prefix, with constructor parameter info. This makes typing "FLine" or "FLin" produce constructor suggestions.

source — 45 lines, from line 2214
    private fun addClasspathClasses(prefix: String, results: MutableList<SourceCodeCompletionVariant>) {
        val existingNames = results.map { it.text }.toHashSet()
        val added = mutableSetOf<String>()
        val maxResults = 40

        val classCache = afield.runtime.kotlin.cache // classpath name cache from importHelp.kt
        for (entry in classCache.entries) {
            if (added.size >= maxResults) break
            val simpleName = entry.key
            val fqNames: List<String> = entry.value
            if (!simpleName.startsWith(prefix, ignoreCase = true)) continue
            if (simpleName in existingNames || !added.add(simpleName)) continue
            if (simpleName.contains("$")) continue
            // Skip lowercase-starting names (packages, not classes)
            if (simpleName.firstOrNull()?.isUpperCase() != true) continue

            // Try to load the class and get constructor info
            var fqName: String? = null
            for (n in fqNames) {
                if (afield.runtime.kotlin.isImportableClassName(n)) {
                    fqName = n; break
                }
            }
            if (fqName == null) continue
            val constructorInfo = try {
                val klass = classLoader?.loadClass(fqName)?.kotlin ?: Class.forName(fqName).kotlin
                val primary = klass.primaryConstructor ?: klass.constructors.firstOrNull()
                if (primary != null) {
                    val valueParams = primary.parameters.filter { it.kind == KParameter.Kind.VALUE }
                    val paramsStr = formatKotlinParams(valueParams)
                    paramsStr
                } else "()"
            } catch (_: Exception) {
                null
            }

            val displayText = if (constructorInfo != null) "$simpleName$constructorInfo" else simpleName
            results.add(
                SourceCodeCompletionVariant(
                    text = simpleName, displayText = displayText, tail = fqName,
                    icon = "class",
                )
            )
        }
    }

Lazy index of Kotlin file-level classes (ending in "Kt") per package from the classpath. These classes contain top-level functions and extension functions.

    private val kotlinFileClassesByPackage: Map<String, List<String>> by lazy {
        val result = mutableMapOf<String, MutableList<String>>()
        for ((_, fqNames) in afield.runtime.kotlin.cache) {
            for (fqName in fqNames) {
                val simpleName = fqName.substringAfterLast('.')
                if (simpleName.endsWith("Kt") && !simpleName.contains('$')) {
                    val pkg = fqName.substringBeforeLast('.', "")
                    result.getOrPut(pkg) { mutableListOf() }.add(fqName)
                }
            }
        }
        result
    }

Add top-level functions and extension functions on implicit receiver types from the imported packages (parsed from the intro preamble). This enables completion of functions like lines(), display(), etc. that are accessible as top-level calls in the REPL due to implicit receiver scope.

source — 526 lines, from line 2284
    private fun addImportedTopLevelFunctions(
        prefix: String,
        results: MutableList<SourceCodeCompletionVariant>,
        existingNames: Set<String>
    ) {
        val nameFilter: (String) -> Boolean = { name ->
            prefix.isEmpty() || name.startsWith(prefix, ignoreCase = true)
        }
        val seen = existingNames.toMutableSet()

        // Get implicit receiver classes to identify extension functions callable as top-level
        val implicitReceiverClasses = implicitReceivers.mapNotNull { kotlinType ->
            try {
                kotlinType.fromClass?.java
            } catch (_: Exception) {
                null
            }
        }

        // Parse star-import packages from intro
        val starImportPackages = intro.lines()
            .map { it.trim() }
            .filter { it.startsWith("import ") && it.endsWith(".*") }
            .map { it.removePrefix("import ").removeSuffix(".*").trim() }

        for (pkg in starImportPackages) {
            val fileClasses = kotlinFileClassesByPackage[pkg] ?: continue
            for (fqClassName in fileClasses) {
                val javaClass = try {
                    classLoader?.loadClass(fqClassName) ?: continue
                } catch (_: Exception) {
                    continue
                }

                for (method in javaClass.declaredMethods) {
                    if (!Modifier.isPublic(method.modifiers) || !Modifier.isStatic(method.modifiers)) continue
                    val name = method.name
                    if (name.contains('$') || name.contains('-')) continue // skip internal/mangled names
                    if (!nameFilter(name) || !seen.add(name)) continue

                    val kfunc = try {
                        method.kotlinFunction
                    } catch (_: Exception) {
                        null
                    }

                    if (kfunc != null) {
                        // Use Kotlin reflection — reliable extension receiver detection
                        val extensionReceiver =
                            kfunc.parameters.firstOrNull { it.kind == KParameter.Kind.EXTENSION_RECEIVER }
                        if (extensionReceiver != null) {
                            // Extension function — only include if receiver matches an implicit receiver
                            val receiverClass = try {
                                extensionReceiver.type.classifier as? KClass<*>
                            } catch (_: Exception) {
                                null
                            }
                            val matchesImplicit = receiverClass != null &&
                                    implicitReceiverClasses.any { it.isAssignableFrom(receiverClass.java) }
                            if (!matchesImplicit) continue  // skip extension on unrelated type

                            val valueParams = kfunc.parameters.filter { it.kind == KParameter.Kind.VALUE }
                            val paramsStr = formatKotlinParams(valueParams)
                            val returnTypeStr = try {
                                shortenType(kfunc.returnType.toString())
                            } catch (_: Exception) {
                                "Any?"
                            }
                            val variant = SourceCodeCompletionVariant(
                                text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                                icon = "method",
                            )
                            // Embed KDoc for later use in documentation building
                            val qualifiedName = receiverClass.simpleName?.let { "$it.$name" } ?: name
                            val kdoc = try {
                                KotlinSourceIndex.getKDocForSymbol(qualifiedName) ?: KotlinSourceIndex.getKDocForSymbol(
                                    name
                                )
                            } catch (_: Exception) {
                                null
                            }
                            if (kdoc != null) variant.extraInfo = mapOf("kdoc" to kdoc)
                            results.add(variant)
                        } else {
                            // Plain top-level function
                            val valueParams = kfunc.parameters.filter { it.kind == KParameter.Kind.VALUE }
                            val paramsStr = formatKotlinParams(valueParams)
                            val returnTypeStr = try {
                                shortenType(kfunc.returnType.toString())
                            } catch (_: Exception) {
                                "Any?"
                            }
                            val variant = SourceCodeCompletionVariant(
                                text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                                icon = "method",
                            )
                            val kdoc = try {
                                KotlinSourceIndex.getKDocForSymbol(name)
                            } catch (_: Exception) {
                                null
                            }
                            if (kdoc != null) variant.extraInfo = mapOf("kdoc" to kdoc)
                            results.add(variant)
                        }
                    } else {
                        // Java fallback — can't detect extension receivers, include as top-level
                        val params = method.parameters
                        val paramsStr = formatJavaParams(params.toList())
                        val returnTypeStr =
                            shortenType(method.returnType.kotlin.simpleName ?: method.returnType.simpleName)
                        val variant = SourceCodeCompletionVariant(
                            text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                            icon = "method",
                        )
                        val kdoc = try {
                            KotlinSourceIndex.getKDocForSymbol(name)
                        } catch (_: Exception) {
                            null
                        }
                        if (kdoc != null) variant.extraInfo = mapOf("kdoc" to kdoc)
                        results.add(variant)
                    }
                }
            }
        }
    }

    private fun formatJavaParams(params: List<java.lang.reflect.Parameter>): String {
        if (params.isEmpty()) return "()"
        return "(${
            params.joinToString(", ") { p ->
                val name = p.name
                val type = shortenType(p.type.kotlin.simpleName ?: p.type.simpleName)
                if (name.matches(Regex("^(arg|p)\\d+$"))) type else "$name: $type"
            }
        })"
    }

    private fun addMembersFromClass(
        klass: KClass<*>,
        prefix: String,
        results: MutableList<SourceCodeCompletionVariant>
    ) {
        val nameFilter: (String) -> Boolean = { name ->
            prefix.isEmpty() || name.startsWith(prefix, ignoreCase = true)
        }
        val seen = mutableSetOf<String>()
        val receiverName = klass.simpleName ?: "Unknown"

        fun tagExtra(variant: SourceCodeCompletionVariant, sortGroup: Int): SourceCodeCompletionVariant {
            variant.extraInfo = mapOf(
                "sortGroup" to sortGroup,
                "receiverType" to receiverName,
                "receiverQualifiedName" to (klass.qualifiedName ?: receiverName)
            )
            return variant
        }

        // Properties (including inherited)
        try {
            for (prop in klass.memberProperties) {
                val name = prop.name
                if (!nameFilter(name) || name.contains("$") || !seen.add(name)) continue
                val typeStr = try {
                    shortenType(prop.returnType.toString())
                } catch (_: Exception) {
                    "Any?"
                }
                val kind = if (prop is KMutableProperty<*>) "var" else "val"
                val deprecated = if (prop.annotations.any { it is Deprecated }) DeprecationLevel.WARNING else null
                results.add(
                    tagExtra(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "$kind $name", tail = ": $typeStr",
                            icon = kind, deprecationLevel = deprecated,
                        ), 0
                    )
                )
            }
        } catch (_: Exception) {
        }

        // Extension properties from kotlin stdlib
        addCommonExtensions(klass, prefix, results, seen)

        // Functions — try Kotlin reflection first (gives proper parameter names for Kotlin-compiled classes),
        // then fall back to Java reflection for anything missed
        val kotlinFuncsSeen = mutableSetOf<String>()
        try {
            for (func in klass.members) {
                if (func !is KFunction<*>) continue
                val name = func.name
                if (!nameFilter(name) || name.contains("$")) continue
                if (name.startsWith("get") && func.parameters.count { it.kind == KParameter.Kind.VALUE } == 0 && name.length > 3) continue
                if (name.startsWith("set") && func.parameters.count { it.kind == KParameter.Kind.VALUE } == 1 && name.length > 3) continue
                if (name in setOf("hashCode", "equals", "toString", "notify", "notifyAll", "wait", "getClass")) continue
                if (!seen.add(name)) continue
                kotlinFuncsSeen.add(name)

                val valueParams = func.parameters.filter { it.kind == KParameter.Kind.VALUE }
                val paramsStr = formatKotlinParams(valueParams)
                val returnTypeStr = try {
                    shortenType(func.returnType.toString())
                } catch (_: Exception) {
                    "Any?"
                }
                val deprecated = if (func.annotations.any { it is Deprecated }) DeprecationLevel.WARNING else null

                results.add(
                    tagExtra(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                            icon = "method", deprecationLevel = deprecated,
                        ), 0
                    )
                )
            }
        } catch (e: Exception) {
            lprintln("debug,kotlin", "Kotlin reflection memberFunctions failed for ${klass.simpleName}: ${e.message}")
        }
        // Fall back to Java reflection for any functions not covered by Kotlin reflection
        try {
            for (method in klass.java.methods) {
                val name = method.name
                if (!nameFilter(name) || name.contains("$") || name in kotlinFuncsSeen || !seen.add(name)) continue
                if (name.startsWith("get") && method.parameterCount == 0 && name.length > 3) continue
                if (name.startsWith("set") && method.parameterCount == 1 && name.length > 3) continue
                if (name in setOf("hashCode", "equals", "toString", "notify", "notifyAll", "wait", "getClass")) continue

                val paramsStr = formatJavaMethodParams(method)
                val returnTypeStr = formatMethodReturnType(method)
                val deprecated = isDeprecated(method)

                results.add(
                    tagExtra(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                            icon = "method", deprecationLevel = deprecated,
                        ), 0
                    )
                )
            }
        } catch (_: Exception) {
        }

        // Also add toString/hashCode/equals explicitly since they're commonly used
        if (!seen.contains("toString") && nameFilter("toString")) {
            results.add(
                tagExtra(
                    SourceCodeCompletionVariant(
                        text = "toString",
                        displayText = "fun toString()",
                        tail = ": String",
                        icon = "method"
                    ), 3
                )
            )
            seen.add("toString")
        }
        if (!seen.contains("hashCode") && nameFilter("hashCode")) {
            results.add(
                tagExtra(
                    SourceCodeCompletionVariant(
                        text = "hashCode",
                        displayText = "fun hashCode()",
                        tail = ": Int",
                        icon = "method"
                    ), 3
                )
            )
            seen.add("hashCode")
        }
        if (!seen.contains("equals") && nameFilter("equals")) {
            results.add(
                tagExtra(
                    SourceCodeCompletionVariant(
                        text = "equals",
                        displayText = "fun equals(other: Any?)",
                        tail = ": Boolean",
                        icon = "method"
                    ), 3
                )
            )
            seen.add("equals")
        }
    }

    private fun addCommonExtensions(
        klass: KClass<*>,
        prefix: String,
        results: MutableList<SourceCodeCompletionVariant>,
        seen: MutableSet<String>
    ) {
        val nameFilter: (String) -> Boolean = { prefix.isEmpty() || it.startsWith(prefix, ignoreCase = true) }

        // Collection-like extensions
        val isIterable = Iterable::class.java.isAssignableFrom(klass.java)
        val isCollection = Collection::class.java.isAssignableFrom(klass.java)
        val isList = List::class.java.isAssignableFrom(klass.java)
        val isMap = Map::class.java.isAssignableFrom(klass.java)
        val isArray = klass.java.isArray
        val isString = klass == String::class
        val isComparable = Comparable::class.java.isAssignableFrom(klass.java)

        val receiverName = klass.simpleName ?: "Unknown"

        fun addExt(name: String, params: String, returnType: String, icon: String = "method", sortGroup: Int = 1) {
            if (nameFilter(name) && seen.add(name)) {
                val display = if (icon == "val" || icon == "var") "$icon $name" else "fun $name$params"
                val tail = ": $returnType"
                val variant = SourceCodeCompletionVariant(text = name, displayText = display, tail = tail, icon = icon)
                variant.extraInfo = mapOf(
                    "sortGroup" to sortGroup,
                    "receiverType" to receiverName,
                    "receiverQualifiedName" to (klass.qualifiedName ?: receiverName)
                )
                results.add(variant)
            }
        }

        if (isIterable || isArray) {
            addExt("filter", "(predicate: (T) -> Boolean)", "List<T>")
            addExt("map", "(transform: (T) -> R)", "List<R>")
            addExt("flatMap", "(transform: (T) -> Iterable<R>)", "List<R>")
            addExt("forEach", "(action: (T) -> Unit)", "Unit")
            addExt("forEachIndexed", "(action: (Int, T) -> Unit)", "Unit")
            addExt("any", "(predicate: (T) -> Boolean)", "Boolean")
            addExt("all", "(predicate: (T) -> Boolean)", "Boolean")
            addExt("none", "(predicate: (T) -> Boolean)", "Boolean")
            addExt("first", "()", "T")
            addExt("firstOrNull", "()", "T?")
            addExt("last", "()", "T")
            addExt("lastOrNull", "()", "T?")
            addExt("find", "(predicate: (T) -> Boolean)", "T?")
            addExt("count", "()", "Int")
            addExt("sortedBy", "(selector: (T) -> R)", "List<T>")
            addExt("sortedByDescending", "(selector: (T) -> R)", "List<T>")
            addExt("groupBy", "(keySelector: (T) -> K)", "Map<K, List<T>>")
            addExt("associate", "(transform: (T) -> Pair<K, V>)", "Map<K, V>")
            addExt("associateBy", "(keySelector: (T) -> K)", "Map<K, T>")
            addExt("toList", "()", "List<T>")
            addExt("toSet", "()", "Set<T>")
            addExt("toMutableList", "()", "MutableList<T>")
            addExt("joinToString", "(separator: String)", "String")
            addExt("take", "(n: Int)", "List<T>")
            addExt("drop", "(n: Int)", "List<T>")
            addExt("zip", "(other: Iterable<R>)", "List<Pair<T, R>>")
            addExt("distinct", "()", "List<T>")
            addExt("distinctBy", "(selector: (T) -> K)", "List<T>")
            addExt("flatten", "()", "List<T>")
            addExt("reversed", "()", "List<T>")
            addExt("shuffled", "()", "List<T>")
            addExt("maxByOrNull", "(selector: (T) -> R)", "T?")
            addExt("minByOrNull", "(selector: (T) -> R)", "T?")
            addExt("sumOf", "(selector: (T) -> Int)", "Int")
            addExt("withIndex", "()", "Iterable<IndexedValue<T>>")
            addExt("mapIndexed", "(transform: (Int, T) -> R)", "List<R>")
            addExt("filterNot", "(predicate: (T) -> Boolean)", "List<T>")
            addExt("filterNotNull", "()", "List<T>")
            addExt("partition", "(predicate: (T) -> Boolean)", "Pair<List<T>, List<T>>")
            addExt("chunked", "(size: Int)", "List<List<T>>")
            addExt("windowed", "(size: Int)", "List<List<T>>")
        }
        if (isList || isArray) {
            addExt("indices", "", "IntRange", "val")
            addExt("lastIndex", "", "Int", "val")
            addExt("getOrNull", "(index: Int)", "T?")
            addExt("getOrElse", "(index: Int, defaultValue: (Int) -> T)", "T")
        }
        if (isCollection) {
            addExt("isNotEmpty", "()", "Boolean")
            addExt("isNullOrEmpty", "()", "Boolean")
            addExt("orEmpty", "()", "Collection<T>")
        }
        if (isMap) {
            addExt("getOrDefault", "(key: K, defaultValue: V)", "V")
            addExt("getOrElse", "(key: K, defaultValue: () -> V)", "V")
            addExt("mapValues", "(transform: (Map.Entry<K, V>) -> R)", "Map<K, R>")
            addExt("mapKeys", "(transform: (Map.Entry<K, V>) -> R)", "Map<R, V>")
            addExt("filterKeys", "(predicate: (K) -> Boolean)", "Map<K, V>")
            addExt("filterValues", "(predicate: (V) -> Boolean)", "Map<K, V>")
            addExt("toList", "()", "List<Pair<K, V>>")
            addExt("toMutableMap", "()", "MutableMap<K, V>")
        }
        if (isString) {
            addExt("lines", "()", "List<String>")
            addExt("trim", "()", "String")
            addExt("trimEnd", "()", "String")
            addExt("trimStart", "()", "String")
            addExt("split", "(delimiter: String)", "List<String>")
            addExt("substringBefore", "(delimiter: String)", "String")
            addExt("substringAfter", "(delimiter: String)", "String")
            addExt("removePrefix", "(prefix: String)", "String")
            addExt("removeSuffix", "(suffix: String)", "String")
            addExt("toIntOrNull", "()", "Int?")
            addExt("toDoubleOrNull", "()", "Double?")
            addExt("isBlank", "()", "Boolean")
            addExt("isNotBlank", "()", "Boolean")
            addExt("isNullOrBlank", "()", "Boolean")
            addExt("repeat", "(n: Int)", "String")
            addExt("padStart", "(length: Int)", "String")
            addExt("padEnd", "(length: Int)", "String")
            addExt("lowercase", "()", "String")
            addExt("uppercase", "()", "String")
            addExt("capitalize", "()", "String")
            addExt("decapitalize", "()", "String")
            addExt("toRegex", "()", "Regex")
        }
        if (isComparable) {
            addExt("coerceIn", "(min: T, max: T)", "T")
            addExt("coerceAtLeast", "(minimumValue: T)", "T")
            addExt("coerceAtMost", "(maximumValue: T)", "T")
        }

        // Universal extensions (available on Any/Any?) — sort after type-specific extensions
        addExt("let", "(block: (T) -> R)", "R", sortGroup = 2)
        addExt("also", "(block: (T) -> Unit)", "T", sortGroup = 2)
        addExt("apply", "(block: T.() -> Unit)", "T", sortGroup = 2)
        addExt("run", "(block: T.() -> R)", "R", sortGroup = 2)
        addExt("takeIf", "(predicate: (T) -> Boolean)", "T?", sortGroup = 2)
        addExt("takeUnless", "(predicate: (T) -> Boolean)", "T?", sortGroup = 2)
        addExt("to", "(that: B)", "Pair<A, B>", sortGroup = 2)
    }

    private fun addStaticMembersIfPackageOrClass(
        receiverText: String,
        prefix: String,
        results: MutableList<SourceCodeCompletionVariant>
    ) {
        // Try to resolve as a class name (supports short names like "Math" → java.lang.Math)
        val klass = resolveTypeName(receiverText) ?: return

        val nameFilter: (String) -> Boolean = { name ->
            prefix.isEmpty() || name.startsWith(prefix, ignoreCase = true)
        }
        val seen = mutableSetOf<String>()

        // Add companion object members (Kotlin classes)
        try {
            val companion = klass.companionObject
            if (companion != null) {
                for (prop in companion.memberProperties) {
                    val name = prop.name
                    if (!nameFilter(name) || name.contains("$") || !seen.add(name)) continue
                    val typeStr = try {
                        shortenType(prop.returnType.toString())
                    } catch (_: Exception) {
                        "Any?"
                    }
                    val kind = if (prop is KMutableProperty<*>) "var" else "val"
                    val deprecated = if (prop.annotations.any { it is Deprecated }) DeprecationLevel.WARNING else null
                    results.add(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "$kind $name", tail = ": $typeStr",
                            icon = kind, deprecationLevel = deprecated,
                        )
                    )
                }
                for (func in companion.declaredFunctions) {
                    val name = func.name
                    if (!nameFilter(name) || name.contains("$") || !seen.add(name)) continue
                    val paramsStr = formatKotlinParams(func.parameters.filter { it.kind == KParameter.Kind.VALUE })
                    val returnTypeStr = try {
                        shortenType(func.returnType.toString())
                    } catch (_: Exception) {
                        "Any?"
                    }
                    val deprecated = if (func.annotations.any { it is Deprecated }) DeprecationLevel.WARNING else null
                    results.add(
                        SourceCodeCompletionVariant(
                            text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                            icon = "method", deprecationLevel = deprecated,
                        )
                    )
                }
            }
        } catch (_: Exception) {
        }

        // Add Java static methods and fields (e.g., Math.random(), Math.PI)
        try {
            val javaClass = klass.java
            for (method in javaClass.methods) {
                if (!Modifier.isStatic(method.modifiers)) continue
                val name = method.name
                if (!nameFilter(name) || name.contains("$") || !seen.add(name)) continue
                if (name in setOf("getClass", "wait", "notify", "notifyAll")) continue
                val paramsStr = formatJavaMethodParams(method)
                val returnTypeStr = formatMethodReturnType(method)
                val deprecated = isDeprecated(method)
                results.add(
                    SourceCodeCompletionVariant(
                        text = name, displayText = "fun $name$paramsStr", tail = ": $returnTypeStr",
                        icon = "method", deprecationLevel = deprecated,
                    )
                )
            }
            for (field in javaClass.fields) {
                if (!Modifier.isStatic(field.modifiers)) continue
                val name = field.name
                if (!nameFilter(name) || name.contains("$") || !seen.add(name)) continue
                val typeStr = try {
                    shortenType(field.type.kotlin.simpleName ?: field.type.simpleName)
                } catch (_: Exception) {
                    "Any?"
                }
                val kind = if (Modifier.isFinal(field.modifiers)) "val" else "var"
                results.add(
                    SourceCodeCompletionVariant(
                        text = name, displayText = "$kind $name", tail = ": $typeStr",
                        icon = kind,
                    )
                )
            }
        } catch (_: Exception) {
        }
    }

    private fun extractPrefix(s: String, cursor: Int): String {
        if (cursor <= 0 || cursor > s.length) return ""
        var start = cursor - 1
        while (start >= 0 && (s[start].isLetterOrDigit() || s[start] == '_')) {
            start--
        }
        return s.substring(start + 1, cursor)
    }

override fun completeAndWait(s: String, cursor: Int): CompletionResult

    override fun completeAndWait(s: String, cursor: Int): CompletionResult {
        return runBlocking { complete(s, cursor) }
    }

override fun applyMacros(box: Box, s: String, skipMacros: Set<String>, previewOnly: Boolean): String

body — 173 lines, from line 2815
    override fun applyMacros(box: Box, s: String, skipMacros: Set<String>, previewOnly: Boolean): String {
        // Pattern-rewrite layer: expand user-authored `rewrite { … }` rules (and blank the rule
        // blocks out of the source) before the builtin identifier macros run below, so a rule's
        // `to {}` output can still trigger the builtin macros. Pure PSI. See [PatternRewrite].
        // Procedural `unquote {}`: compile+evaluate the snippet in this box's REPL context and
        // return its String result. Blocking, fail-soft, and side-effect-free (updateContext=false);
        // disabled in editor-preview expansions where running user code is unwanted.
        val evalUnquote: ((String) -> String?)? = if (previewOnly) null else { snippet ->
            try {
                // Compile+eval the snippet exactly as ordinary box code is run — with the box as the
                // implicit receiver (box engines are built with one `Box` receiver, so snippets
                // expect one at eval; passing none fails with "wrong number of arguments"). The
                // snippet binds its metavars as `String` vals and runs against the box's REPL state
                // (stdlib + top-level decls + box-scoped members). updateContext=false keeps it
                // side-effect-free.
                val result = compileSnippetAndWait(snippet, implicits = listOf(box)) { diags ->
                    lprintln("warning,macros", "rewrite unquote {} did not compile: ${diags.joinToString { it.message }}")
                }?.evalAndWait(updateContext = false, implicits = listOf(box))
                when (result) {
                    is ResultValue.Value -> result.value?.toString()
                    is ResultValue.Error -> {
                        lprintln("warning,macros", "rewrite unquote {} threw at expansion time: ${result.error.message}")
                        null
                    }
                    else -> null
                }
            } catch (t: Throwable) {
                lprintln("warning,macros", "rewrite unquote {} eval failed: ${t.message}")
                null
            }
        }
        // Ambient rules: `rewrite {}` rules declared in a box apply to that box's own code AND every
        // descendant box's code. We read each box's source `code` directly (so a rule is ambient the
        // moment it is *written* — the box need not have run). searchUpwards visits this box first,
        // then all ancestors (nearest first). We INCLUDE this box so its own rules also apply to a
        // *fragment* run from it (where `s` is only the selected snippet and does not itself contain
        // the rule block). For a full-box run `s` already carries the rule (collected + blanked); the
        // duplicate is harmless — a rewrite makes its target no longer match, and guards fail closed.
        // The `"rewrite" in code` pre-filter skips parsing code that declares no rules.
        val ambientRuleSources = mutableListOf<String>()
        searchUpwards(box, "code") { code ->
            if (code != null && "rewrite" in code) ambientRuleSources += code
        }

        val expansion = PatternRewrite.expand(
            s,
            parse = { txt ->
                PsiFileFactory.getInstance(psiProject)
                    .createFileFromText(Language.findLanguageByID("kotlin")!!, txt) as KtFile
            },
            evalUnquote = evalUnquote,
            ambientRuleSources = ambientRuleSources,
            onDiagnostic = { msg -> lprintln("warning,macros", msg) },
        )
        if (!previewOnly) {
            // `provide {}` clauses → stably-keyed, deduplicating preamble segments (see the
            // /*prefix*/ "stack mark" idempotency mechanism); routed through the existing merge.
            expansion.preambleBlocks.forEach { applyInsertToPreamble(box, Insert(preambleAfterImports = it)) }
            // `ai("…")` clauses → cached AI generation into the box attic (once per stable key).
            expansion.aiRequests.forEach { box.resolvePatternRewriteAi(it) }
        }
        var rewritten = expansion.text
        val needsRerun = false
        lblock("debug,kotlin", "rewriting AST analysis") {
            do {
                val factory = PsiFileFactory.getInstance(psiProject)
                val lang = Language.findLanguageByID("kotlin")!!
                val instance = factory.createFileFromText(lang, rewritten)

                val edits = mutableListOf<Pair<Pair<Int, Int>, String>>()

                instance.forEachDescendantOfType<PsiElement> { expression ->
                    when (expression) {
                        is KtCallExpression -> {
                            expression.children.forEach {
                                when (it) {
                                    is KtNameReferenceExpression -> {
                                        if (it.text in skipMacros) return@forEach

                                        lprintln(
                                            "debug,kotlin",
                                            "child (named): ${it} ${it.text} ${it.javaClass} ${it.startOffset} -> ${it.endOffset} ${it.reference}"
                                        )

                                        var ns = it.nextSibling
                                        val t = mutableListOf<String>()
                                        while (ns != null) {
                                            t.add(ns.text)
                                            ns = ns.nextSibling
                                        }

                                        val containingFunction =
                                            PsiTreeUtil.getParentOfType(expression, KtNamedFunction::class.java)
                                        val containingFunctionName = containingFunction?.name
                                        val valueParameterList = containingFunction?.valueParameterList?.text
                                        val returnParameterText = containingFunction?.typeReference?.text

                                        val containingFunctionInfo =
                                            if (containingFunctionName != null && valueParameterList != null && returnParameterText != null)
                                                ContainingFunction(
                                                    containingFunctionName,
                                                    valueParameterList,
                                                    returnParameterText
                                                )
                                            else null

                                        val e = MacroRegistry.rewrite(box, it.text, t, containingFunctionInfo)

                                        if (e != null) {
                                            if (!previewOnly) {
                                                if (e.start.length > 0) {
                                                    edits.add((0 to 0) to e.preamble)
                                                }
                                                if (e.afterImports.length > 0) {
                                                    val a = findLastImport(rewritten)
                                                    edits.add((a to a) to e.afterImports)
                                                }
                                            }

                                            val replacement =
                                                if (expression.parent is KtBlockExpression || expression.parent is KtScriptInitializer)
                                                    e.standaloneHere ?: e.here
                                                else
                                                    e.here
                                            edits.add((expression.startOffset to expression.endOffset) to replacement)

                                            if (!previewOnly) {
                                                if (e.preamble.isNotBlank() || e.preambleAfterImports.isNotBlank()) {
                                                    applyInsertToPreamble(box, e)
                                                }
                                            }
                                        }
                                    }

                                    else -> {
                                    }
                                }
                            }
                        }

                        is KtNameReferenceExpression -> {
                        }
                    }
                }
                while (edits.size > 0) {
                    lprintln("debug,kotlin", "applying edits")
                    val e = edits.pop()
                    rewritten = rewritten.replaceRange(e.first.first, e.first.second, e.second)
                    val shrink = (e.first.second - e.first.first) - e.second.length
                    edits.forEachIndexed { index, it ->
                        val a = if (it.first.first > e.first.first) it.first.first - shrink else it.first.first
                        val b = if (it.first.second > e.first.first) it.first.second - shrink else it.first.second
                        edits[index] = (a to b) to it.second
                    }
                }
            } while (needsRerun)

            lprintln("debug,kotlin", "final string is:\n$rewritten")
        }
        // Surface the exact post-expansion source handed to the compiler (execute path only, and
        // only when macro expansion actually changed the code), to aid debugging rewrite/unquote/
        // builtin-macro output. Toggle with -Dafield.macros.logExpanded=false.
        if (!previewOnly && KotlinEngineConfig.logExpandedMacros && rewritten != s) {
            lprintln(
                "info,macros",
                "macro-expanded source for box '${box.name.ifBlank { box.id }}' (sent to the Kotlin compiler):\n" +
                        "┌────────────────────────────────────────────────────────────\n" +
                        rewritten.lines().joinToString("\n") { "│ $it" } +
                        "\n└────────────────────────────────────────────────────────────"
            )
        }
        return rewritten
    }

override fun findLastImport(s: String): Int

    override fun findLastImport(s: String): Int {
        if (s.contains("import ")) {
            val m = s.findLastAnyOf(listOf("import "))!!.first
            val m2 = s.findAnyOf(listOf("\n"), startIndex = m)
            if (m2 == null) return s.length
            else return m2.first + 1
        } else return 0
    }

override fun parseAndRewrite(s: String, rules: List<(PsiElement, String) -> String>): String {

    override fun parseAndRewrite(s: String, rules: List<(PsiElement, String) -> String>): String {
        return s;
    }

override var execNumber = 0

override suspend fun exec

    override suspend fun exec(
        s: String,
        onCompileError: (List<ScriptDiagnostic>) -> Unit,
        onEvalError: (List<ScriptDiagnostic>) -> Unit,
        onEvalException: (Throwable) -> Unit,
        implicits: List<Any>,
    ): ResultValue? {
        // Never evaluate before the stdlib intro has (see [bootstrapReady]). The bootstrap
        // itself uses [execInternal] to avoid waiting on itself.
        LatencySpans.time("engine.bootstrapWait", box = (associatedBox as? Box)?.id) { bootstrapReady.await() }
        return execInternal(s, onCompileError, onEvalError, onEvalException, implicits)
    }

override suspend fun bootstrap

Run the stdlib bootstrap (intro) without waiting on the bootstrapReady gate, since this call is what opens it, and release it afterwards whatever happened, so nothing waits forever.

    override suspend fun bootstrap(
        s: String,
        implicits: List<Any>,
        onCompileError: (List<ScriptDiagnostic>) -> Unit,
        onEvalError: (List<ScriptDiagnostic>) -> Unit,
        onEvalException: (Throwable) -> Unit,
    ): ResultValue? {
        try {
            return execInternal(s, onCompileError, onEvalError, onEvalException, implicits)
        } finally {
            bootstrapReady.complete(Unit)
        }
    }

override fun armBootstrapGate()

    override fun armBootstrapGate() {
        bootstrapReady = CompletableDeferred()
    }

override fun releaseBootstrapGate()

body — 51 lines, from line 3039
    override fun releaseBootstrapGate() {
        bootstrapReady.complete(Unit)
    }

    private suspend fun execInternal(
        s: String,
        onCompileError: (List<ScriptDiagnostic>) -> Unit,
        onEvalError: (List<ScriptDiagnostic>) -> Unit,
        onEvalException: (Throwable) -> Unit,
        implicits: List<Any>,
    ): ResultValue? {

        return when (val r = compileOrReuse(s, implicits)) {
            is ResultWithDiagnostics.Success -> {
                when (val r2 = evaluateInternal(r.value, updateContext = true, implicits = implicits)) {
                    is ResultWithDiagnostics.Success -> {
                        when (val e = r2.value.get().result) {
                            is ResultValue.Error -> {
                                lprintln("error,kotlin", "Evaluation Error ${e.error} / ${e.error.javaClass}")
                                onEvalException(translateKotlinEvalException(e.error, s))
                                e
                            }

                            is ResultValue.NotEvaluated -> {
                                lprintln("warning,kotlin", "not evaluated for ${r2.reports}")
                                onEvalError(r2.reports)
                                e
                            }

                            else -> {
                                execNumber++
                                e
                            }
                        }
                    }

                    is ResultWithDiagnostics.Failure -> {
                        lprintln("error,kotlin", "evaluator error ${r2}")
                        onEvalError(r2.reports)
                        null
                    }
                }
            }

            is ResultWithDiagnostics.Failure -> {
                lprintln("error,kotlin", "compiler error ${r}")
                onCompileError(r.reports)
                null
            }
        }
    }

override fun execAndWait

    override fun execAndWait(
        s: String,
        onCompileError: (List<ScriptDiagnostic>) -> Unit,
        onEvalError: (List<ScriptDiagnostic>) -> Unit,
        onEvalException: (Throwable) -> Unit,
        implicits: List<Any>
    ): ResultValue? {
        return runBlocking {
            exec(s, onCompileError, onEvalError, onEvalException, implicits)
        }
    }

inner class K2PreparedSnippet internal constructor

K2 implementation of PreparedSnippet; see that interface for the evaluation contract.

    inner class K2PreparedSnippet internal constructor(
        private val compiled: LinkedSnippet<out CompiledScript>,
        private val defaultImplicits: List<Any>
    ) : afield.runtime.kotlin.PreparedSnippet {

override suspend fun eval

        override suspend fun eval(
            updateContext: Boolean,
            implicits: List<Any>,
            onEvalError: (List<ScriptDiagnostic>) -> Unit,
            onEvalException: (Throwable) -> Unit
        ): ResultValue? {
            return when (val r = evaluate(compiled, updateContext, implicits.ifEmpty { defaultImplicits })) {
                is ResultWithDiagnostics.Success -> {
                    when (val e = r.value.get().result) {
                        is ResultValue.Error -> {
                            lprintln("error,kotlin", "Evaluation Error ${e.error} / ${e.error.javaClass}")
                            onEvalException(translateKotlinEvalException(e.error))
                            e
                        }

                        is ResultValue.NotEvaluated -> {
                            lprintln("warning,kotlin", "not evaluated for ${r.reports}")
                            onEvalError(r.reports)
                            e
                        }

                        else -> {
                            execNumber++
                            e
                        }
                    }
                }

                is ResultWithDiagnostics.Failure -> {
                    lprintln("error,kotlin", "evaluator error ${r}")
                    onEvalError(r.reports)
                    null
                }
            }
        }

override fun evalAndWait

        override fun evalAndWait(
            updateContext: Boolean,
            implicits: List<Any>,
            onEvalError: (List<ScriptDiagnostic>) -> Unit,
            onEvalException: (Throwable) -> Unit
        ): ResultValue? {
            return runBlocking {
                eval(updateContext, implicits, onEvalError, onEvalException)
            }
        }
    }

override suspend fun compileSnippet

    override suspend fun compileSnippet(
        s: String,
        implicits: List<Any>,
        onCompileError: (List<ScriptDiagnostic>) -> Unit
    ): K2PreparedSnippet? {
        // Never compile before the stdlib intro has, either (see [bootstrapReady]): a snippet
        // compiled first resolves against an engine with no default imports, so every `vec`,
        // `loop` and `.f2` is unresolved. The compile-only guard compiles a box's code while the
        // box's async intro may still be queued on the runloop, and lost that race under load.
        LatencySpans.time("engine.bootstrapWait", box = (associatedBox as? Box)?.id) { bootstrapReady.await() }
        return when (val r = compile(s)) {
            is ResultWithDiagnostics.Success -> {
                K2PreparedSnippet(r.value, implicits)
            }

            is ResultWithDiagnostics.Failure -> {
                lprintln("error,kotlin", "compiler error ${r}")
                onCompileError(r.reports)
                null
            }
        }
    }

override fun compileSnippetAndWait

    override fun compileSnippetAndWait(
        s: String,
        implicits: List<Any>,
        onCompileError: (List<ScriptDiagnostic>) -> Unit
    ): K2PreparedSnippet? {
        return runBlocking {
            compileSnippet(s, implicits, onCompileError)
        }
    }

override fun dispose()

Dispose the underlying K2 REPL compilation state. Call this when the KotlinInterface is no longer needed.

It is called from any thread, whatever the engine is doing: a document's last websocket leaving retires its boxes from a Ktor IO thread while a box's stdlib imports may still be queued on the runloop or compiling on compilationDispatcher. So the state is released only once nothing is using it (usingCompilationState), and a compile that starts after this is refused (compileAfterDisposal) instead of run.

Releasing the state under a compile used to break more than that compile. rootDisposable is this engine's compiler project, and when it is the last one in the JVM the compiler disposes the shared IntelliJ application with it, so ApplicationManager.getApplication() returns null. The compile then died with an NPE in LanguageUtil.matchingMetaLanguages; when it was the JVM's first compile, the NPE was thrown inside FileSizeLimit's static initializer, and every later compile in that JVM failed with Could not initialize class …FileSizeLimit. Measured 2026-10-04 by aborting a backdoor socket just after agent-create-box replied.

    override fun dispose() {
        disposed = true
        if (compilationStateUsers.get() == 0) releaseCompilationState()
        // Its own compiler project, which nothing else releases, unless it is being handed on.
        if (!completionDelegateHandedOn) k1CompletionDelegate?.dispose()
    }

    @Volatile
    private var disposed = false

override val isDisposed: Boolean get() = disposed

    private val compilationStateUsers = AtomicInteger(0)
    private val compilationStateReleased = AtomicBoolean(false)

Run body with the compilation state held live, or onDisposed once dispose has run.

dispose writes disposed and then reads the count; the last lease out decrements the count and then reads disposed. Whichever of the two comes second sees the other's write and releases the state, and releaseCompilationState makes sure that happens once.

    private inline fun <T> usingCompilationState(onDisposed: () -> T, body: () -> T): T {
        compilationStateUsers.incrementAndGet()
        try {
            return if (disposed) onDisposed() else body()
        } finally {
            if (compilationStateUsers.decrementAndGet() == 0 && disposed) releaseCompilationState()
        }
    }

    private fun releaseCompilationState() {
        if (compilationStateReleased.compareAndSet(false, true)) Disposer.dispose(rootDisposable)
    }

    private fun compileAfterDisposal(s: String): ResultWithDiagnostics<LinkedSnippet<CompiledScript>> {
        lprintln(
            "info,kotlin",
            "compile skipped: engine filePrefix=$filePrefix was disposed before this snippet compiled, preview=${compileSnippetPreview(s)}"
        )
        return ResultWithDiagnostics.Failure(
            ScriptDiagnostic(
                ScriptDiagnostic.unspecifiedError,
                "This box's Kotlin engine was disposed before the code compiled: its document was closed, or the box was deleted or reset.",
                ScriptDiagnostic.Severity.ERROR,
                null,
                null,
                null
            )
        )
    }

companion object

    companion object {

init

        init {
            // Keep the compiler's shared IntelliJ application alive after the last compilation state
            // in the JVM is disposed, as the Kotlin compile daemon does. Without it, disposing the
            // last engine (closing the only open document is enough) disposes the application and
            // nulls `ApplicationManager`, and any PSI work still in flight on another thread fails —
            // fatally for the whole JVM when that work is the first compile (see [dispose]). It also
            // saves rebuilding the application for the next document. An explicit
            // `-Dkotlin.environment.keepalive` wins.
            if (CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value == null) {
                CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value = "true"
            }
        }

var sharedClassLoader: ClassLoader? = null

val compilerPluginArgs: List<String> by lazy(LazyThreadSafetyMode.SYNCHRONIZED)

Every -Xplugin= argument this JVM's K2 compilation states get.

The two plugins are located differently on purpose. Wither's jar is on the app classpath — it carries annotations that box code names, so it has to be — and is found there. The DataFrame plugin's jar deliberately is not: it vendors ~1000 classes of the DataFrame runtime, which alongside the real dataframe-core dependency would put two copies of DataFrame in front of box code. -Xplugin loads it in the compiler's own plugin classloader, so only the compiler ever sees it.

        val compilerPluginArgs: List<String> by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            witherPluginArgs + dataFramePluginArgs + shadowedNamePluginArgs
        }

Field's own checker for box-level names that a lambda's receiver shadows (ShadowedSnippetNameDiagnostics). Disable with -Dafield.shadowedNameWarning=false.

        private val shadowedNamePluginArgs: List<String> by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            if (System.getProperty("afield.shadowedNameWarning", "true") != "true") return@lazy emptyList()
            runCatching { listOf("-Xplugin=${ShadowedSnippetNameCheckerPlugin.jar.absolutePath}") }
                .getOrElse { e ->
                    lprintln("warning,kotlin", "shadowed-name checker not enabled: ${e.message}")
                    emptyList()
                }
        }

The DataFrame compiler plugin, from libs/ rather than the classpath (see compilerPluginArgs). Disable with -Dafield.dataframePlugin=false; override the jar location with -Dafield.dataframePlugin.jar=<path>.

Its extension point (FirFunctionCallRefinementExtension) is documented in the compiler as "highly unstable and not recommended to use", and it generates local classes at body-resolve, so what it can and cannot do across a REPL snippet boundary is a live question — see DataFramePluginSpike.

        private val dataFramePluginArgs: List<String> by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            if (System.getProperty("afield.dataframePlugin", "true") != "true") {
                lprintln("kotlin", "dataframe: plugin disabled by -Dafield.dataframePlugin=false")
                return@lazy emptyList()
            }
            val override = System.getProperty("afield.dataframePlugin.jar")?.let { java.io.File(it) }
            val candidates = listOfNotNull(
                override,
                java.io.File("libs/dataframe-plugin.jar"),
                java.io.File(System.getProperty("user.dir"), "libs/dataframe-plugin.jar"),
            )
            candidates.firstOrNull { it.exists() }
                ?.absoluteFile
                ?.let {
                    lprintln("kotlin", "dataframe: enabling DataFrame compiler plugin from ${it.path}")
                    listOf("-Xplugin=${it.path}")
                }
                ?: emptyList<String>().also {
                    lprintln("kotlin", "dataframe: plugin jar not found in libs/; not enabled")
                }
        }

        private val witherPluginArgs: List<String> by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            System.getProperty("java.class.path").orEmpty()
                .split(java.io.File.pathSeparatorChar)
                .firstOrNull { it.endsWith("wither-plugin.jar") }
                ?.let { java.io.File(it).absoluteFile }
                ?.takeIf { it.exists() }
                ?.let {
                    lprintln("kotlin", "wither: enabling Wither compiler plugin from ${it.path}")
                    listOf("-Xplugin=${it.path}")
                }
                ?: emptyList<String>().also {
                    lprintln("kotlin", "wither: plugin jar not found on classpath; not enabled")
                }
        }

var compilationThread

multi-threaded compilation broken in current kotlin compiler. Daemon-threaded so this shared compile dispatcher never keeps a JVM alive.

        var compilationThread = Executors.newFixedThreadPool(1) { runnable ->
            Thread(runnable, "afield-ki2-compile").apply { isDaemon = true }
        }.asCoroutineDispatcher()

fun functionSignature(function: KFunction<*>)

        fun functionSignature(function: KFunction<*>) = afield.runtime.kotlin.functionSignature(function)

fun shortenType(name: String) = afield.runtime.kotlin.shortenType(name)

fun getTokenBounds(buf: String, cursor: Int) = afield.runtime.kotlin.getTokenBounds(buf, cursor)

fun getResult

        fun getResult(
            code: String, cursor: Int, completions: List<SourceCodeCompletionVariant>
        ) = getCompletionResult(code, cursor, completions)
    }

class ContextUpdater

    class ContextUpdater(
        val context: KotlinContext,
        private val evaluator: K2ReplEvaluator,
        private val ki: KotlinInterface2
    ) {

        private val processedSnippets =
            Collections.newSetFromMap(IdentityHashMap<LinkedSnippet<KJvmEvaluatedSnippet>, Boolean>())

        private class MutableBinding(val field: Field, val owner: Any, val type: KType) {
            fun matches(field: Field, owner: Any): Boolean =
                this.field == field && (Modifier.isStatic(field.modifiers) || this.owner === owner)
        }

        private val mutableBindings = mutableMapOf<String, MutableList<MutableBinding>>()

Track exported declarations by alias for idempotent preamble updates

        private val exportedDeclarations = mutableMapOf<String, String>()

fun update(evaluated: ResultValue? = null)

body — 302 lines, from line 3388
        fun update(evaluated: ResultValue? = null) {
            try {
                var lastSnippet = evaluator.lastEvaluatedSnippet
                val newSnippets = mutableListOf<Any>()
                // A cached evaluation can return to an older node. Do not replay that node's
                // ancestors and propagate their stale fields over the just-evaluated initializer.
                while (lastSnippet != null && processedSnippets.add(lastSnippet)) {
                    val result = lastSnippet.get().result
                    val line = result.scriptInstance
                    // A failed initializer must leave the running code's values alone.
                    if (line != null && result !is ResultValue.Error) newSnippets.add(line)
                    lastSnippet = lastSnippet.previous
                }
                newSnippets.reverse()
                val hasNewDeclarations = newSnippets.isNotEmpty()
                // Cached evaluation can reuse its history node and still rerun an initializer.
                val current = evaluated?.takeUnless { it is ResultValue.Error }?.scriptInstance
                if (current != null && newSnippets.none { it === current }) newSnippets.add(current)
                refreshVariables(newSnippets, current)
                refreshMethods(newSnippets)
                if (hasNewDeclarations) {
                    context.generation++
                }

            } catch (e: ReflectiveOperationException) {
                lprintln("error,kotlin", "Exception updating current variables $e")
            } catch (e: NullPointerException) {
                lprintln("error,kotlin", "Exception updating current variables $e")
            }
        }

        private fun refreshMethods(lines: List<Any>) {
            for (line in lines) {
                val methods = line.javaClass.methods
                for (method in methods) {
                    if (objectMethods.contains(method) || method.name == "main") {
                        continue
                    }
                    val function = method.kotlinFunction ?: continue
                    context.functions[function.name] = KotlinFunctionInfo(function, line)

                    val exportTargets = exportTargetsForFunction(method)
                    if (exportTargets.isNotEmpty()) {
                        processExportedFunction(function, line, exportTargets)
                    }
                }
            }
        }

        private enum class ExportTarget {
            Local,
            Root,
            Metaroot
        }

        private fun exportTargetsForFunction(method: java.lang.reflect.Method): Set<ExportTarget> {
            val targets = linkedSetOf<ExportTarget>()
            if (method.isAnnotationPresent(exported::class.java)) targets += ExportTarget.Local
            if (method.isAnnotationPresent(rootExported::class.java)) targets += ExportTarget.Root
            if (method.isAnnotationPresent(metarootExported::class.java)) targets += ExportTarget.Metaroot
            return targets.withLocalExport()
        }

        private fun exportTargetsForProperty(field: Field?, descriptor: KProperty<*>): Set<ExportTarget> {
            val targets = linkedSetOf<ExportTarget>()
            val annotations = descriptor.annotations + descriptor.getter.annotations
            if (field?.isAnnotationPresent(exported::class.java) == true ||
                annotations.any { it.annotationClass == exported::class }
            ) targets += ExportTarget.Local
            if (field?.isAnnotationPresent(rootExported::class.java) == true ||
                annotations.any { it.annotationClass == rootExported::class }
            ) targets += ExportTarget.Root
            if (field?.isAnnotationPresent(metarootExported::class.java) == true ||
                annotations.any { it.annotationClass == metarootExported::class }
            ) targets += ExportTarget.Metaroot
            return targets.withLocalExport()
        }

        private fun Set<ExportTarget>.withLocalExport(): Set<ExportTarget> =
            if (isEmpty() || contains(ExportTarget.Local)) this else this + ExportTarget.Local

        private fun boxesForExportTargets(targets: Set<ExportTarget>): List<Box> {
            val box = ki.associatedBox as? Box ?: return emptyList()
            return targets.mapNotNull {
                when (it) {
                    ExportTarget.Local -> box
                    ExportTarget.Root -> box.root
                    ExportTarget.Metaroot -> metaroot
                }
            }.distinctBy { it.id }
        }

        private fun processExportedFunction(function: KFunction<*>, line: Any, targets: Set<ExportTarget>) {
            try {
                val exportedName = exportedAlias(function.name)
                val declaration = generateFunctionDeclaration(function, exportedName)

                boxesForExportTargets(targets).forEach { box ->
                    try {
                        val functionReference = createBoundFunctionReference(function, line)
                        box.ki.context.functions[function.name] = KotlinFunctionInfo(function, line)
                        box.setPropertyRaw(exportedName, functionReference, doResync = false)
                        upsertExportedDeclaration(box, exportedName, declaration)
                    } catch (e: Exception) {
                        lprintln("error,kotlin", "Failed to export @exported function '${function.name}': ${e.message}")
                    }
                }
            } catch (e: Exception) {
                lprintln("error,kotlin", "Failed to process @exported function: ${e.message}")
            }
        }

        private fun generateFunctionDeclaration(function: KFunction<*>, exportedName: String): String {
            val params = function.parameters.filter { it.kind != kotlin.reflect.KParameter.Kind.INSTANCE }
            val paramTypes = params.joinToString(", ") { param ->
                ExportedTypeNames.render(param.type, ki.classLoader)
            }
            val returnType = ExportedTypeNames.render(function.returnType, ki.classLoader)

            val functionType = if (params.isEmpty()) {
                "() -> $returnType"
            } else {
                "($paramTypes) -> $returnType"
            }

            return "val Box.$exportedName by InheritanceFromContext<$functionType>() /*from:self*/ /**/"
        }

        private fun processExportedProperty(
            name: String,
            descriptor: KProperty<*>,
            value: Any?,
            line: Any,
            targets: Set<ExportTarget>
        ) {
            try {
                val exportedName = exportedAlias(name)
                val declaration = generatePropertyDeclaration(descriptor, exportedName)

                boxesForExportTargets(targets).forEach { box ->
                    try {
                        box.ki.context.vars[name] = KotlinVariableInfo(value, descriptor, line)
                        box.ki.context.exportedVariables += name
                        if (value != null) {
                            box.setPropertyRaw(exportedName, value, doResync = true)
                        }
                        upsertExportedDeclaration(box, exportedName, declaration)
                    } catch (e: Exception) {
                        lprintln("error,kotlin", "Failed to export @exported property '$name': ${e.message}")
                    }
                }
            } catch (e: Exception) {
                lprintln("error,kotlin", "Failed to process @exported property '$name': ${e.message}")
            }
        }

        private fun generatePropertyDeclaration(property: KProperty<*>, exportedName: String): String {
            val keyword = if (property is KMutableProperty<*>) "var" else "val"
            val typeName = ExportedTypeNames.render(property.returnType, ki.classLoader)
            return "$keyword Box.$exportedName by InheritanceFromContext<$typeName>() /*from:self*/ /**/"
        }

        private fun exportedAlias(name: String): String {
            return if (name.startsWith("_")) name else "_$name"
        }

        private fun upsertExportedDeclaration(box: HasProperties, exportedName: String, declaration: String) {
            val currentPreamble = box.getPropertyRaw("preamble", "") as? String ?: ""
            val currentLines = currentPreamble
                .lines()
                .map { it.trimEnd() }
                .filter { it.isNotBlank() }

            val filteredLines = currentLines.filterNot {
                isExportedDeclarationLineFor(it, exportedName)
            }
            val nextLines = filteredLines + declaration
            val newPreamble = nextLines.joinToString("\n")

            val previous = exportedDeclarations[exportedName]
            if (newPreamble != currentPreamble || previous != declaration) {
                box.setPropertyRaw("preamble", newPreamble, doResync = true)
                exportedDeclarations[exportedName] = declaration
                lprintln("info,kotlin", "Added @exported declaration to preamble: $declaration")
            }
        }

        private fun isExportedDeclarationLineFor(line: String, exportedName: String): Boolean {
            val withoutPrefixComment = line.trim()
                .replaceFirst(Regex("""^\s*/\*.*?\*/\s*"""), "")
                .trim()
            if (!withoutPrefixComment.endsWith("/**/")) return false

            val nameEscaped = Regex.escape(exportedName)
            val patterns = listOf(
                Regex("""^(val|var)\s+Box\.$nameEscaped\b.*\bby\b.*$"""),
                Regex("""^(val|var)\s+$nameEscaped\b.*\bby\b.*$"""),
                Regex("""^(val|var)\s+Box\.$nameEscaped\b\s*:\s*.*$"""),
                Regex("""^(val|var)\s+$nameEscaped\b\s*:\s*.*$""")
            )
            return patterns.any { it.matches(withoutPrefixComment) }
        }

        private fun createBoundFunctionReference(function: KFunction<*>, line: Any): Any {
            val arity = function.parameters.count { it.kind != KParameter.Kind.INSTANCE }
            val functionInterface = when (arity) {
                0 -> kotlin.Function0::class.java
                1 -> kotlin.Function1::class.java
                2 -> kotlin.Function2::class.java
                3 -> kotlin.Function3::class.java
                4 -> kotlin.Function4::class.java
                5 -> kotlin.Function5::class.java
                6 -> kotlin.Function6::class.java
                7 -> kotlin.Function7::class.java
                8 -> kotlin.Function8::class.java
                9 -> kotlin.Function9::class.java
                10 -> kotlin.Function10::class.java
                11 -> kotlin.Function11::class.java
                12 -> kotlin.Function12::class.java
                13 -> kotlin.Function13::class.java
                14 -> kotlin.Function14::class.java
                15 -> kotlin.Function15::class.java
                16 -> kotlin.Function16::class.java
                17 -> kotlin.Function17::class.java
                18 -> kotlin.Function18::class.java
                19 -> kotlin.Function19::class.java
                20 -> kotlin.Function20::class.java
                21 -> kotlin.Function21::class.java
                22 -> kotlin.Function22::class.java
                else -> throw IllegalArgumentException("Cannot export function '${function.name}': arity $arity is not supported")
            }

            return Proxy.newProxyInstance(
                line.javaClass.classLoader,
                arrayOf(functionInterface)
            ) { proxy, method, args ->
                when (method.name) {
                    "invoke" -> {
                        val values = args?.toList() ?: emptyList()
                        val callArgs = ArrayList<Any?>(function.parameters.size)
                        var valueIndex = 0
                        for (parameter in function.parameters) {
                            when (parameter.kind) {
                                KParameter.Kind.INSTANCE -> callArgs.add(line)
                                else -> {
                                    if (valueIndex >= values.size) {
                                        throw IllegalArgumentException(
                                            "Missing argument ${valueIndex + 1} for @exported function '${function.name}'"
                                        )
                                    }
                                    callArgs.add(values[valueIndex++])
                                }
                            }
                        }
                        function.call(*callArgs.toTypedArray())
                    }

                    "toString" -> functionSignature(function)
                    "hashCode" -> System.identityHashCode(proxy)
                    "equals" -> proxy === args?.getOrNull(0)
                    else -> throw UnsupportedOperationException(
                        "Unsupported proxy method '${method.name}' for @exported function '${function.name}'"
                    )
                }
            }
        }

        @Throws(ReflectiveOperationException::class)
        private fun refreshVariables(lines: List<Any>, current: Any?) {
            for (line in lines) {
                val seen = mutableSetOf<String>()
                val fields = line.javaClass.declaredFields
                findVariables(fields, line, seen, line === current)
                findKotlinProperties(line, seen)
            }
        }

        @Throws(IllegalAccessException::class)
        private fun findVariables(fields: Array<Field>, o: Any, seen: MutableSet<String>, reset: Boolean) {
            for (field in fields) {
                val fieldName = field.name
                if (fieldName.contains("$\$implicitReceiver") || fieldName.contains("script$") || fieldName.endsWith("\$delegate")) {
                    continue
                }

                field.isAccessible = true
                val value = field.get(o)
                val descriptor = field.kotlinProperty
                if (descriptor != null) {
                    resetOlderMutableBindings(field, descriptor, o, value, reset)
                    context.vars[descriptor.name] = KotlinVariableInfo(value, descriptor, o)
                    seen += descriptor.name
                    val exportTargets = exportTargetsForProperty(field, descriptor)
                    if (exportTargets.isNotEmpty()) {
                        processExportedProperty(descriptor.name, descriptor, value, o, exportTargets)
                    } else {
                        // redeclared without the annotation: no longer this box's export
                        context.exportedVariables -= descriptor.name
                    }
                }
            }
        }

A declaration rerun resets older mutable slots used by existing closures. This happens once after evaluation; it does not turn the snippets' fields into shared storage. Immutable snapshots, delegates and differently typed declarations keep their normal behavior.

        private fun resetOlderMutableBindings(field: Field, descriptor: KProperty<*>, owner: Any, value: Any?, reset: Boolean) {
            if (descriptor !is KMutableProperty<*>) return
            if (descriptor.isLateinit && value == null) return
            val type = descriptor.returnType
            val bindings = mutableBindings.getOrPut(descriptor.name) { mutableListOf() }
            for (older in bindings) {
                if (reset && !older.matches(field, owner) && older.type == type) older.field.set(older.owner, value)
            }
            if (bindings.none { it.matches(field, owner) }) {
                bindings.add(MutableBinding(field, owner, type))
            }
        }

        private fun findKotlinProperties(o: Any, seen: MutableSet<String>) {
            for (descriptor in o::class.memberProperties) {
                val name = descriptor.name
                if (name in seen || name.contains("$\$implicitReceiver") || name.contains("script$")) continue
                if (descriptor.parameters.size != 1) continue

                val value = runCatching {
                    descriptor.isAccessible = true
                    descriptor.getter.call(o)
                }.getOrNull() ?: continue

                context.vars[name] = KotlinVariableInfo(value, descriptor, o)
                seen += name

                val exportTargets = exportTargetsForProperty(null, descriptor)
                if (exportTargets.isNotEmpty()) {
                    processExportedProperty(name, descriptor, value, o, exportTargets)
                } else {
                    context.exportedVariables -= name
                }
            }
        }

companion object

        companion object {
            private val objectMethods = HashSet(listOf(*Any::class.java.methods))
        }
    }
}