Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions yawn-processor/src/main/kotlin/com/faire/ksp/KspExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.devtools.ksp.getVisibility
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ksp.toClassName
Expand Down Expand Up @@ -54,3 +55,7 @@ private fun KSClassDeclaration.getClassDeclarationNestedChain(): Sequence<KSClas
internal fun ClassName.getUniqueSimpleName(): String {
return simpleNames.joinToString("_")
}

internal fun KSClassDeclaration.getAllPropertiesWithAllAnnotations(): Sequence<KSPropertyDeclaration> {
return KspPropertiesWithAnnotationsResolver(this).resolve()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.faire.ksp

import com.google.devtools.ksp.getDeclaredProperties
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration

/**
* Resolves all properties from a [KSClassDeclaration] by walking the full inheritance chain
* using [KSClassDeclaration.getDeclaredProperties] at each level, then merging annotations
* from multiple sources when the same property name appears at more than one level.
*
* This is entirely to bypass [this bug](https://github.com/google/ksp/issues/2833) with the
* [KSClassDeclaration.getAllProperties] API, which was introduced in KSP2 and is impacting Yawn.
* We can remove this once that is fixed.
*/
internal class KspPropertiesWithAnnotationsResolver(
private val root: KSClassDeclaration,
) {
fun resolve(): Sequence<KSPropertyDeclaration> {
val propertiesByName = mutableMapOf<String, MutableList<KSPropertyDeclaration>>()

val visited = mutableSetOf<String>()
val queue = ArrayDeque<KSClassDeclaration>()
queue.add(root)

while (queue.isNotEmpty()) {
val current = queue.removeFirst()
val qualifiedName = current.qualifiedName?.asString() ?: continue
if (!visited.add(qualifiedName)) {
continue
}

for (property in current.getDeclaredProperties()) {
val name = property.simpleName.asString()
propertiesByName.getOrPut(name) { mutableListOf() }.add(property)
}

for (superTypeRef in current.superTypes) {
val parent = superTypeRef.resolve().declaration as? KSClassDeclaration ?: continue
queue.add(parent)
}
}

return propertiesByName.values.asSequence().map { declarations ->
declarations.singleOrNull() ?: MergedKSPropertyDeclaration(declarations)
}
}
}

/**
* A [KSPropertyDeclaration] wrapper that merges annotations from multiple declarations
* of the same property across the inheritance chain.
*
* Delegates everything to the most-derived (first) declaration, but returns the union
* of all annotations from every declaration in the chain, deduplicated by fully-qualified
* annotation type name (keeping the most-derived one when duplicates exist).
*/
private class MergedKSPropertyDeclaration(
private val declarations: List<KSPropertyDeclaration>,
) : KSPropertyDeclaration by declarations.first() {

override val annotations: Sequence<KSAnnotation>
get() {
val seen = mutableSetOf<String>()
return declarations.asSequence()
.flatMap { it.annotations }
.filter { annotation ->
val type = annotation.annotationType.resolve()
val name = type.declaration.qualifiedName?.asString()
name == null || seen.add(name)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.faire.yawn.generators.objects

import com.faire.ksp.getAllPropertiesWithAllAnnotations
import com.faire.ksp.getEffectiveVisibility
import com.faire.yawn.generators.addGeneratedAnnotation
import com.faire.yawn.project.YawnCompositeQueryProjection
Expand Down Expand Up @@ -69,7 +70,7 @@ internal object YawnProjectionRefObjectGenerator : YawnReferenceObjectGenerator
val type: TypeName,
)

val properties = yawnContext.classDeclaration.getAllProperties()
val properties = yawnContext.classDeclaration.getAllPropertiesWithAllAnnotations()
.filter { yawnContext.classDeclaration.isConstructorProperty(it) }
.mapIndexed { idx, property ->
Property(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.faire.yawn.generators.types

import com.faire.ksp.getAllPropertiesWithAllAnnotations
import com.faire.yawn.YawnTableDef
import com.faire.yawn.generators.addGeneratedAnnotation
import com.faire.yawn.generators.properties.ColumnDefGenerator
Expand Down Expand Up @@ -114,7 +115,7 @@ internal object EmbeddedTypeGenerator : YawnEmbeddableTypeGenerator {
.addSuperclassConstructorParameter("%S", pathPrefix)

propertyDeclaration.typeAsClassDeclaration()
?.getAllProperties()
?.getAllPropertiesWithAllAnnotations()
?.forEach { property ->
val propertySpec = ColumnDefGenerator.generate(yawnContext, property, pathPrefixes)
typeSpec.addProperty(propertySpec)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.faire.yawn.processors

import com.faire.ksp.getAllPropertiesWithAllAnnotations
import com.faire.ksp.getEffectiveVisibility
import com.faire.yawn.YawnDef
import com.faire.yawn.YawnTableDefParent
Expand Down Expand Up @@ -109,7 +110,7 @@ internal abstract class BaseYawnProcessor(
.superclass(yawnContext.superClassName)
.run { additionalClassBuilder(yawnContext, this) }

for (propertyDeclaration in yawnContext.classDeclaration.getAllProperties()) {
for (propertyDeclaration in yawnContext.classDeclaration.getAllPropertiesWithAllAnnotations()) {
val property = generateProperty(yawnContext, propertyDeclaration)
?: continue

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.faire.yawn.processors

import com.faire.ksp.getAllPropertiesWithAllAnnotations
import com.faire.yawn.YawnDef
import com.faire.yawn.YawnEntity
import com.faire.yawn.YawnTableDef
Expand Down Expand Up @@ -102,7 +103,7 @@ internal class YawnEntityProcessor(codeGenerator: CodeGenerator) : BaseYawnProce
private fun generateEmbeddedDefinitions(
yawnContext: YawnContext,
): List<TypeSpec> {
return yawnContext.classDeclaration.getAllProperties()
return yawnContext.classDeclaration.getAllPropertiesWithAllAnnotations()
.mapNotNull { property ->
val generator = when {
property.isEmbeddedId() -> EmbeddedIdTypeGenerator
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.faire.yawn.util

import com.faire.ksp.getAllPropertiesWithAllAnnotations
import com.faire.ksp.getAnnotationsByType
import com.faire.ksp.isAnnotationPresent
import com.faire.yawn.YawnEntity
Expand Down Expand Up @@ -130,7 +131,7 @@ internal fun KSPropertyDeclaration.getHibernateForeignKeyReference(): ForeignKey
// if it is a composite key we just assume it is the PK on the other end
val isCompositeKey = joinColumns.size > 1

return declaration.getAllProperties()
return declaration.getAllPropertiesWithAllAnnotations()
.filter { property ->
when {
isCompositeKey -> property.isAnnotationPresent<EmbeddedId>()
Expand Down
Loading