错误的“this”被用在嵌套闭包中

我试图保持这个最小,但让我知道,如果我太小了。

假设你有这样的类层次结构,为生成HTML而设计(受到Kotlin教程的启发;下面是半伪代码):

class Tag { protected val children = arrayListOf<Tag>() operator fun String.unaryPlus() = children.add(Text(this)) } class TagWithChildren : Tag() { fun head(init: Head.() -> Unit) = initializeTag(Head(), init) fun script(init: Script.() -> Unit) = initializeTag(Script(), init) fun <T : Tag> initializeTag(tag: T, init: T.() -> Unit): T { tag.init() children.add(tag) return tag } } class Head : TagWithChildren() class Script : Tag() class Text(val str: Text) : Tag() 

注意Headheadscript方法,而Script没有。

现在你可以构建一个如下所示的模板:

 head { script { +"alert('hi');" } } 

哪个效果很好! 但是,如果传递给script的块尝试调用script上不可用的方法则可以调用Head上的方法 。 例如,

 head { script { script { +"alert('hi');" } } } 

不仅不是一个编译错误,它实际上相当于

 head { script { } script { +"alert('hi');" } } 

从模板作者的角度来看,这是超级混淆的。

有什么办法来防止方法查找像这样的范围内旅行? 我希望它看到最内层的范围。


更新11/24/2016:Kotlin 1.1-M03引入了范围控制,我相信这正是解决这个问题的方法。 https://blog.jetbrains.com/kotlin/2016/11/kotlin-1-1-m03-is-here/

目前的行为是故意的。 lambda中的代码可以访问所有封闭作用域的接收者。 未来版本的Kotlin可能会添加一个修改器,将接收器的lambda限制为仅在该接收器上调用方法,而不是封闭的范围,但在当前版本中,无法更改该行为。

作为一个解决方法,我可以让它在运行时失败,如果我改变类看起来像这样:

 open class Tag { operator fun String.unaryPlus() // pulled up from TagWithChildren, call protected method fun head(init: Head.() -> Unit) = addChild(Head()) fun script(init: Script.() -> Unit) = addChild(Head()) // throws in Tag open protected fun addChild(t: Tag) = throw IllegalArgumentException() } class TagWithChildren : Tag() { // overridden to not throw in subclass protected override fun addChild(t: Tag) = children.add(t) } 

这样, 每个 Tag都具有构建器方法(解决范围问题),但实际调用它们可能会导致运行时失败。