Reputation: 89668
I'm going to use the official example from the documentation that implements a DSL for some HTML creation.
Since Kotlin 1.1, the @DslMarker
annotation allows us to restrict the scope of the functions in our classes, like the example does with the @HtmlTagMarker
annotation. This gives us an error when trying to write incorrectly structured code like this:
html {
body {
body { // this in an error, as it's a function call on the outside Html element
}
}
}
However, this doesn't prevent nesting the outermost function, which is the entry point to the DSL. For example, with the example as it is now, this can be written down without problems:
html {
html {
}
}
Is there any way to make a DSL safer in this regard?
Upvotes: 10
Views: 490
Reputation: 148159
Probably this can somehow be done in a more elegant way, but I can suggest using the @Deprecated
annotation with DeprecationLevel.ERROR
on a function with a matching signature defined for the receiver type, for example:
@Deprecated("Cannot be used in a html block.", level = DeprecationLevel.ERROR)
fun HtmlReceiver.html(action: HtmlReceiver.() -> Unit): Nothing = error("...")
Or this can be a member function. By the way, the IDE completion behaves a bit differently based on whether it is an extension or a member.
This will make the calls like the inner one invalid:
html {
html { // Error: Cannot be used in a html block.
}
}
The top-level function can still be called inside a DSL block by its FQN e.g. com.example.html { }
, so this trick only protects the users from calling the top level function by mistake.
Upvotes: 11