Skip to content

Make required virtual methods abstract #826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import godot.tools.common.constants.TO_GODOT_NAME_UTIL_FUNCTION

class EnrichedCoreRule : GodotApiRule<ApiTask>() {
override fun apply(task: ApiTask, context: GenerationContext) {
val coreTypes = context.api.builtinClasses.associate { it.name to (it.enums?.toEnriched(GenerationType(it.name)) ?: listOf()) }
val coreTypes = context.api.builtinClasses.associate {
it.name to (it.enums?.toEnriched(GenerationType(it.name)) ?: listOf())
}
val globalEnumList = context.api.globalEnums.toEnriched()
val globalEnumMap = globalEnumList.associateBy { it.identifier }
val nativeStructureMap = mutableMapOf<String, EnrichedNativeStructure>()
Expand All @@ -53,8 +55,8 @@ class EnrichedCoreRule : GodotApiRule<ApiTask>() {
class EnrichedClassRule : GodotApiRule<ApiTask>() {
override fun apply(task: ApiTask, context: GenerationContext) {
val classes = context.api.classes
val classList = classes.toEnriched().filter { it.apiType == ApiType.CORE }
val classMap = classList.associateBy { it.identifier }
var classList = classes.toEnriched().filter { it.apiType == ApiType.CORE }
var classMap = classList.associateBy { it.identifier }

classes.forEach {
val enrichedChild = classMap[it.name]
Expand All @@ -69,6 +71,14 @@ class EnrichedClassRule : GodotApiRule<ApiTask>() {
classMap[it.type]?.makeSingleton()
}


classList = classList
.filter { clazz ->// Remove class extending singletons
val parent = clazz.parent
parent == null || !parent.isSingleton
}
classMap = classList.associateBy { it.identifier }

context.classMap += classMap
context.classList += classList

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import godot.codegen.generation.task.EnrichedEnumTask
import godot.codegen.generation.task.EnrichedMethodTask
import godot.codegen.generation.task.EnrichedPropertyTask
import godot.codegen.generation.task.SignalTask
import godot.codegen.models.enriched.EnrichedMethod
import godot.codegen.models.traits.addKdoc
import godot.tools.common.constants.GODOT_BASE_TYPE
import godot.tools.common.constants.KT_OBJECT
Expand All @@ -31,19 +32,7 @@ class MemberRule : GodotApiRule<EnrichedClassTask>() {
}

for (method in clazz.methods) {
if (context.isNativeStructure(method.type.identifier)) {
continue
}
var shouldGenerate = true
for (argument in method.arguments) {
if (context.isNativeStructure(argument.type.identifier)) {
shouldGenerate = false
break
}
}
if (!shouldGenerate) {
continue
}
if(!canGenerateMethod(context, method)) continue

if (method.isStatic) {
task.enrichedStaticMethods.add(EnrichedMethodTask(method, clazz))
Expand All @@ -69,15 +58,42 @@ class MemberRule : GodotApiRule<EnrichedClassTask>() {
if (task.clazz.isSingleton) {
generateSingletonConstructor(context)
} else {
addModifiers(KModifier.OPEN)
generateClassConstructor(task.clazz.isInstantiable, context)
if (clazz.isAbstract) {
addModifiers(KModifier.ABSTRACT)
} else {
addModifiers(KModifier.OPEN)
}
generateClassConstructor(clazz.isInstantiable, context)
}

addKdoc(clazz)
addAnnotation(GODOT_BASE_TYPE)

val parent = task.clazz.parent?: return@configure
for (method in parent.methods.filter { it.isAbstract }) {
if(!canGenerateMethod(context, method)) continue
val overrideMethod = method.override()
task.enrichedMethods.add(EnrichedMethodTask(overrideMethod, clazz))
}
}

private fun TypeSpec.Builder.generateClassConstructor(isInstantiable: Boolean, context: GenerationContext): TypeSpec.Builder {
private fun canGenerateMethod(context: GenerationContext, method: EnrichedMethod): Boolean {
if (context.isNativeStructure(method.type.identifier)) {
return false
}
for (argument in method.arguments) {
if (context.isNativeStructure(argument.type.identifier)) {
return false
}
}
return true
}

private fun TypeSpec.Builder.generateClassConstructor(
isInstantiable: Boolean,
context: GenerationContext
): TypeSpec.Builder {

if (!isInstantiable) {
primaryConstructor(
FunSpec.constructorBuilder()
Expand Down Expand Up @@ -116,20 +132,20 @@ class MemberRule : GodotApiRule<EnrichedClassTask>() {
}

class BindingRule : GodotApiRule<EnrichedClassTask>() {
override fun apply(classTask: EnrichedClassTask, context: GenerationContext) = configure(classTask.builder) {
val clazz = classTask.clazz
override fun apply(task: EnrichedClassTask, context: GenerationContext) = configure(task.builder) {
val clazz = task.clazz
clazz.methods
.filter { !it.isVirtual }
.onEach {
classTask.bindings.addProperty(
task.bindings.addProperty(
PropertySpec
.builder(it.voidPtrVariableName, VOID_PTR)
.addModifiers(KModifier.INTERNAL)
.initializer(
"%T.getMethodBindPtr(%S,·%S,·%L)",
TYPE_MANAGER,
clazz.identifier,
it.godotName,
it.originalName,
it.hash
)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,11 @@ import godot.tools.common.constants.godotCorePackage

interface BaseMethodeRule {
fun FunSpec.Builder.configureMethod(method: EnrichedMethod, clazz: EnrichedClass, context: GenerationContext) {

addKdoc(method)
// This method already exist in the Kotlin class Any. We have to override it because Godot uses the same name in Object.
if (method.name == "toString") {
addModifiers(KModifier.OVERRIDE)
}

// Godot doesn't override its methods, they are either final or meant to be implemented by script or extension.
if (method.isVirtual) {
addModifiers(KModifier.OPEN)
} else {
addModifiers(KModifier.FINAL)
}

val methodTypeName = method.getCastedType()
val shouldReturn = method.getTypeName() != UNIT
if (shouldReturn) {
Expand All @@ -51,15 +42,32 @@ interface BaseMethodeRule {

generateParameters(method, context)

if (!method.isVirtual) {
writeCode(method, clazz)
} else {
// Godot can override virtual methods in child classes, we have to create a dummy final implementation for theM
if (method.isOverride) {
addModifiers(KModifier.OVERRIDE)
addStatement(
"%L·%T(%S)",
"throw",
NotImplementedError::class,
"${method.name} is not implemented for ${clazz.identifier}"
"${clazz.identifier}::${method.name} can't be called from the JVM."
)
addKdoc("Virtual method inherited from base class implemented in non-JVM code. Don't call it.")
} else if (method.isAbstract) {
addModifiers(KModifier.ABSTRACT)
addKdoc(method)
} else if (method.isVirtual) {
addModifiers(KModifier.OPEN)
addStatement(
"%L·%T(%S)",
"throw",
NotImplementedError::class,
"${clazz.identifier}::${method.name} is not implemented."
)
addKdoc(method)
} else {
addModifiers(KModifier.FINAL)
writeCode(method, clazz)
addKdoc(method)
}
}

Expand Down Expand Up @@ -215,7 +223,7 @@ class StringOnlyRule : GodotApiRule<EnrichedClassTask>(), BaseMethodeRule {
}

private fun createStringOnlyMethod(method: EnrichedMethod, clazz: EnrichedClass, context: GenerationContext): EnrichedMethodTask? {
if (method.isVirtual) {
if (method.isVirtual || method.isOverride) {
return null
}
if (method.arguments.none { it.type.identifier == GodotTypes.stringName || it.type.identifier == GodotTypes.nodePath }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,20 @@ import godot.tools.common.constants.godotCorePackage

class RegistrationRule() : GodotApiRule<ApiTask>() {
override fun apply(task: ApiTask, context: GenerationContext) {
val coreTypes = context.classList
val coreTypes = context
.classList
.filter { it.isCoreClass() }

val apiTypes = context.classList
val apiTypes = context
.classList
.filter { !it.isCoreClass() }
.filter { //Remove class extending singletons
val parent = it.parent
parent == null || parent.isSingleton == false
}


val registrationTask = RegistrationTask()
(coreTypes + apiTypes)
.filter { clazz -> //Remove class extending singletons
val parent = clazz.parent
parent == null || parent.isSingleton == false
parent == null || !parent.isSingleton
}.onEach { clazz ->
registrationTask.addVariantMapping(clazz)
registrationTask.addClassRegistering(clazz)
Expand All @@ -35,7 +34,7 @@ class RegistrationRule() : GodotApiRule<ApiTask>() {
task.registrationFiles.add(registrationTask)
}

fun RegistrationTask.addVariantMapping(enrichedClass: EnrichedClass) {
private fun RegistrationTask.addVariantMapping(enrichedClass: EnrichedClass) {
variantMapper.addStatement(
"%M[%T::class] = %T",
MemberName(godotCorePackage, "variantMapper"),
Expand All @@ -44,24 +43,41 @@ class RegistrationRule() : GodotApiRule<ApiTask>() {
)
}

fun RegistrationTask.addClassRegistering(clazz: EnrichedClass) {
private fun RegistrationTask.addClassRegistering(clazz: EnrichedClass) {
val formatString: String
if (clazz.isSingleton) {
engineTypes.addStatement("%T.registerSingleton(%S)·{·%T·}", TYPE_MANAGER, clazz.identifier, clazz.className)
formatString = "%T.registerEngineType(%S,·%T::class)·{·%T·}"
engineTypes.addStatement(
formatString,
TYPE_MANAGER,
clazz.identifier,
clazz.className,
clazz.className
)
} else {
formatString = "%T.registerEngineType(%S,·%T::class,·::%T)"
if(clazz.isAbstract) {
formatString = "%T.registerEngineType(%S,·%T::class,·null)"
engineTypes.addStatement(
formatString,
TYPE_MANAGER,
clazz.identifier,
clazz.className,
)
} else {
formatString = "%T.registerEngineType(%S,·%T::class,·::%T)"
engineTypes.addStatement(
formatString,
TYPE_MANAGER,
clazz.identifier,
clazz.className,
clazz.className
)
}
}
engineTypes.addStatement(
formatString,
TYPE_MANAGER,
clazz.identifier,
clazz.className,
clazz.className
)
}

fun RegistrationTask.addMethodBindings(clazz: EnrichedClass) {
private fun RegistrationTask.addMethodBindings(clazz: EnrichedClass) {
engineMethods.addStatement(
"%T",
clazz.className.nestedClass(methodBindingsInnerClassName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class EnrichedClass(model: Class) : TypeGenerationTrait, DocumentedGenerationTra
val properties = model.properties?.toEnriched() ?: listOf()
val methods = model.methods?.toEnriched() ?: listOf()

val isAbstract = methods.any { it.isAbstract }

override var description = model.description
val additionalImports = mutableSetOf<ClassName>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ import godot.codegen.workarounds.sanitizeApiType
import godot.common.constants.Constraints
import godot.common.extensions.convertToCamelCase

class EnrichedMethod(model: Method) : CallableGeneratorTrait, DocumentedGenerationTrait {
class EnrichedMethod(private val model: Method, override: Boolean = false) : CallableGeneratorTrait, DocumentedGenerationTrait {
enum class Modifier {
DEFAULT,
STATIC,
VIRTUAL,
ABSTRACT,
OVERRIDE
}

override val type = GenerationType(model.returnValue?.type?.sanitizeApiType() ?: "void")
override val nullable = type.isObjectSubClass() || type.isVariant()
override val genericParameters = emptyList<ClassName>()
Expand All @@ -29,21 +37,42 @@ class EnrichedMethod(model: Method) : CallableGeneratorTrait, DocumentedGenerati
override var description = model.description

val hash = model.hash
val isVirtual = model.isVirtual
val isStatic = model.isStatic
val godotName = model.name
private val modifier = run {
if (override) {
Modifier.OVERRIDE
} else if (model.isVirtual) {
if(model.isRequired) {
Modifier.ABSTRACT
} else {
Modifier.VIRTUAL
}

} else if (model.isStatic) {
Modifier.STATIC

} else {
Modifier.DEFAULT
}
}
val isVirtual: Boolean
get() = modifier == Modifier.VIRTUAL || modifier == Modifier.ABSTRACT
val isAbstract: Boolean
get() = modifier == Modifier.ABSTRACT
val isStatic: Boolean
get() = modifier == Modifier.STATIC
val isOverride: Boolean
get() = modifier == Modifier.OVERRIDE

val originalName = model.name
init {
if (arguments.size > Constraints.MAX_FUNCTION_ARG_COUNT) {
throw TooManyMethodArgument(model)
}
}

fun override(): EnrichedMethod {
return EnrichedMethod(model, true)
}
}

fun List<Method>.toEnriched() = map { EnrichedMethod(it) }

fun EnrichedMethod.isSameSignature(other: EnrichedMethod): Boolean {
return name == other.name &&
type == other.type
arguments == other.arguments
}
Loading
Loading