@DslMarker

Stop Leaking Scopes

Without help, inside li { } you could still call body's p { }, because outer receivers are visible. Annotate your builders with a @DslMarker annotation and the compiler forbids implicit calls to an outer receiver.

Example

@DslMarker
annotation class MenuDsl

@MenuDsl
class Menu(val restaurant: String) {
    val sections = mutableListOf<Section>()
    fun section(title: String, init: Section.() -> Unit) { sections += Section(title).apply(init) }
}

@MenuDsl
class Section(val title: String) {
    val dishes = mutableListOf<Pair<String, Int>>()
    fun dish(name: String, price: Int) { dishes += name to price }
    // section("...") { }  here would NOT compile: Menu's members are hidden by @DslMarker
}

fun menu(restaurant: String, init: Menu.() -> Unit) = Menu(restaurant).apply(init)

fun main() {
    val m = menu("Saravana Bhavan") {
        section("Breakfast") {
            dish("Idli (2)", 60)
            dish("Masala Dosa", 110)
        }
        section("Beverages") {
            dish("Filter Coffee", 40)
        }
    }
    println(m.restaurant)
    m.sections.forEach { s ->
        println("  ${s.title}")
        s.dishes.forEach { (n, p) -> println("    %-14s Rs %d".format(n, p)) }
    }
}

Output

Saravana Bhavan
  Breakfast
    Idli (2)       Rs 60
    Masala Dosa    Rs 110
  Beverages
    Filter Coffee  Rs 40