Skip to content

Commit

Permalink
feat(entry-gen): Manage type parameters with classgraph
Browse files Browse the repository at this point in the history
  • Loading branch information
piiertho committed Jan 13, 2025
1 parent 816d24b commit 87b5a5f
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package godot.annotation.processor.classgraph.constants

const val BOOLEAN = "boolean"
const val INT = "int"
const val LONG = "long"
const val FLOAT = "float"
const val DOUBLE = "double"
const val BYTE = "byte"
const val SHORT = "short"

val jvmPrimitives = listOf(
BOOLEAN,
INT,
LONG,
FLOAT,
DOUBLE,
BYTE,
SHORT
)

const val VOID = "void"

const val STRING = "java.lang.String"

const val JVM_OBJECT = "java.lang.Object"

const val SET = "java.util.Set"
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import godot.annotation.RpcMode
import godot.annotation.Sync
import godot.annotation.Tool
import godot.annotation.TransferMode
import godot.annotation.processor.classgraph.constants.SET
import godot.annotation.processor.classgraph.models.TypeDescriptor
import godot.entrygenerator.model.ColorNoAlphaHintAnnotation
import godot.entrygenerator.model.DirHintAnnotation
Expand All @@ -39,7 +40,9 @@ import godot.entrygenerator.model.RegisterPropertyAnnotation
import godot.entrygenerator.model.RegisterSignalAnnotation
import godot.entrygenerator.model.RpcAnnotation
import godot.entrygenerator.model.ToolAnnotation
import io.github.classgraph.AnnotationEnumValue
import io.github.classgraph.AnnotationInfo
import io.github.classgraph.ClassRefTypeSignature
import io.github.classgraph.FieldInfo
import io.github.classgraph.ScanResult

Expand Down Expand Up @@ -69,7 +72,21 @@ fun AnnotationInfo.mapToGodotAnnotation(parentDeclaration: Any): GodotAnnotation
require(parentDeclaration is FieldInfo) {
"EnumFlag annotation should be placed on property."
}
val enumValues = TypeDescriptor(parentDeclaration).enumValues

val typeDescriptor = parentDeclaration.typeSignature

require(typeDescriptor is ClassRefTypeSignature)

require(typeDescriptor.fullyQualifiedClassName == SET) {
"Property annotated with EnumFlag should be of type $SET"
}

val typeArgument = typeDescriptor.typeArguments.first().typeSignature as ClassRefTypeSignature

