Skip to content

Commit

Permalink
Fix accessor generation for suspend methods
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrill-halter-ergon committed Jul 9, 2024
1 parent 3535156 commit 3362161
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,4 @@
public @interface GenerateAccessor {

Class<?> value() default Void.class;

// We need to explicitly specify this since the information is lost during annotation processing
boolean isNullableSuspendFun() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ object CrystalWrap {
fieldName: String
): List<T>? = (changes[fieldName] ?: doc[fieldName])?.let { value ->
catchTypeConversionError(fieldName, value) {
value as List<T>
(value as List<Any>).map { it as T }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ package com.schwarz.crystalprocessor.generation

import com.schwarz.crystalprocessor.CoachBaseBinderProcessor
import com.schwarz.crystalprocessor.ProcessingContext
import com.schwarz.crystalprocessor.model.accessor.CblGenerateAccessorHolder
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.OriginatingElementsHolder
import java.io.File

import java.io.IOException

import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.tools.StandardLocation
import kotlin.io.path.createDirectories
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists
import kotlin.io.path.outputStream

class CodeGenerator(private val filer: Filer) {

Expand All @@ -30,6 +37,59 @@ class CodeGenerator(private val filer: Filer) {
}
}

@Throws(IOException::class)
fun generateAndFixAccessors(
entityToGenerate: FileSpec,
generateAccessors: MutableList<CblGenerateAccessorHolder>,
processingEnvironment: ProcessingEnvironment
) {
ClassName(entityToGenerate.packageName, entityToGenerate.name)?.apply {
ProcessingContext.createdQualifiedClazzNames.add(this)
}

val codePath = processingEnvironment.options[CoachBaseBinderProcessor.KAPT_KOTLIN_GENERATED_OPTION_NAME]
val fileWithHeader = entityToGenerate.toBuilder().addFileComment(HEADER).build()

val fixedFileString = generateAccessors.fold(fileWithHeader.toString()) { acc, generateAccessor ->
if (generateAccessor.memberFunction != null && generateAccessor.memberFunction.isSuspend) {
acc.replace(Regex("(${generateAccessor.memberFunction.name}\\([^)]*\\)):\\s*Unit(\\s*=)"), "$1$2")
} else {
acc
}
}

// used for kapt returns null for legacy annotationprocessor declarations
if (codePath != null) {
val directory = File(codePath).toPath()
require(directory.notExists() || directory.isDirectory()) {
"path $directory exists but is not a directory."
}
val outputPath = directory.resolve(fileWithHeader.relativePath)
outputPath.parent.createDirectories()
outputPath.outputStream().bufferedWriter().use { it.write(fixedFileString) }
} else {
val originatingElements = fileWithHeader.members.asSequence()
.filterIsInstance<OriginatingElementsHolder>()
.flatMap { it.originatingElements.asSequence() }
.toSet()
val filerSourceFile = filer.createResource(
StandardLocation.SOURCE_OUTPUT,
fileWithHeader.packageName,
"${fileWithHeader.name}.kt",
*originatingElements.toTypedArray(),
)
try {
filerSourceFile.openWriter().use { it.write(fixedFileString) }
} catch (e: Exception) {
try {
filerSourceFile.delete()
} catch (ignored: Exception) {
}
throw e
}
}
}

companion object {

private val HEADER = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import com.squareup.kotlinpoet.TypeName

class CblGenerateAccessorHolder(
private val sourceClassTypeName: TypeName,
private val memberFunction: SourceMemberFunction?,
private val memberProperty: SourceMemberField?
val memberFunction: SourceMemberFunction?,
val memberProperty: SourceMemberField?
) {

fun accessorFunSpec(): FunSpec? {
Expand All @@ -32,11 +32,11 @@ class CblGenerateAccessorHolder(
memberFunction.name
)

val isNullableSuspendFun = memberFunction.generateAccessor?.isNullableSuspendFun ?: false

if (isNullableSuspendFun) {
methodBuilder.returns(memberFunction.returnTypeName.copy(nullable = true))
} else {
// We only specify a return value if the function is non-suspending. If the function
// is suspending, we cannot safely obtain the return type. Since KotlinPoet doesn't
// allow us to use implicit return types, we default to Unit and remove it later
// in the CodeGenerator.
if (!memberFunction.isSuspend) {
methodBuilder.returns(memberFunction.returnTypeName)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,7 @@ data class SourceModel(private val sourceElement: Element) : ISourceModel, IClas
}
}

val returnType = if (isSuspend) {
val continuationParam = (it.parameters.last()) as Symbol.VarSymbol
val continuationParamType = continuationParam.type as Type.ClassType
val wildcardTypeParam = continuationParamType.allparams().first() as Type.WildcardType
wildcardTypeParam.type.asTypeName().javaToKotlinType()
} else {
it.returnType.asTypeName().javaToKotlinType().copy(it.getAnnotation(Nullable::class.java) != null)
}
val returnType = it.returnType.asTypeName().javaToKotlinType().copy(it.getAnnotation(Nullable::class.java) != null)

relevantStaticsFunctions.add(
SourceMemberFunction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ class ModelWorker(override val logger: Logger, override val codeGenerator: CodeG
generateInterface(generatedInterfaces, baseModelHolder, useSuspend, typeConvertersByConvertedClass)
}

process(workSet.entities, generatedInterfaces, useSuspend, typeConvertersByConvertedClass) {
processAndFixAccessors(workSet.entities, generatedInterfaces, useSuspend, typeConvertersByConvertedClass) {
EntityGeneration().generateModel(it, useSuspend, typeConvertersByConvertedClass)
}

process(workSet.wrappers, generatedInterfaces, useSuspend, typeConvertersByConvertedClass) {
processAndFixAccessors(workSet.wrappers, generatedInterfaces, useSuspend, typeConvertersByConvertedClass) {
WrapperGeneration().generateModel(it, useSuspend, typeConvertersByConvertedClass)
}

Expand Down Expand Up @@ -105,6 +105,35 @@ class ModelWorker(override val logger: Logger, override val codeGenerator: CodeG
}
}

private fun <T : BaseEntityHolder> processAndFixAccessors(
models: List<T>,
generatedInterfaces: MutableSet<String>,
useSuspend: Boolean,
typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>,
generate: (T) -> FileSpec
) {
for (model in models) {
generateInterface(generatedInterfaces, model, useSuspend, typeConvertersByConvertedClass)
documentationGenerator?.addEntitySegments(model)
schemaGenerator?.addEntity(model)
entityRelationshipGenerator?.addEntityNodes(model)
generate(model).apply {
if (model.generateAccessors.isNotEmpty()) {
codeGenerator.generateAndFixAccessors(
this,
model.generateAccessors,
processingEnv
)
} else {
codeGenerator.generate(
this,
processingEnv
)
}
}
}
}

private fun generateInterface(
generatedInterfaces: MutableSet<String>,
holder: BaseEntityHolder,
Expand Down
18 changes: 17 additions & 1 deletion demo/src/main/java/com/schwarz/crystaldemo/tesst/Task.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,31 @@ open class Task {
return ""
}

@GenerateAccessor
fun ultraComplexQueryReturningEntity(storeId: String): List<TaskEntity> {
return emptyList()
}

@GenerateAccessor
suspend fun suspendingUltraComplexQueryReturningList(storeId: String): List<String> {
return listOf("")
}

@GenerateAccessor(isNullableSuspendFun = true)
@GenerateAccessor()
suspend fun suspendingUltraComplexQueryReturningNullableList(storeId: String): List<String>? {
return null
}

@GenerateAccessor()
suspend fun suspendingUltraComplexQueryReturningEntity(storeId: String): List<TaskEntity> {
return emptyList()
}

@GenerateAccessor()
suspend fun suspendingUltraComplexQueryWithAVeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryLongName(storeId: String): List<TaskEntity> {
return emptyList()
}

}
//
// override fun documentId(): String {
Expand Down

0 comments on commit 3362161

Please sign in to comment.