Is there a way to restrict extension methods in DSLs?
Say I have a class structure like this:
class Outer {
fun middle(op: Middle.() -> Unit): Middle {
val middle = Middle()
middle.op()
return middle
}
}
class Middle {
fun inner(op: Inner.() -> Unit): Inner {
val inner = Inner()
inner.op()
return inner
}
}
class Inner
fun outer(op: Outer.() -> Unit): Outer {
val outer = Outer()
outer.op()
return outer
}
I can then create a call like so:
outer {
middle {
inner {
middle { } // Here is the problem
}
}
}
My problem is that the marked middle { }
call is confusing, as it adds a Middle
to the Outer
when it looks like it is adding to the Inner
.
Is there a way to not allow the middle { }
call?
You can use a workaround with deprecated
:
class Outer {
fun middle(op: Middle.() -> Unit): Middle {...}
@Deprecated("can not be used inside a Outer block", level = DeprecationLevel.ERROR)
fun outer(op: Outer.() -> Unit): Outer = TODO()
}
class Middle {
fun inner(op: Inner.() -> Unit): Inner {...}
@Deprecated("can not be used inside a Middle block", level = DeprecationLevel.ERROR)
fun middle(op: Middle.() -> Unit): Middle = TODO()
}
class Inner {
@Deprecated("can not be used inside a Inner block", level = DeprecationLevel.ERROR)
fun inner(op: Inner.() -> Unit): Inner = TODO()
}
Now the compiler will give you an error, and IDE will not suggest a wrong function in the completion:
outer {
middle {
inner {
middle { } // error
}
}
}
But you really should not do it for big DSLs. It is better to wait for https://youtrack.jetbrains.com/issue/KT-11551 as @KirillRakhman suggested.
Edit: After I fixed my example, it became much smaller. With one dummy function for a class it is not that much of boilerplate after all.
The official way to restrict scope is DslMarker.
It cannot help in some cases (when you need to annotate java sources, for example) - and here @Deprecated
is used. But try DslMarker
first.
@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class Scope
@Scope
class Outer {
fun middle(op: Middle.() -> Unit): Middle { /**/ }
}
@Scope
class Middle {
fun inner(op: Inner.() -> Unit): Inner {/**/ }
}
class Inner
Thus the last middle call is not compilable anymore.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With