Udi Cohen
12/08/2021, 4:24 PM@KspTestingAnnotation
class KspTesterDelegation : MainInterface by MainImpl() {
fun method2(): Int {
return 10
}
}
interface MainInterface {
fun method1(): Int
}
class MainImpl : MainInterface {
override fun method1(): Int {
return 4
}
}
Getting all the declarations of KspTesterDelegation
will only provide its constructor and method2()
, while in KAPT we also get method1()
. I know the reason KAPT can do that is because the generated stub has the delegated methods inside the main class.
Is there a way I can still get the methods of a delegated class when using KSP?yigit
12/08/2021, 4:28 PMUdi Cohen
12/08/2021, 5:24 PMUdi Cohen
12/08/2021, 5:25 PMif (!classDeclaration.modifiers.contains(Modifier.ABSTRACT)) {
classDeclaration.superTypes.forEach {
val methodsFromDelegation = mutableListOf<KSFunctionDeclaration>()
val superType = it.resolve().declaration
if (superType is KSClassDeclaration && superType.classKind == ClassKind.INTERFACE) {
superType.getDeclaredFunctions().forEach { method -> methodsFromDelegation.add(method) }
}
}
}
// Add all methods in methodsFromDelegation to the enclosed elements of the main class, in case they're not there.
Which does:
- Get all the super types of the class and iterate only the interfaces
- Get all the methods of the interface
- For each method, if the method doesn’t already exists in the class (i.e. it’s overridden and implemented) then add it to the list of enclosed elements. It’s similar to the way X-Processing is adding synthetic getter/setter methods.
If the class is abstract then we can’t tell whether the interface methods will be added by a delegation or not, since KSP doesn’t provide any info that tells me there’s a delegation declared (i.e. by MainImpl()
). I chose to skip abstract classes for now.
Does this makes sense? Am I missing something? Is there a better way to do this?yigit
12/08/2021, 8:41 PMyigit
12/08/2021, 8:42 PMUdi Cohen
12/10/2021, 3:38 PMUdi Cohen
12/10/2021, 3:39 PM// Inside KspTypeElement, which extends javax.lang.model.element.TypeElement
// Called by getEnclosedElements()
/**
* Gets methods from a delegated object
*
* Example:
* // The methods in CoolInterface will be returned
* class ClassToProcessWithKsp : CoolInterface by CoolImpl() {...}
*
* Notes:
* - This will skip abstract classes since it can't be sure child classes aren't implementing
* interface methods.
* - The methods will have an @Override annotation, as expected in a KAPT output.
*/
private fun getMethodsFromDelegation(): List<KspExecutableElement> {
val methodsFromDelegation = mutableListOf<KSFunctionDeclaration>()
// kspInternalObject is a KSClassDeclaration
if (!kspInternalObject.modifiers.contains(com.google.devtools.ksp.symbol.Modifier.ABSTRACT)) {
kspInternalObject.superTypes.forEach {
if (it.origin != Origin.JAVA) {
val superType = it.resolve().declaration
if (superType is KSClassDeclaration && superType.classKind == ClassKind.INTERFACE) {
superType.getDeclaredFunctions().forEach { method -> methodsFromDelegation.add(method) }
}
}
}
}
if (methodsFromDelegation.isNotEmpty()) {
kspInternalObject.declarations.forEach {
if (it is KSFunctionDeclaration) {
// TODO expensive, find more ways to avoid calling if possible
val overrideeFunc = it.findOverridee()
if (methodsFromDelegation.contains(overrideeFunc)) {
methodsFromDelegation.remove(overrideeFunc)
}
}
}
}
return methodsFromDelegation
.map {
object : KspExecutableElement(it, this) {
// Since these methods come from a super type, we need to add a (synthetic) @Override
override fun getAnnotationMirrors(): List<AnnotationMirror> {
return listOf(KspSyntheticOverrideAnnotationMirror())
}
}
}
.toList()
}