Skip to content

ContextWhy.kt

src/jvmMain/kotlin/afield/document/context/ContextWhy.kt · 267 lines

package afield.document.context

import afield.app.server.app
import afield.data.table.div
import afield.data.table.get
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.document.graph.preamble
import afield.runtime.execution.plus
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.with

import afield.audio.synth.*
import afield.document.graph.Box
import afield.document.graph.box
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.execution.resolveInheritedDeclarationForHover
import afield.runtime.lifecycle.map
import afield.ui.output.templates.filter
import afield.runtime.macros.gatherPreambleUpwardsReport
import afield.runtime.macros.preambleSupplierTag
import afield.runtime.macros.stripPreambleSupplierTag
import afield.runtime.macros.PREAMBLE_SELF_SUPPLIER
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

Builds the JSON answer to "why did this value resolve here?" for the backdoor, MCP and AI tools.

ContextWhy.explain takes a box and a name or a type. The answer names the supplying box, the path of parents to it, whether the value was a property or a live variable, and what it shadows. For a name declared in the box's preamble it also names that preamble line and the box it came from.

object ContextWhy

"Why did this value resolve here?" — ContextResolution.explain, the answer as JSON, shared by the backdoor's why command, the MCP tool field_why and the in-app AI tool WhyDoesThisResolve.

Nothing here has a walk of its own. A name declared in the box's gathered preamble (val Box._x by InheritanceFromContext<Double>() /*from:<uid>*/ /**/) is resolved with the walk that declaration's delegate performs — the same one the Monaco hover uses — and the answer also names the preamble line and the supplier its from: tag records. A name with no declaration is resolved as fromContextWithNameOrNull(name) would; a type as fromContext<T>() would (a value matches when its class, a superclass or an interface has that name, so no class loader is needed).

object ContextWhy {

At most this many shadowed candidates are listed; the count is always given.

