Summary
In 0.1.x, FUN <name> resolved to any top-level declaration — class, object, val/var, or fun — and rendered the declaration text as a snippet. In 0.2.0 the resolver was narrowed to KtNamedFunction only; everything else now produces a diagnostic (fatal in strict mode, a warning + stale snippet with behavior { ignoreMissing = true }).
This is a documented change — MIGRATION.md behavior-changes section and CLAUDE.md invariants both state it — but it broke a real downstream use case.
Impact
DataFrame uses Korro to demo pluggable types, e.g. a custom JDBC DbType. The natural shape of such a demo is an object or class, not a function:
object MyJdbcType : DbType("custom") {
override fun convertSqlTypeToColumnSchemaValue(...) = ...
}
You can't reasonably wrap this in fun demo() { ... } — the point of the snippet is the type declaration and its override members, not an expression body. After the 0.2.0 upgrade this scenario either fails the build or degrades to stale output.
Reported by DataFrame users after the 0.2.0 upgrade.
Where it lives in code
korro-analysis/src/main/kotlin/io/github/devcrocod/korro/analysis/FqnResolver.kt:74 — only KtNamedFunction is collected into the FQN/short-name index.
MIGRATION.md — "Non-function targets (properties, classes, top-level declarations, .kts scripts) now produce a diagnostic. Only fun declarations are valid FUN targets."
CLAUDE.md — listed as an invariant ("Only KtNamedFunction is a valid FUN/FUNS target").
Options
Three ways to restore the downstream use case, with different trade-offs:
-
Extend FUN to accept KtClassOrObject and KtProperty. Emit the full declaration text (signature + body). Closest to 0.1.x behavior, existing consumer docs that pointed at classes would start working again with no changes. Downside: FUN becomes polymorphic — same directive emits "function body" for one target type and "whole declaration" for another.
-
New directive: DECL (or TYPE). Cleanly separates "just the function body" (FUN) from "the whole declaration" (DECL). Least surprising semantically, but doesn't restore compatibility for 0.1.x consumers who wrote FUN pointing at a class — they'd have to migrate the directive name too.
-
Allow //SampleStart / //SampleEnd markers inside classes/objects. FUN resolves by FQN of the container and emits only the marker-bracketed region. Most controlled output, but requires consumers to add markers everywhere they want a class snippet — no drop-in compatibility.
My preference is (1) + keep marker semantics working inside classes — it restores 0.1.x behavior for existing consumer docs and covers the DataFrame use case without introducing new syntax. The CLAUDE.md invariant and MIGRATION.md note would need to be updated.
Acceptance
- A
FUN/FUNS directive pointing at a top-level class, object, or val/var resolves and emits a non-empty snippet.
- Strict mode (
ignoreMissing = false) does not fail on such targets.
- Integration test fixture demonstrating an
object target in integration-tests/fixtures/.
Summary
In 0.1.x,
FUN <name>resolved to any top-level declaration —class,object,val/var, orfun— and rendered the declaration text as a snippet. In 0.2.0 the resolver was narrowed toKtNamedFunctiononly; everything else now produces a diagnostic (fatal in strict mode, a warning + stale snippet withbehavior { ignoreMissing = true }).This is a documented change —
MIGRATION.mdbehavior-changes section andCLAUDE.mdinvariants both state it — but it broke a real downstream use case.Impact
DataFrame uses Korro to demo pluggable types, e.g. a custom JDBC
DbType. The natural shape of such a demo is anobjectorclass, not a function:You can't reasonably wrap this in
fun demo() { ... }— the point of the snippet is the type declaration and itsoverridemembers, not an expression body. After the 0.2.0 upgrade this scenario either fails the build or degrades to stale output.Reported by DataFrame users after the 0.2.0 upgrade.
Where it lives in code
korro-analysis/src/main/kotlin/io/github/devcrocod/korro/analysis/FqnResolver.kt:74— onlyKtNamedFunctionis collected into the FQN/short-name index.MIGRATION.md— "Non-function targets (properties, classes, top-level declarations,.ktsscripts) now produce a diagnostic. Onlyfundeclarations are validFUNtargets."CLAUDE.md— listed as an invariant ("OnlyKtNamedFunctionis a validFUN/FUNStarget").Options
Three ways to restore the downstream use case, with different trade-offs:
Extend
FUNto acceptKtClassOrObjectandKtProperty. Emit the full declaration text (signature + body). Closest to 0.1.x behavior, existing consumer docs that pointed at classes would start working again with no changes. Downside:FUNbecomes polymorphic — same directive emits "function body" for one target type and "whole declaration" for another.New directive:
DECL(orTYPE). Cleanly separates "just the function body" (FUN) from "the whole declaration" (DECL). Least surprising semantically, but doesn't restore compatibility for 0.1.x consumers who wroteFUNpointing at a class — they'd have to migrate the directive name too.Allow
//SampleStart///SampleEndmarkers inside classes/objects.FUNresolves by FQN of the container and emits only the marker-bracketed region. Most controlled output, but requires consumers to add markers everywhere they want a class snippet — no drop-in compatibility.My preference is (1) + keep marker semantics working inside classes — it restores 0.1.x behavior for existing consumer docs and covers the DataFrame use case without introducing new syntax. The
CLAUDE.mdinvariant andMIGRATION.mdnote would need to be updated.Acceptance
FUN/FUNSdirective pointing at a top-levelclass,object, orval/varresolves and emits a non-empty snippet.ignoreMissing = false) does not fail on such targets.objecttarget inintegration-tests/fixtures/.