根据设计,
@DslMarker
注释只能应用于annotation class
。然而,一旦定义了我们自己的注释(例如,annotation class MyDslMarker
),它就可以不受限制地应用于任何目标。
在传统示例中(例如 Kotlin 文档、HTML 库、其他来源、Stack Overflow 以及几乎所有地方中找到的示例),
@MyDslMarker
注释的主要目标是类(或可能是对象)。
我只能找到一两次提到将其直接应用于函数,而没有提到属性或其他目标。
fun
的一个很好的例子,还解释了颜色样式是如何派生的
@DslMarker
专门设计用于限制从内部 lambda 内部对外部上下文接收器的隐式访问。它通常间接应用于表示上下文的类。
如果上下文类属于第三方库(您无法直接对其应用注释),您将按如下方式处理它(简化):
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) // Added a target "TYPE"
@DslMarker
annotation class MyDslMarker
// Can't apply the annotation here because it's outside our codebase
class ThirdPartyClassContext {
fun doPrint() = println("Third-party")
}
@MyDslMarker
class OuterContext {
// Applying to the receiver type
fun third(init: (@MyDslMarker ThirdPartyClassContext).() -> Unit) {
ThirdPartyClassContext().init()
//...
}
}
@MyDslMarker
class OuterThing
fun outerThing(init: OuterContext.() -> Unit): OuterThing {
val context = OuterContext()
context.init()
return OuterThing()
// in the real code, of course, it would be more like:
// return OuterThing(context),
// but that is beside the point
}
fun main() {
outerThing {
third {
doPrint()
// As desired: 'fun third(init: (ThirdPartyClassContext).() -> Unit): Unit'
// cannot be called implicitly in this context due to the receiver restriction
third { }
}
}
}
这是一个常见的用例,并且经常被涵盖 - 例如,如此处所述。
但是,这是
@MyDslMarker
仅有的两种经常讨论的使用场景,没有可靠的来源说明其在函数、属性或其他目标上的应用。
由于文档中既没有禁止也没有特别提及,因此在函数、属性甚至其他目标上使用此类注释可能具有有效的用例,类似于注释
ThirdPartyClass
类型的场景。
当我将
@MyDslMarker
应用于 Context
类中的方法,然后使用该方法时,它会显示为紫色(再次,有关颜色的更多详细信息请参见此处)。这表明:
fun third(init: (@MyDslMarker ThirdPartyClassContext).() -> Unit) {
ThirdPartyClassContext().init()
}
和
@MyDslMarker
fun third(init: (@MyDslMarker ThirdPartyClassContext).() -> Unit) {
ThirdPartyClassContext().init()
}
有些不同。
// At a minimum, these targets are available:
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.TYPE,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER
)
@DslMarker
annotation class MyDslMarker
不幸的是,官方文档和相关示例充其量是稀疏且缺乏信息的。
感谢您的任何见解!我相信这些问题的答案不仅对我有帮助,而且对许多其他正在搜索相关信息的人都有帮助,特别是因为 JetBrains 团队本身并没有提供太多说明。
除了颜色之外,上面的示例中实际上还有什么变化吗?
着色只是 IntelliJ 语法突出显示的一部分。它的目的只是突出显示哪些调用是 DSL 方法调用。它还允许您自定义 DSL 标记应使用的颜色,从而区分来自不同 DSL 的方法调用。您可以通过单击此装订线图标来完成此操作:
另请参阅:Kotlin - DSL 颜色样式 以及此功能的测试数据。
虽然理论上我们可以将其应用于任何可用的目标(例如函数、属性、属性获取器等),但这是否可行?如果可行,以什么方式实现?
IntelliJ 已经利用它来进行语法突出显示,正如我刚才所描述的。 IntelliJ 的这一功能不仅限于函数,还包括属性和对象声明。请参阅此目录中的其他文件以查看一些示例。
然而,就语言而言,只有应用于 types 的 DSL 标记才有效(至少目前如此)。您可以在 DslMarkerUtils.kt 中找到编译器如何处理此问题。该文件是编译器查找
@DslMarker
注释的唯一文件。 extractDslMarkerFqNames
仅从 KotlinType
中提取 DSL 标记:
fun extractDslMarkerFqNames(kotlinType: KotlinType): Set<FqName> {
val result = mutableSetOf<FqName>()
result.addAll(kotlinType.annotations.extractDslMarkerFqNames())
kotlinType.getAbbreviation()?.constructor?.declarationDescriptor?.run {
result.addAll(annotations.extractDslMarkerFqNames())
(this as? TypeAliasDescriptor)?.run {
result.addAll(extractDslMarkerFqNames(this.underlyingType))
}
}
kotlinType.constructor.declarationDescriptor?.getAllSuperClassifiers()?.asIterable()
?.flatMapTo(result) { it.annotations.extractDslMarkerFqNames() }
return result
}
您可以准确地看到它如何找到
@DslMarker
注释,例如 @MyDslMarker
:
@MyDslMarker ThirdPartyClassContext
。OuterContext
。它还在类型的超类中查找此类注释此方法由另一个采用
extractDslMarkerFqNames
的 ReceiverValue
方法调用,然后由 DslScopeViolationCallChecker.kt 使用。