    private const val MAX_SHADOWED = 24

fun explain(box: Box, name: String? = null, type: String? = null): JsonObject

body — 90 lines, from line 61
    fun explain(box: Box, name: String? = null, type: String? = null): JsonObject {
        require(!name.isNullOrBlank() || !type.isNullOrBlank()) { "why needs a name or a type" }
        val gathered = runCatching { gatherPreambleUpwardsReport(box) }.getOrNull()
        val declaration = name?.let { n -> gathered?.let { findDeclaration(box, n, it.text) } }

        val resolution: ContextResolution.Resolution?
        val mechanism: ContextResolution.Mechanism
        val delegate: String?
        if (declaration != null && type == null) {
            val hover = resolveInheritedDeclarationForHover(box, name, gathered!!.text)
            resolution = hover?.resolution
            delegate = hover?.delegate
            mechanism = when (hover?.delegate) {
                "InheritanceFromContext" -> ContextResolution.Mechanism.INHERITANCE_FROM_CONTEXT
                else -> ContextResolution.Mechanism.INHERITANCE
            }
        } else if (name != null) {
            val accept: (Any?) -> Boolean = if (type == null) { v -> v != null } else { v -> v != null && isOfType(v, type) }
            mechanism = ContextResolution.Mechanism.FROM_CONTEXT_NAMED
            delegate = null
            resolution = ContextResolution.walk(
                box,
                ContextResolution.Query(name, listOf(name), accept, { accept(it.value) }),
                ContextResolution.Policy.fromContext(includeLocalVariables = false, selfCandidate = false),
                mechanism,
                explain = true,
            )
        } else {
            mechanism = ContextResolution.Mechanism.FROM_CONTEXT
            delegate = null
            resolution = ContextResolution.walk(
                box,
                ContextResolution.Query(type!!, null, { it != null && isOfType(it, type) }, { it.value != null && isOfType(it.value, type) }, { isOfType(it, type) }),
                ContextResolution.Policy.fromContext(includeLocalVariables = false),
                mechanism,
                explain = true,
            )
        }

        return buildJsonObject {
            put("box", boxJson(box))
            put("query", name ?: type)
            name?.let { put("name", it) }
            type?.let { put("type", it) }
            put("mechanism", mechanism.name)
            delegate?.let { put("delegate", it) }
            put("found", resolution != null)
            if (resolution != null) {
                put("supplier", boxJson(resolution.supplier))
                put("matchKind", resolution.matchKind.name)
                resolution.matchedName?.let { put("matchedName", it) }
                put("depth", resolution.depth)
                put("path", JsonArray(resolution.path.map { boxJson(it) }))
                put("crossesEdges", resolution.path.size > 1)
                put("value", valueText(resolution.value))
                put("valueType", resolution.value?.javaClass?.name?.let { JsonPrimitive(it) } ?: JsonNull)
                put("ambiguous", resolution.ambiguous)
                put("competing", JsonArray(resolution.competing.map { candidateJson(it) }))
                put("shadowedCount", resolution.shadowed.size)
                put("shadowed", JsonArray(resolution.shadowed.take(MAX_SHADOWED).map { candidateJson(it) }))
            }
            declaration?.let { d ->
                put("declaration", buildJsonObject {
                    put("line", d.text)
                    put("inOwnPreamble", d.inOwnPreamble)
                    put("tag", d.tag?.let { JsonPrimitive(it) } ?: JsonNull)
                    put("suppliedBy", d.suppliedBy?.let { boxJson(it) } ?: JsonNull)
                })
            }
            if (gathered != null && name != null) {
                val bare = name.removePrefix("_")
                val mentions = { line: String -> Regex("""\bBox\.(_?${Regex.escape(bare)})\b""").containsMatchIn(line) }
                put("stalePreambleLines", JsonArray(gathered.stale.filter(mentions).map { JsonPrimitive(stripPreambleSupplierTag(it).trim()) }))
                put("preambleConflicts", JsonArray(gathered.conflicts.filter { it.receiverAndName.substringAfterLast('.').removePrefix("_") == bare }.map { c ->
                    buildJsonObject {
                        put("declares", c.receiverAndName)
                        put("kept", stripPreambleSupplierTag(c.kept).trim())
                        put("keptSupplier", c.keptSupplier?.let { JsonPrimitive(it) } ?: JsonNull)
                        put("discarded", JsonArray(c.discarded.map { (line, supplier) ->
                            buildJsonObject {
                                put("line", stripPreambleSupplierTag(line).trim())
                                put("supplier", supplier?.let { JsonPrimitive(it) } ?: JsonNull)
                            }
                        }))
                    }
                }))
            }
            put("summary", summary(box, name ?: type!!, mechanism, delegate, resolution, declaration))
        }
    }

class Declaration(val text: String, val tag: String?, val suppliedBy: Box?, val inOwnPreamble: Boolean)

One line of the box's gathered preamble that declares Box.<name>, and where its tag says it came from.

    class Declaration(val text: String, val tag: String?, val suppliedBy: Box?, val inOwnPreamble: Boolean)

    private fun findDeclaration(box: Box, name: String, gatheredText: String): Declaration? {
        val regex = Regex("""\b(val|var)\s+Box\.${Regex.escape(name)}\s+by\b""")
        val line = gatheredText.lines().firstOrNull { regex.containsMatchIn(it) } ?: return null
        val tag = preambleSupplierTag(line)
        val own = (box.getPropertyRaw("preamble") as? String)?.lines()?.any { it.trim() == line.trim() } == true
        val supplier = when (tag) {
            null -> if (own) box else null
            PREAMBLE_SELF_SUPPLIER -> box
            else -> findBox(box, tag)
        }
        return Declaration(stripPreambleSupplierTag(line).trim(), tag, supplier, own)
    }

