Kotlin типа безопасный строитель DSLs, безопасность для самой внешней функции

Я собираюсь использовать официальный пример из документации, которая реализует DSL для некоторого создания HTML.

Начиная с Kotlin 1.1, аннотация @DslMarker позволяет нам ограничить область действия функций в наших классах, как в примере с аннотацией @HtmlTagMarker . Это дает нам ошибку при попытке написать неправильно структурированный код следующим образом:

 html { body { body { // this in an error, as it's a function call on the outside Html element } } } 

Однако это не предотвращает вложенность внешней функции, которая является точкой входа в DSL. Например, с примером, как сейчас, это можно записать без проблем:

 html { html { } } 

Есть ли способ сделать DSL более безопасным в этом отношении?

Вероятно, это можно как-то сделать более элегантным способом, но я могу предложить использовать @Deprecated аннотацию с DeprecationLevel.ERROR для функции с подходящей сигнатурой, определенной для типа приемника, например:

 @Deprecated("Cannot be used in a html block.", level = DeprecationLevel.ERROR) fun HtmlReceiver.html(action: HtmlReceiver.() -> Unit): Nothing = error("...") 

Или это может быть функция-член. Кстати, завершение IDE ведет себя по-другому, основываясь на том, является ли это расширением или членом.

Это приведет к тому, что вызовы наподобие внутреннего недействительны:

 html { html { // Error: Cannot be used in a html block. } } 

(демонстрация этого кода)

Функция верхнего уровня все еще может быть вызвана внутри блока DSL по его FQN, например com.example.html { } , поэтому этот трюк только защищает пользователей от неправильного вызова функции верхнего уровня.