Default receiver resolution in nested lambda may cause confusion when trying to implement typesafe builder

I am trying to implement my own version of typesafe DSL builder for creating web pages. I came into a problem that an extension function of the receiver of an outer lambda can be referenced by a call from a nested inner lambda, which the programmer may not mean to do so. The compiler seems unable to safe-guard the program. The problem can be illustrated as the following simplified example:

class Div {
}

class P {
}

class Anchor {
}

// Extension functions for Div to allow Div, P, Anchor elements nested in a Div element
fun Div.div(init: Div.() -> Unit) {
    // code here to create a Div element and add it to the parent Div and call init()...
}

fun Div.p(init: P.() -> Unit) {
    // code here to create a P element and add it to the parent Div and call init()...
}

fun Div.a(init: Anchor.() -> Unit) {
    // code here to create an Anchor element and add it to the parent Div and call init()...
}

// Extension functions for P to allow only Anchor element and text:
fun P.a(init; Anchor.() -> Unit) {
    // code here to create an Anchor element and add it to the parent P and call init()...
}

fun P.text(text: String) {
    // code here to add the text node to the P element
}

// Top level function to create a Div element and initialize it
fun div(init: Div.() -> Unit): Div {
    val divElement = Div()
    divElement.init()
    return divElement
}

// Test code
fun test() {
    div {
        div {
            a {
            }
        }
        p {
            div { 
                // this Div is a programming mistake because a Div cannot be included in a P, 
                // and P does not have an extension function div() to allow this to happen. 
                // However, the compiler allows this to happen because the div{ } call in p{ } is 
                // resolved to call the receiver in the outer div{ } lambda, which allows a div{ } call.
            }
        }
    }
}

The problem here is that the implicit “this” receiver is resolved to the outer scope if the current scope does not apply.

I wonder if there is a way to workaround it; otherwise the DSL cannot be made syntactically safe.

Regards,
VN

1 Like

See the response(s) to my StackOverflow question, dated January 2016.

Here’s the YouTrack issue: https://youtrack.jetbrains.com/issue/KT-11551
And the draft design proposal: Scope control for implicit receivers by abreslav · Pull Request #38 · Kotlin/KEEP · GitHub