    private fun findBox(from: Box, uid: String): Box? {
        if (from.id == uid) return from
        val seen = HashSet<Box>()
        var fringe = listOf(from)
        while (fringe.isNotEmpty()) {
            val next = ArrayList<Box>()
            for (b in fringe) for (p in b.parents) if (seen.add(p)) {
                if (p.id == uid) return p
                next += p
            }
            fringe = next
        }
        return if (from.isInsideInitialized()) from.inside.boxes[uid] else null
    }

fun isOfType(value: Any, type: String): Boolean

Whether value's class, one of its superclasses or one of their interfaces is called type (a fully-qualified or simple name).

body — 74 lines, from line 184
    fun isOfType(value: Any, type: String): Boolean {
        val seen = HashSet<Class<*>>()
        val queue = ArrayDeque<Class<*>>()
        queue += value.javaClass
        while (queue.isNotEmpty()) {
            val c = queue.removeFirst()
            if (!seen.add(c)) continue
            if (c.name == type || c.simpleName == type || c.canonicalName == type) return true
            c.superclass?.let { queue += it }
            queue += c.interfaces
        }
        return false
    }

    private fun boxJson(b: Box): JsonObject = buildJsonObject {
        put("uid", b.id)
        put("name", b.name)
    }

    private fun candidateJson(c: ContextResolution.Candidate): JsonObject = buildJsonObject {
        put("supplier", boxJson(c.supplier))
        put("matchKind", c.matchKind.name)
        c.matchedName?.let { put("matchedName", it) }
        put("depth", c.depth)
        put("path", JsonArray(c.path.map { JsonPrimitive(it.id) }))
        put("value", valueText(c.value))
    }

    private fun valueText(v: Any?): String = when (v) {
        null -> "null"
        is Box -> "box '${v.name.ifBlank { v.id }}'"
        else -> runCatching { v.toString() }.getOrElse { v.javaClass.name }.let { if (it.length > 200) it.take(199) + "…" else it }
    }

    private fun label(b: Box) = "'" + b.name.ifBlank { b.id } + "'"

    private fun summary(
        box: Box,
        query: String,
        mechanism: ContextResolution.Mechanism,
        delegate: String?,
        r: ContextResolution.Resolution?,
        d: Declaration?,
    ): String = buildString {
        val how = delegate ?: when (mechanism) {
            ContextResolution.Mechanism.FROM_CONTEXT -> "fromContext"
            ContextResolution.Mechanism.FROM_CONTEXT_NAMED -> "fromContextWithNameOrNull"
            else -> mechanism.name.lowercase()
        }
        if (r == null) {
            append("${label(box)}: nothing up the graph supplies `$query` ($how)")
        } else {
            val slot = when (r.matchKind) {
                ContextResolution.MatchKind.SELF -> "the box itself"
                ContextResolution.MatchKind.VARIABLE -> "variable `${r.matchedName}`"
                ContextResolution.MatchKind.PROPERTY -> "property `${r.matchedName}`"
            }
            val where = if (r.supplier === box) "this box" else label(r.supplier)
            append("${label(box)} reads `$query` ($how) from $slot of $where")
            if (r.path.size > 1) append(", path " + r.path.joinToString(" → ") { label(it) })
            append(" = ${valueText(r.value)}")
            if (r.ambiguous) append("; ambiguous: " + r.competing.joinToString(", ") { label(it.supplier) } + " also supplies it at the same distance with a different value, and this one is first in parents order")
            val hidden = r.shadowed.map { it.supplier }.filter { it !== r.supplier }.distinct()
            if (hidden.isNotEmpty()) append("; shadows " + hidden.joinToString(", ") { label(it) })
        }
        if (d != null) {
            append("; declared by `${d.text.removeSuffix("/**/").trim()}`")
            when {
                d.suppliedBy != null && d.suppliedBy !== box -> append(" from the preamble of ${label(d.suppliedBy)}")
                d.inOwnPreamble -> append(" in this box's own preamble")
                d.tag != null -> append(" (tagged from ${d.tag}, not an ancestor)")
            }
        }
    }

fun plain(json: JsonElement): Any?

json as plain maps, lists and primitives — what a Jackson-serialised AI tool result carries.

    fun plain(json: JsonElement): Any? = when (json) {
        is JsonNull -> null
        is JsonPrimitive -> if (json.isString) json.content else json.content.toBooleanStrictOrNull() ?: json.content.toLongOrNull() ?: json.content.toDoubleOrNull() ?: json.content
        is JsonArray -> json.map { plain(it) }
        is JsonObject -> json.mapValues { plain(it.value) }
    }
}