Lesson 1
Source information becomes tooling data
Map compiler-emitted source strings and markers to composition groups, diagnostic traces, tracer events, and scope observers.
Evidence legend: Durable marks a stable mental model; Version-specific marks behavior tied to the pins above; Experimental marks a tooling-only, opt-in, or changing surface.
A crash in measure does not carry the original Kotlin call stack through every composition operation. Instead, Compose can build a second, diagnostic path: the compiler leaves source metadata around groups, the runtime stores it, and tooling interprets it later.
Compiler markers are breadcrumbs, not a source map
Version-specific
The pinned compiler lowering accepts collectSourceInformation and traceMarkersEnabled switches. With source collection enabled, it emits sourceInformation immediately after a group starts and emits sourceInformationMarkerStart/End around calls whose group was elided—ReadOnlyComposable is the example in the lowering comments (ComposableFunctionBodyTransformer.kt#L373-L386, #L815-L850). The string is an internal encoding of function/call information, file, and offsets; it is not the original stack frame.
The runtime’s CompositionData exposes a tree of CompositionGroups. A group has a compiler key, optional sourceInfo, an optional emitted node, and slot data; its source-info format is explicitly internal and meant to be translated by tools (CompositionData.kt#L30-L93). This is the mapping:
flowchart LR
A[Compiler source markers] --> B[Composer groups]
B --> C[CompositionData]
C --> D[Tooling or diagnostics]
B --> E[Compose stack frames]A useful boundary follows: tooling can inspect a call/group tree and node association, but CompositionData is not a supported slot-table API. Its own KDoc tells tools to use a higher-level interpretation. Treat data, keys, and offsets as evidence at this revision, not application contracts.
Traces and errors use different channels
Version-specific
CompositionTracer is a global internal hook. The compiler can guard traceEventStart(key, dirty1, dirty2, info) and traceEventEnd() behind isTraceInProgress; the current signature carries a generated group key, dirty metadata, and display text (Composer.kt#L1087-L1148). The runtime documentation deliberately permits occasional information loss and says callers should not synchronize it. A trace is therefore a low-friction instrumentation stream, not a complete event log.
Diagnostic stack traces are another path. ComposeStackTraceMode.SourceInformation records source information and appends a suppressed exception when a crash occurs; GroupKeys is less precise but works with minified builds and adds no runtime overhead until a crash (ComposeStackTrace.kt#L38-L94). LocalCompositionErrorContext lets custom node code attach a diagnostic trace to failures in measure, layout, or draw. It returns false when the node or source information cannot be found, including a minified build without source information (CompositionErrorContext.kt#L24-L65).
Observers report scope edges, not “all recomposition”
Experimental
CompositionObserver reports composition begin/end, scope enter/exit, reads, invalidation, and disposal. Its KDoc warns that a state invalidation is not necessarily composed: a branch may disappear, or movable content may move elsewhere. It also says state-change invalidations are normally reported before recomposition, not synchronously at the write (CompositionObserver.kt#L85-L156). Use it to explain observed scope edges, not to infer a universal scheduler or a rendered-frame count.
Reproduce the mapping
At the pinned AndroidX and Kotlin checkouts:
# AndroidX runtime tooling and observer behavior
./gradlew :compose:runtime:runtime:desktopTest \
--tests 'androidx.compose.runtime.tooling.CompositionDataTests' \
--tests 'androidx.compose.runtime.CompositionObserverTests'
# Kotlin compiler emission and source/no-source variants
./gradlew :plugins:compose-compiler-plugin:compiler-hosted:integration-tests:test \
--tests 'androidx.compose.compiler.plugins.kotlin.TraceInformationTest' \
--tests 'androidx.compose.compiler.plugins.kotlin.RuntimeTestsK2'
Controls: compare CompositionDataTests.canFindSourceInfo with a no-source compiler variant; compare observer read/invalidation callbacks with a branch that disappears. Expected: groups can expose source info when collection is enabled, compiler goldens show paired marker calls, and observer events distinguish reads from invalidations/disposal. Limits: these tests do not guarantee an IDE’s presentation, a custom node’s error path, or behavior after an optimizer changes the generated code. This procedure was not executed in this content edit.
Build caveats and check
- “A Kotlin stack trace is preserved.” The runtime reconstructs Compose frames from group keys, source strings, and offsets.
- “Source information is free.” The pinned diagnostic docs call out runtime overhead and advise against enabling source-information traces in unoptimized release builds.
- “R8 only removes useful diagnostics.” Minification can remove source information, but group-key mode is designed to remain usable with mapping files.
Before enabling a diagnostic mode, ask: is this a trace stream, a scope-observation stream, or a crash-time reconstructed path? If the build can remove source markers, keep the fallback and the limitation visible.
Evidence ledger
| Claim | Evidence | Label |
|---|---|---|
| Compiler emission is optional and markers can stand in for elided groups | ComposableFunctionBodyTransformer.kt#L373-L386 | Version-specific |
| Groups expose internal source info, nodes, and slot data to tooling | CompositionData.kt#L30-L93 | Version-specific |
| Source and group-key stack traces have different cost/precision trade-offs | ComposeStackTrace.kt#L38-L94 | Version-specific |
Freshness
Refresh when compiler source-info flags, marker pairing, CompositionData parsing, diagnostic stack-trace modes, tracer delivery, or observer callback timing changes. Re-check optimizer rules before making a release-build recommendation.
Finished this lesson?
Your progress stays only in this browser.