Explain how the @DslMarker check is enforced (compile-time vs runtime), what the annotation's own retention/target are, and one limitation candidates often overlook.
answer
- Compile-time resolution filter, zero runtime cost
- @Target(ANNOTATION_CLASS), @Retention(BINARY)
- Filters outer same-marked receiver from implicit set
- Doesn't govern captured vars / top-level functions
- Must mark EVERY receiver type or holes appear
basics
~20 sIt's a compile-time check by the Kotlin compiler on implicit receiver resolution — no runtime cost. The marker annotation itself targets annotation classes and has binary retention. A common gap: it only governs implicit receiver calls, not extension functions you import.
solid answer
~40 s@DslMarker enforcement is purely **compile-time**: during overload/receiver resolution, when two implicit receivers share the marker, the compiler removes the outer one from the candidate implicit receivers, producing an error for unqualified outer-member access. There is zero runtime representation of the rule. The @DslMarker annotation is declared with `@Target(AnnotationTarget.ANNOTATION_CLASS)` (so it only goes on annotation classes) and `@Retention(AnnotationRetention.BINARY)` (kept in class files, not reflectable at runtime by default). A frequently overlooked limitation: the restriction is about implicit receivers, so it does not stop you from calling top-level or extension functions, nor from using a captured variable reference to the outer builder; and it only triggers when both receivers are actually annotated — mark every receiver type or the protection has holes. It also doesn't constrain explicitly qualified this@label access.
code
kotlin · 14 lines// Conceptual stdlib declaration
@Target(AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class DslMarker
@DslMarker annotation class HtmlDsl
@HtmlDsl class HtmlBuilder { fun meta() {} ; fun body(b: BodyBuilder.() -> Unit) {} }
@HtmlDsl class BodyBuilder
fun render(h: HtmlBuilder) = h.body {
// meta() // compile error: outer implicit receiver hidden
val outer = h // captured variable bypasses the rule
outer.meta() // OK
}go deeper
Unlikely to know retention/target details; may just say 'the compiler checks it'.
Knows it's compile-time and applied to receiver classes, but fuzzy on retention/target and limitations.
States compile-time-only enforcement, the ANNOTATION_CLASS target and BINARY retention, and at least one real limitation.
Connects the resolution-filter mechanism to API-design implications, cross-module behavior via BINARY retention, and the coverage gaps to guard against in a published DSL.
## Compile-time only The `@DslMarker` rule is resolved entirely by the **Kotlin compiler** during *receiver/overload resolution*. When the compiler builds the set of implicit receivers available at a call site, it applies the rule: for any marker `M`, only the innermost `M`-annotated receiver stays in the implicit set; outer `M`-annotated receivers are filtered out. An unqualified call that would have bound to a filtered receiver becomes a **compile error**. There is **no runtime check, no generated guard, and no performance cost** — the produced bytecode is identical to code that never had the conflict. ## The annotation's own metadata `@DslMarker` is declared in the standard library roughly as: ```kotlin @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.BINARY) annotation class DslMarker ``` - **Target = ANNOTATION_CLASS**: it can *only* be placed on another annotation class. Putting it on a class, function, or property is a compile error. - **Retention = BINARY**: it is kept in the compiled class files (so cross-module DSLs work) but is *not* visible to runtime reflection (`RUNTIME` would be needed for that). You don't normally reflect on it. ## Limitations candidates overlook 1. **Only implicit receivers are governed.** Top-level functions, member extension functions you import, and **captured variables** (`val outer = this`) bypass the rule entirely — they aren't implicit receivers. 2. **Every receiver type must be annotated.** If you mark `HtmlBuilder` but forget `BodyBuilder`, the protection has a hole at that level. There's no warning for an unmarked receiver. 3. **Qualified `this@label` always works**, so the rule never *forbids* outer access — it only forbids the *implicit* form. 4. **It's per-marker, not global** — different markers don't interact (see related question). 5. **No runtime enforcement**, so dynamically constructed or reflective DSL invocation gets no protection. ## Putting it together ```kotlin @DslMarker annotation class HtmlDsl @HtmlDsl class HtmlBuilder { fun meta() {} ; fun body(b: BodyBuilder.() -> Unit) {} } @HtmlDsl class BodyBuilder fun HtmlBuilder.extMeta() {} // extension, not an implicit-receiver member fun page(html: HtmlBuilder) = html.body { // meta() // ERROR: outer HtmlBuilder hidden (implicit) // extMeta() // would also be governed only if resolved via the hidden implicit receiver } ``` The key mental model: `@DslMarker` is a *resolution filter at compile time*, not a runtime gate, and it operates strictly on the implicit receiver chain.
- Does @DslMarker add any runtime overhead?No. It's resolved entirely at compile time during receiver resolution; the emitted bytecode is unaffected and there's no runtime check.
- Why is its retention BINARY rather than RUNTIME?The rule only needs to be enforced by the compiler (including across compiled modules), so it's kept in class files but doesn't need to be reflectable at runtime.
saying these in an interview costs you the question
- Claiming there is a runtime check or performance cost
- Saying @DslMarker has SOURCE or RUNTIME retention
- Thinking it can be applied to classes or functions directly
- Assuming it protects extension functions and captured variables
- Believing one annotated receiver protects the whole chain