-
Notifications
You must be signed in to change notification settings - Fork 336
Description
Here is a real-world example that could benefit from sealed hierarchies in the KSP API:
/**
* The binary name of this class-like declaration on the JVM, as specified by
* [The Java® Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.1).
*/
val KSClassDeclaration.jvmBinaryName: String
get() = when (val parent = parentDeclaration) {
// this is a top-level class-like declaration -> canonical name / fully qualified name (same for top-level)
null -> this.qualifiedName!!.asString()
// this is a member class-like declaration -> binary name of immediately enclosing declaration + $ + simple name
is KSClassDeclaration -> parent.jvmBinaryName + '$' + this.simpleName.asString()
is KSFunctionDeclaration, is KSPropertyDeclaration ->
error("jvmBinaryName isn't implemented for local class-like declarations but $this seems to be one")
is KSTypeAlias, is KSTypeParameter -> error("$parent shouldn't be the parentDeclaration of $this")
else -> error("$this has an unknown parentDeclaration: $parent")
}
If KSDeclaration
was a sealed interface
, there would be no need to provide an else
. In the hypothetical scenario that a new subtype of KSDeclaration
was introduced, a compile error would arise because the when
wouldn't cover all possible cases anymore (which is desirable if one wants to be exhaustive and handle all cases correctly).
jvmBinaryName
could also be implemented using a visitor and can even also result in compiler errors (because of new non-overridden methods from KSVisitor
) when new KSNode
s are introduced, however the implementation is more complex and requires more code:
val KSClassDeclaration.jvmBinaryName get() = accept(JvmBinaryNameVisitor, data = this)
private object JvmBinaryNameVisitor : KSVisitor<KSClassDeclaration, String> {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: KSClassDeclaration): String {
val parent = classDeclaration.parentDeclaration
return if (parent == null) {
classDeclaration.qualifiedName!!.asString()
} else {
parent.accept(this, classDeclaration) + '$' + classDeclaration.simpleName.asString()
}
}
private fun localError(clazz: KSClassDeclaration): Nothing =
error("jvmBinaryName isn't implemented for local class-like declarations but $clazz seems to be one")
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: KSClassDeclaration) =
localError(clazz = data)
override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: KSClassDeclaration) =
localError(clazz = data)
private fun wrongParentError(parent: KSDeclaration, child: KSClassDeclaration): Nothing =
error("$parent shouldn't be the parentDeclaration of $child")
override fun visitTypeAlias(typeAlias: KSTypeAlias, data: KSClassDeclaration) =
wrongParentError(parent = typeAlias, child = data)
override fun visitTypeParameter(typeParameter: KSTypeParameter, data: KSClassDeclaration) =
wrongParentError(parent = typeParameter, child = data)
private fun error(symbol: KSNode): Nothing =
error("Unexpected symbol $symbol, JvmBinaryNameVisitor should only visit subtypes of KSDeclaration")
// repeat for all other visit<Node> methods, if a new one is added, there will be a compile error until the method is overridden
override fun visitNode(node: KSNode, data: KSClassDeclaration) = error(node)
override fun visitAnnotated(annotated: KSAnnotated, data: KSClassDeclaration) = error(annotated)
// ...
}
I think the following types would benefit from being sealed
: KSNode
, KSAnnotated
, KSDeclarationContainer
, KSReferenceElement
, KSModifierListOwner
, KSPropertyAccessor
, KSDeclaration
(the one that would benefit the example above) and also com.google.devtools.ksp.processing.PlatformInfo
.