val enumValues = this@ScanResult.getClassInfo(typeArgument.fullyQualifiedClassName)
.fieldInfo
.filter { it.typeDescriptor == typeArgument }
.map { it.name }
EnumFlagHintStringAnnotation(enumValueNames = enumValues, source = this)
}
IntFlag::class.java.name -> IntFlagHintAnnotation(
Expand Down Expand Up @@ -131,10 +148,10 @@ private fun AnnotationInfo.getTransferMode(): godot.entrygenerator.model.Transfe

@Suppress("UNCHECKED_CAST")
private fun <T : Number> AnnotationInfo.provideRangeHintAnnotation(stepDefault: T): RangeHintAnnotation<T> {
val start = parameterValues.getValue("start") as T
val end = parameterValues.getValue("end") as T
val start = parameterValues.getValue("min") as T
val end = parameterValues.getValue("max") as T
val step = parameterValues.getValue("step") as? T ?: stepDefault
val or = Range.NONE // Adjust this logic based on available data
val or = Range.valueOf((parameterValues.getValue("or") as AnnotationEnumValue).valueName) // Adjust this logic based on available data
val hideSlider = parameterValues.getValue("hideSlider") as? Boolean ?: false
val isRadians = parameterValues.getValue("isRadians") as? Boolean ?: false
val isDegrees = parameterValues.getValue("isDegrees") as? Boolean ?: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import godot.annotation.RegisterFunction
import godot.annotation.RegisterProperty
import godot.annotation.RegisterSignal
import godot.annotation.processor.classgraph.Settings
import godot.annotation.processor.classgraph.constants.JVM_OBJECT
import godot.entrygenerator.model.ClassAnnotation
import godot.entrygenerator.model.Clazz
import godot.entrygenerator.model.RegisteredClass
import godot.entrygenerator.model.Type
import godot.entrygenerator.model.TypeKind
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import org.jetbrains.annotations.NotNull
import io.github.classgraph.TypeArgument

context(ScanResult)
fun ClassInfo.mapToClazz(settings: Settings): Clazz {
Expand All @@ -31,13 +32,13 @@ fun ClassInfo.mapToClazz(settings: Settings): Clazz {
.filter { fieldInfo ->
fieldInfo.hasAnnotation(RegisterProperty::class.java, this)
}
.map { it.mapToRegisteredProperty(settings) }
.map { it.mapToRegisteredProperty(settings, this) }

val signals = fieldInfo
.filter { fieldInfo ->
fieldInfo.hasAnnotation(RegisterSignal::class.java, this)
}
.map { it.mapFieldToRegisteredSignal(settings) }
.map { it.mapFieldToRegisteredSignal(settings, this) }

val duplicateConstructorArgumentCount = constructorInfo
.groupBy { it.parameterInfo.size }
Expand Down Expand Up @@ -137,43 +138,35 @@ internal fun ClassInfo.provideRegisteredClassName(
}

// TODO: remove when https://github.com/classgraph/classgraph/issues/703 is fixed
internal fun getJavaLangObjectType(isNullable: Boolean, settings: Settings): Type {
val fqName = "java.lang.Object"
internal fun getJavaLangObjectType(settings: Settings): Type {
val fqName = JVM_OBJECT
return Type(
fqName = fqName,
kind = TypeKind.CLASS,
isNullable = isNullable,
supertypes = listOf(),
arguments = { listOf() },
registeredName = { getDefaultRegisteredName(fqName, settings) }
)
}

context(ScanResult)
internal fun ClassInfo.mapToType(settings: Settings, nullable: Boolean): Type {
val fqName = name

val superTypes = superclasses.map { it.mapToType(settings, nullable) }

val typeKind = when {
val ClassInfo.typeKind: TypeKind
get() = when {
isAnnotation -> TypeKind.ANNOTATION_CLASS
isInterface -> TypeKind.INTERFACE
isEnum -> TypeKind.ENUM_CLASS
isStandardClass -> TypeKind.CLASS
else -> TypeKind.UNKNOWN
}

context(ScanResult)
internal fun ClassInfo.mapToType(typeArguments: List<TypeArgument>, settings: Settings): Type {
val superTypes = superclasses.map { it.mapToType(listOf(), settings) }

return Type(
fqName = fqName,
fqName = name,
kind = typeKind,
isNullable = hasAnnotation(NotNull::class.java),
supertypes = superTypes,
arguments = { getTypeParameters(settings) },
arguments = { typeArguments.map { it.getType(settings) } },
registeredName = { provideRegisteredClassName(settings) },
)
}

context(ScanResult)
internal fun ClassInfo.getTypeParameters(settings: Settings): List<Type> = typeDescriptor
.typeParameters
.map { it.typeClassInfo.mapToType(settings, false) } //TODO: See how to manage nullables
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package godot.annotation.processor.classgraph.extensions

import io.github.classgraph.ClassMemberInfo

const val DELEGATE_SUFFIX = "\$delegate"

val ClassMemberInfo.fqdn: String
get() = "${classInfo.name}\$$sanitizedName"

val ClassMemberInfo.sanitizedName: String
get() = name.removeSuffix("\$delegate")
get() = name.removeSuffix(DELEGATE_SUFFIX)
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,33 @@ package godot.annotation.processor.classgraph.extensions
import godot.annotation.processor.classgraph.Settings
import godot.annotation.processor.classgraph.models.TypeDescriptor
import godot.entrygenerator.ext.hasAnnotation
import godot.entrygenerator.model.EnumAnnotation
import godot.entrygenerator.model.EnumHintStringAnnotation
import godot.entrygenerator.model.EnumListHintStringAnnotation
import godot.entrygenerator.model.PropertyAnnotation
import godot.entrygenerator.model.RegisteredProperty
import godot.entrygenerator.model.RegisteredSignal
import godot.entrygenerator.model.*
import io.github.classgraph.AnnotationInfo
import io.github.classgraph.AnnotationInfoList
import io.github.classgraph.ClassInfo
import io.github.classgraph.FieldInfo
import io.github.classgraph.MethodInfo
import io.github.classgraph.ScanResult

context(ScanResult)
fun FieldInfo.mapToRegisteredProperty(settings: Settings): RegisteredProperty {
fun FieldInfo.mapToRegisteredProperty(settings: Settings, classInfo: ClassInfo): RegisteredProperty {
// Map annotations
val annotations = annotationInfo
val annotations = getAnnotations(classInfo)
.mapNotNull { it.mapToGodotAnnotation(this) as? PropertyAnnotation }
.toMutableList()

// Handle enums and collections with enums
val fieldType = this.typeDescriptor?.toString() ?: throw IllegalStateException("Type cannot be null")

val typeDescriptor = TypeDescriptor(this)
val typeDescriptor = TypeDescriptor(this, classInfo)

if (!typeDescriptor.isPrimitive) {
val classInfo = typeDescriptor.typeClassInfo
val typeClassInfo = typeDescriptor.typeClassInfo

if (!annotations.hasAnnotation<EnumAnnotation>() && classInfo.isEnum) {
if (!annotations.hasAnnotation<EnumAnnotation>() && typeClassInfo.isEnum) {
annotations.add(
EnumHintStringAnnotation(
enumValueNames = classInfo.fieldInfo
enumValueNames = typeClassInfo.fieldInfo
.filter { it.isEnum }
.map { it.name },
source = this
Expand All @@ -41,7 +39,7 @@ fun FieldInfo.mapToRegisteredProperty(settings: Settings): RegisteredProperty {

// Check if the property is a collection of enums
if (!annotations.hasAnnotation<EnumAnnotation>() && fieldType.startsWith("kotlin.collections")) {
val containingTypeDeclaration = classInfo.typeSignature.typeParameters.firstOrNull()?.typeClassInfo
val containingTypeDeclaration = typeClassInfo.typeSignature.typeParameters.firstOrNull()?.typeClassInfo
if (containingTypeDeclaration?.isEnum == true) {
annotations.add(
EnumListHintStringAnnotation(
Expand All @@ -57,34 +55,51 @@ fun FieldInfo.mapToRegisteredProperty(settings: Settings): RegisteredProperty {

// Check modifiers
val isMutable = !this.isFinal
val isLateinit = this.annotationInfo.any { it.name == "kotlin.Lateinit" }
val isOverridee = this.isOverridee

return RegisteredProperty(
fqName = fqdn,
type = typeDescriptor.getMappedType(settings),
type = typeDescriptor.getMappedPropertyType(settings),
isMutable = isMutable,
isLateinit = isLateinit,
isLateinit = typeDescriptor.isLateInit,
isOverridee = isOverridee,
annotations = annotations.toList(),
symbolProcessorSource = this
)
}

context(ScanResult)
fun FieldInfo.mapFieldToRegisteredSignal(settings: Settings): RegisteredSignal {
val typeDescriptor = TypeDescriptor(this)
val annotations = annotationInfo.mapNotNull { it.mapToGodotAnnotation(this) as? PropertyAnnotation }
fun FieldInfo.mapFieldToRegisteredSignal(settings: Settings, classInfo: ClassInfo): RegisteredSignal {
return if (name.endsWith(DELEGATE_SUFFIX)) {
val methodInfo = getGetter(classInfo)
val typeDescriptor = TypeDescriptor(methodInfo)

return RegisteredSignal(
fqName = fqdn,
type = typeDescriptor.getMappedType(settings),
parameterTypes = typeDescriptor.typeClassInfo.getTypeParameters(settings),
parameterNames = listOf(), //TODO: ClassGraph cannot parse expressions, if we want to use it we need to add parameters to the annotation
isOverridee = isOverridee,
annotations = annotations,
symbolProcessorSource = this
)
val type = typeDescriptor.getMappedType(settings)

RegisteredSignal(
fqName = fqdn,
type = type,
parameterTypes = type.arguments(),
parameterNames = listOf(), //TODO: ClassGraph cannot parse expressions, if we want to use it we need to add parameters to the annotation
isOverridee = isOverridee,
annotations = getAnnotations(classInfo).mapNotNull { it.mapToGodotAnnotation(this) as? PropertyAnnotation },
symbolProcessorSource = this
)
} else {
val typeDescriptor = TypeDescriptor(this, classInfo)
val annotations = annotationInfo.mapNotNull { it.mapToGodotAnnotation(this) as? PropertyAnnotation }

val type = typeDescriptor.getMappedType(settings)
RegisteredSignal(
fqName = fqdn,
type = type,
parameterTypes = type.arguments(),
parameterNames = listOf(), //TODO: ClassGraph cannot parse expressions, if we want to use it we need to add parameters to the annotation
isOverridee = isOverridee,
annotations = annotations,
symbolProcessorSource = this
)
}
}

val FieldInfo.isOverridee: Boolean
Expand All @@ -100,12 +115,33 @@ fun <T : Annotation> FieldInfo.hasAnnotation(annotationClass: Class<T>, classInf
hasAnnotation(annotationClass) ||
classInfo.methodInfo
.any {
it.hasAnnotation(annotationClass) && it.name == toSyntheticAnnotations()
it.hasAnnotation(annotationClass) && (it.name == toSyntheticAnnotations() || it.name == toGetterName())
}

fun FieldInfo.hasAnnotation(annotationName: String, classInfo: ClassInfo): Boolean =
hasAnnotation(annotationName) ||
classInfo.methodInfo
.any {
it.hasAnnotation(annotationName) && (it.name == toSyntheticAnnotations() || it.name == toGetterName())
}


fun FieldInfo.toGetterName(): String = "get${capitalizedName()}"
fun FieldInfo.toSetterName(): String = "set${capitalizedName()}"
fun FieldInfo.toSyntheticAnnotations(): String = "${toGetterName()}\$annotations"

private fun FieldInfo.capitalizedName(): String =
sanitizedName.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase() else char.toString() }
fun FieldInfo.getGetter(classInfo: ClassInfo): MethodInfo = classInfo
.getMethodInfo(toGetterName())
.first { it.parameterInfo.isEmpty() }

fun FieldInfo.getAnnotations(classInfo: ClassInfo): Collection<AnnotationInfo> = classInfo
.getMethodInfo(toSyntheticAnnotations())
.firstOrNull()
?.annotationInfo ?: listOf<AnnotationInfo>()
.union(
annotationInfo
)
.distinct()

private fun FieldInfo.capitalizedName(): String = sanitizedName
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase() else char.toString() }
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ private fun MethodInfo.isOverrideInHierarchyOf(classInfo: ClassInfo): Boolean {

context(ScanResult)
fun MethodInfo.mapToRegisteredConstructor(settings: Settings): RegisteredConstructor {
val parameters = parameterInfo.map { it.mapToValueParameter(settings) }
return RegisteredConstructor(
fqName = fqdn,
parameters = parameterInfo.map { it.mapToValueParameter(settings) },
parameters = parameters,
annotations = annotationInfo
.mapNotNull { it.mapToGodotAnnotation(settings) as? ConstructorAnnotation },
symbolProcessorSource = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ import io.github.classgraph.ScanResult
context(ScanResult)
fun MethodParameterInfo.mapToValueParameter(settings: Settings): ValueParameter {
val typeDescriptor = TypeDescriptor(this)
val type = typeDescriptor.getMappedType(settings)
return ValueParameter(
name,
typeDescriptor.getMappedType(settings),
if (typeDescriptor.isPrimitive || typeDescriptor.isObject) {
listOf()
} else {
typeDescriptor.typeClassInfo.getTypeParameters(settings)
}
type,
type.arguments()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package godot.annotation.processor.classgraph.extensions

import godot.annotation.processor.classgraph.Settings
import godot.annotation.processor.classgraph.constants.JVM_OBJECT
import godot.entrygenerator.model.Type
import io.github.classgraph.ClassRefTypeSignature
import io.github.classgraph.ScanResult
import io.github.classgraph.TypeArgument

context(ScanResult)
fun TypeArgument.getType(settings: Settings): Type {
val typeSignature = typeSignature as ClassRefTypeSignature
return typeSignature.getMappedType(settings)
}

context(ScanResult)
private fun ClassRefTypeSignature.getMappedType(settings: Settings): Type {
return if (fullyQualifiedClassName == JVM_OBJECT) {
getJavaLangObjectType(settings)
} else {
this@ScanResult.getClassInfo(fullyQualifiedClassName).mapToType(
typeArguments,
settings
)
}
}
Loading

0 comments on commit 87b5a5f

Please sign in to comment.