Consider this Kotlin DSL code using @DslMarker to control scope. What will be printed when running buildHtml()?
import kotlin.text.StringBuilder @DslMarker annotation class HtmlTagMarker @HtmlTagMarker class Html { private val children = mutableListOf<Any>() fun body(init: Body.() -> Unit) { val body = Body() body.init() children.add(body) } override fun toString() = children.joinToString(separator = "\n") } @HtmlTagMarker class Body { private val children = mutableListOf<String>() fun p(text: String) { children.add("<p>$text</p>") } override fun toString() = children.joinToString(separator = "\n") } fun buildHtml(): String { val html = Html() html.body { p("Hello") // The following line would cause a compile error if uncommented: // this@Html.body { p("World") } } return html.toString() } fun main() { println(buildHtml()) }
Look at how toString() is implemented for Html and Body. The Html class collects Body objects, and Body collects paragraph strings.
The buildHtml() function creates an Html object and calls body on it. Inside body, it adds a paragraph with text "Hello". The toString() of Html joins its children, which are Body objects. The toString() of Body joins its paragraphs. So the output is just the paragraph tag with "Hello".
Why do Kotlin DSL authors use the @DslMarker annotation on their DSL marker annotations?
Think about how nested DSL blocks might have multiple receivers with similar function names.
The @DslMarker annotation is used to mark DSL scopes so that Kotlin compiler restricts access to implicit receivers from outer DSL scopes when inside nested DSL blocks. This prevents accidental calls to functions from the wrong scope and makes the DSL safer and clearer.
Examine the code below. Why does the call to p("World") inside the nested body cause a compile error?
import kotlin.text.StringBuilder @DslMarker annotation class HtmlTagMarker @HtmlTagMarker class Html { fun body(init: Body.() -> Unit) { val body = Body() body.init() } } @HtmlTagMarker class Body { fun p(text: String) {} fun body(init: Body.() -> Unit) {} } fun main() { val html = Html() html.body { p("Hello") body { p("World") // Compile error here } } }
Consider how @DslMarker restricts implicit receivers in nested DSL scopes.
The @DslMarker annotation marks both Html and Body classes with the same marker. This causes Kotlin to restrict access to implicit receivers from outer scopes when inside nested DSL blocks. The nested body call creates a new Body scope, so the outer Body scope's p function is not accessible implicitly, causing a compile error.
Choose the correct Kotlin code snippet that defines a DSL marker annotation named MyDslMarker and applies it to two DSL classes BuilderA and BuilderB.
Remember that @DslMarker must annotate an annotation class, not a regular class.
The correct way to create a DSL marker is to define an annotation class annotated with @DslMarker. Then apply that annotation to DSL classes. Option C correctly does this. Option C incorrectly applies @DslMarker to classes instead of an annotation class. Option C tries to make MyDslMarker a class, not an annotation. Option C misuses annotations on classes.
Given the following Kotlin DSL code using @DslMarker, how many items does the items list contain after buildList() is called?
import kotlin.collections.mutableListOf @DslMarker annotation class ListDsl @ListDsl class ListBuilder { val items = mutableListOf<String>() fun item(value: String) { items.add(value) } fun nested(init: NestedBuilder.() -> Unit) { val nested = NestedBuilder() nested.init() items.addAll(nested.items) } } @ListDsl class NestedBuilder { val items = mutableListOf<String>() fun item(value: String) { items.add(value) } } fun buildList(): List<String> { val builder = ListBuilder() builder.item("A") builder.nested { item("B") item("C") } return builder.items } fun main() { println(buildList()) }
Count all items added to the main builder's list, including those added from the nested builder.
The ListBuilder adds "A" first. Then it calls nested, which creates a NestedBuilder and adds "B" and "C" to its own list. After the nested block, items.addAll(nested.items) adds "B" and "C" to the main list. So the final list contains "A", "B", and "C" — 3 items total.