What does this@Html mean inside a nested builder, and when do you need qualified this (this@Label) instead of a bare this or an unqualified call?
answer
- this = innermost receiver
- this@Label = specific enclosing receiver
- Label = function or class name that introduced it
- Needed for shadowing, @DslMarker, passing the receiver value
- Label is lexical / compile-time
basics
~10 sthis@Html means 'the Html receiver specifically', not whichever receiver is innermost. You use it when an outer receiver is shadowed by an inner one and you need to reach the outer object explicitly.
solid answer
~40 sBare this always refers to the innermost implicit receiver. Qualified this — this@Label — selects a specific enclosing receiver, where Label is the name of the function or class that introduced it (this@html for the builder function html, or this@Html when the receiver type/class itself supplies the label). You need it when (1) an inner receiver shadows an outer member of the same name and you want the outer one, (2) the outer member is hidden by a @DslMarker scope and the compiler forces qualification, or (3) you want to pass the outer receiver object itself (e.g., this@html as an argument). The label is resolved lexically to the nearest enclosing scope with that name. Without it, you only ever get the innermost this and the innermost-matching member.
code
kotlin · 15 lines@DslMarker annotation class HtmlDsl
@HtmlDsl class Html { fun shared() = "html" }
@HtmlDsl class Body { fun shared() = "body" }
fun html(b: Html.() -> Unit) = Html().b()
fun Html.body(b: Body.() -> Unit) = Body().b()
html {
body {
shared() // "body" (innermost)
// html-level shared() unqualified is REJECTED by @DslMarker
this@html.shared() // "html" - explicit, allowed
}
}go deeper
Recognizes this@Html points at a named outer receiver rather than the closest one.
Explains label source (function/class name) and the shadowing case where it's required.
Ties qualified this to @DslMarker enforcement and to passing the receiver value itself.
Considers DSL design where labels are intentionally chosen/renamed to keep outer-access readable and unambiguous.
## Bare this vs qualified this - **`this`** (unqualified) = the **innermost** implicit receiver. - **`this@Label`** = a **specific** enclosing receiver, chosen by label. The label after `@` is the name of the construct that introduced the receiver: a **function name** (e.g. `this@html` for the lambda passed to `html(...)`) or a **class/type name** when you're inside that class's member or an extension on it (`this@Html`). For receiver lambdas in a builder, you typically label by the builder function: `this@html`, `this@body`. ```kotlin class Html { val name = "html" ; fun tag() = "H" } class Body { val name = "body" ; fun tag() = "B" } fun html(b: Html.() -> Unit) = Html().b() fun Html.body(b: Body.() -> Unit) = Body().b() html { body { this // Body (innermost) this@body // Body (explicit, same as this here) this@html // Html (the OUTER receiver) tag() // "B" (Body.tag — innermost) [email protected]() // "H" (reach outer explicitly) [email protected] // "html" } } ``` ## When you NEED qualified this 1. **Shadowing**: inner and outer share a member name; unqualified picks the inner, so `[email protected]` is the only way to the outer. 2. **@DslMarker enforcement**: if the DSL marks its scopes with a `@DslMarker` annotation, the compiler **forbids** an unqualified call that would resolve to an outer marked receiver. The fix is exactly `[email protected]()`. Qualified this is the escape hatch the marker leaves open. 3. **Passing the receiver object**: when you need the receiver *value itself* (not one of its members) — e.g., `register(this@html)` — you must name it, and `this@html` is how. ## What it is NOT - It does not change resolution rules for other names; it only re-points one specific `this` reference. - The label is **lexical** — it binds to the nearest enclosing scope with that name, resolved at compile time. - It is unrelated to qualified `super@` (which is for supertype dispatch). ## Picking the label - Lambda passed to `html { }` → label is `html` (the function) → `this@html`. - Inside a method of class `Html` → `this@Html` (the class). - If two enclosing scopes share a name, rename or restructure; the nearest one wins. ## Mental model Bare `this` = "the current innermost object". `this@X` = "the object from scope X", letting you climb the receiver stack deliberately.
- Inside body { }, are this and this@body the same object?Yes. Bare this is the innermost receiver, which is the Body, and this@body names that same Body explicitly. They're interchangeable there.
- What is the label exactly — the type name or the function name?It's the name of the construct that introduced the receiver: the builder function's name for a receiver lambda (this@html), or the class name when you're inside that class's member (this@Html).
Like saying 'this room' vs 'the upstairs room' — the label tells the compiler which level you mean.
saying these in an interview costs you the question
- Saying this@Html refers to the innermost receiver
- Confusing this@Label with super@Type
- Thinking the label must always be a class name
- Believing qualified this changes resolution for unrelated calls
- Not knowing it's the way to satisfy @DslMarker when you truly want the outer receiver