Skip to content

Regression: FUN no longer resolves non-function targets (classes, objects, properties) #18

@devcrocod

Description

@devcrocod

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:

  1. 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.

  2. 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.

  3. 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/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions