Show how to define and apply a @DslMarker annotation. Where exactly must the marker be placed for the scope restriction to take effect?
answer
- @DslMarker target = ANNOTATION_CLASS only
- Put the custom annotation on receiver CLASSES
- Or on the function-type receiver: @M Builder.()->Unit
- Must reach the receiver TYPE to matter
- One shared marker for the whole DSL
basics
~10 sCreate an annotation class and mark it with @DslMarker. Then put that annotation on the builder/receiver classes (or on the lambda's receiver type). The restriction kicks in when two implicit receivers share that marker.
solid answer
~40 sDefine `@DslMarker annotation class MyDsl`. @DslMarker is a meta-annotation, so MyDsl now propagates the DSL-scope behavior. Apply MyDsl to each receiver type that participates in the DSL — typically the builder classes (`@MyDsl class TableBuilder`). The compiler then treats any two implicit receivers both annotated with MyDsl as conflicting: inside the inner one, the outer is hidden. You can also annotate the function-type receiver directly: `fun row(init: @MyDsl RowBuilder.() -> Unit)`, useful when you don't own/can't annotate the receiver class. The annotation propagates through type usages, so annotating the class is the most common and robust approach. The marker must reach the receiver TYPE; putting it elsewhere (a function, a property) does nothing for scope control.
code
kotlin · 14 lines@DslMarker
annotation class TableDsl
@TableDsl class TableBuilder { fun row(init: RowBuilder.() -> Unit) {} }
@TableDsl class RowBuilder { fun cell(text: String) {} }
fun table(init: TableBuilder.() -> Unit) = TableBuilder().apply(init)
fun demo() = table {
row {
cell("a") // OK
// row { } // ERROR: outer TableBuilder is shadowed
}
}go deeper
Recognizes that you define an annotation and mark it @DslMarker, but may be fuzzy on exactly where to apply it.
Correctly applies the custom annotation to receiver classes and knows the effect on nested calls.
Knows the alternative of annotating the function-type receiver for types you don't own, and that @DslMarker's target is ANNOTATION_CLASS.
Designs the marker strategy for a whole library, deciding single vs multiple markers and ensuring every receiver type is covered consistently.
## Step 1 — define the marker `@DslMarker` is a built-in **meta-annotation** in `kotlin` package. You apply it to *your own* annotation class: ```kotlin @DslMarker annotation class TableDsl ``` That's the whole definition. `TableDsl` now carries DSL-scope semantics. (`@DslMarker` is declared with `@Target(AnnotationTarget.ANNOTATION_CLASS)`, so it can *only* be placed on an annotation class — the compiler rejects it elsewhere.) ## Step 2 — apply it to the receiver types The scope restriction is keyed on the **type of the implicit receiver**. So the marker must end up on those types. Two ways: **A) Annotate the builder class (most common):** ```kotlin @TableDsl class TableBuilder { fun row(init: RowBuilder.() -> Unit) {} } @TableDsl class RowBuilder { fun cell(text: String) {} } ``` **B) Annotate the receiver of the function type** (when you can't modify the class): ```kotlin fun table(init: @TableDsl TableBuilder.() -> Unit) = TableBuilder().apply(init) ``` The `@TableDsl` before `TableBuilder.()` annotates the *receiver type usage*. This is handy for builders over third-party types. ## What now becomes an error ```kotlin table { // this: TableBuilder row { // this: RowBuilder row { } // ERROR: outer TableBuilder hidden (same marker) cell("x") // OK: cell belongs to RowBuilder (nearest) } } ``` Before the marker, `row { }` inside `row { }` would have compiled. ## Where it must NOT go (no effect) - On a function/method: pointless for scope control. - On a property: pointless. - Forgetting to mark even one receiver class breaks the chain at that level — that receiver won't be shadowed. ## Multiple markers If two receiver types use **different** markers, no shadowing happens between them. So a single, shared marker for the whole DSL is usually what you want; use separate markers deliberately when you actually want cross-scope access (e.g. an HTML DSL nested inside an unrelated config DSL). ## Recap of the mechanism 1. `@DslMarker` on annotation class → makes it a DSL marker. 2. Marker on receiver types → activates same-marker shadowing. 3. Shadowing → only nearest implicit receiver of that marker is unqualified-accessible. 4. Qualified `this@label` always escapes.
- Can you apply @DslMarker to a function?No. @DslMarker has @Target(ANNOTATION_CLASS), so the compiler only allows it on an annotation class. You apply the resulting custom annotation (not @DslMarker itself) to receiver types.
- When would you annotate the function-type receiver instead of the class?When you don't own the receiver class (e.g. a builder over a third-party or stdlib type) and therefore can't put the marker on the class declaration.
saying these in an interview costs you the question
- Putting @DslMarker (rather than the custom annotation) on the builder classes
- Annotating functions/properties expecting scope control
- Forgetting that the marker must reach the receiver type
- Believing each nested level needs a different marker
- Thinking applying it once anywhere covers the whole DSL automatically