diff --git a/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt b/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt index 30c56e05..8eae9abd 100644 --- a/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt +++ b/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt @@ -43,7 +43,10 @@ object JavaCodeGenerator { .apply { addAnnotation(scopeImplAnnotation.spec()) addModifiers(Modifier.PUBLIC) - addSuperinterface(superClassName.j) + when (superClassName) { + is SuperClassName.Interface -> addSuperinterface(superClassName.name.j) + is SuperClassName.AbstractClass -> superclass(superClassName.name.j) + } objectsField?.let { addField(it.spec()) } addField(dependenciesField.spec()) cacheFields.forEach { addField(it.spec(useNullFieldInitialization)) } diff --git a/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt b/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt index 4bdc2654..cb1a4ace 100644 --- a/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt +++ b/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt @@ -46,7 +46,10 @@ object KotlinCodeGenerator { addAnnotation(suppressAnnotationSpec("REDUNDANT_PROJECTION", "UNCHECKED_CAST")) addAnnotation(scopeImplAnnotation.spec()) addModifiers(if (internalScope) KModifier.INTERNAL else KModifier.PUBLIC) - addSuperinterface(superClassName.kt) + when (superClassName) { + is SuperClassName.Interface -> addSuperinterface(superClassName.name.kt) + is SuperClassName.AbstractClass -> superclass(superClassName.name.kt) + } objectsField?.let { addProperty(it.spec()) } addProperty(dependenciesField.spec()) cacheFields.forEach { addProperty(it.spec(useNullFieldInitialization)) } diff --git a/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt b/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt index 499e9c0b..a6cbd51f 100644 --- a/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt +++ b/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt @@ -37,7 +37,7 @@ import motif.ast.compiler.CompilerMethod class ScopeImpl( val useNullFieldInitialization: Boolean, val className: ClassName, - val superClassName: ClassName, + val superClassName: SuperClassName, val internalScope: Boolean, val scopeImplAnnotation: ScopeImplAnnotation, val objectsField: ObjectsField?, @@ -54,6 +54,12 @@ class ScopeImpl( val dependencies: Dependencies?, ) +sealed interface SuperClassName { + data class Interface(val name: ClassName) : SuperClassName + + data class AbstractClass(val name: ClassName) : SuperClassName +} + /** * ``` * @ScopeImpl( diff --git a/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt b/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt index f8049db9..a7ab7fdb 100644 --- a/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt +++ b/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt @@ -33,6 +33,7 @@ import motif.models.ConstructorFactoryMethod import motif.models.FactoryMethod import motif.models.FactoryMethodSink import motif.models.Scope +import motif.models.ScopeMustBeAnInterfaceOrAbstractClass import motif.models.Sink import motif.models.Spread import motif.models.Type @@ -67,13 +68,19 @@ private constructor( fun create(): ScopeImpl { val isInternal = (scope.clazz as? CompilerClass)?.isInternal() ?: false + val superType = + when { + scope.clazz.kind == IrClass.Kind.INTERFACE -> SuperClassName.Interface(scope.typeName) + scope.clazz.isAbstract() -> SuperClassName.AbstractClass(scope.typeName) + else -> throw ScopeMustBeAnInterfaceOrAbstractClass(scope.clazz) + } return ScopeImpl( (scope.clazz.annotations .find { it.className == motif.Scope::class.java.name }!! .annotationValueMap[SCOPE_ANNOTATION_FIELD_USE_NULL] as? Boolean) ?: false, scope.implClassName, - scope.typeName, + superType, isInternal, scopeImplAnnotation(), objectsField(), diff --git a/errormessage/src/main/kotlin/motif/errormessage/ErrorHandler.kt b/errormessage/src/main/kotlin/motif/errormessage/ErrorHandler.kt index c5351e67..7524e4e3 100644 --- a/errormessage/src/main/kotlin/motif/errormessage/ErrorHandler.kt +++ b/errormessage/src/main/kotlin/motif/errormessage/ErrorHandler.kt @@ -41,7 +41,7 @@ import motif.models.ObjectsConstructorFound import motif.models.ObjectsFieldFound import motif.models.ParsingError import motif.models.ScopeExtendsScope -import motif.models.ScopeMustBeAnInterface +import motif.models.ScopeMustBeAnInterfaceOrAbstractClass import motif.models.UnspreadableType import motif.models.VoidDependenciesMethod import motif.models.VoidFactoryMethod @@ -60,7 +60,8 @@ internal interface ErrorHandler { when (error) { is ParsingError -> when (error) { - is ScopeMustBeAnInterface -> ScopeMustBeAnInterfaceHandler(error) + is ScopeMustBeAnInterfaceOrAbstractClass -> + ScopeMustBeAnInterfaceOrAbstractClassHandler(error) is VoidScopeMethod -> VoidScopeMethodHandler(error) is AccessMethodParameters -> AccessMethodParametersHandler(error) is ObjectsFieldFound -> ObjectsFieldFoundHandler(error) diff --git a/errormessage/src/main/kotlin/motif/errormessage/ScopeMustBeAnInterfaceHandler.kt b/errormessage/src/main/kotlin/motif/errormessage/ScopeMustBeAnInterfaceOrAbstractClassHandler.kt similarity index 76% rename from errormessage/src/main/kotlin/motif/errormessage/ScopeMustBeAnInterfaceHandler.kt rename to errormessage/src/main/kotlin/motif/errormessage/ScopeMustBeAnInterfaceOrAbstractClassHandler.kt index eda45433..d9c18a6d 100644 --- a/errormessage/src/main/kotlin/motif/errormessage/ScopeMustBeAnInterfaceHandler.kt +++ b/errormessage/src/main/kotlin/motif/errormessage/ScopeMustBeAnInterfaceOrAbstractClassHandler.kt @@ -15,17 +15,18 @@ */ package motif.errormessage -import motif.models.ScopeMustBeAnInterface +import motif.models.ScopeMustBeAnInterfaceOrAbstractClass -internal class ScopeMustBeAnInterfaceHandler(private val error: ScopeMustBeAnInterface) : - ErrorHandler { +internal class ScopeMustBeAnInterfaceOrAbstractClassHandler( + private val error: ScopeMustBeAnInterfaceOrAbstractClass, +) : ErrorHandler { override val name = "SCOPE CLASS" override fun StringBuilder.handle() { appendLine( """ - Scope must be an interface: + Scope must be an interface or abstract class: ${error.scopeClass.qualifiedName} """ diff --git a/intellij/src/main/kotlin/motif/intellij/hierarchy/descriptor/ScopeHierarchyErrorDescriptor.kt b/intellij/src/main/kotlin/motif/intellij/hierarchy/descriptor/ScopeHierarchyErrorDescriptor.kt index f6963ac6..932b3972 100644 --- a/intellij/src/main/kotlin/motif/intellij/hierarchy/descriptor/ScopeHierarchyErrorDescriptor.kt +++ b/intellij/src/main/kotlin/motif/intellij/hierarchy/descriptor/ScopeHierarchyErrorDescriptor.kt @@ -46,7 +46,7 @@ import motif.models.NullableParameter import motif.models.NullableSpreadMethod import motif.models.ObjectsConstructorFound import motif.models.ObjectsFieldFound -import motif.models.ScopeMustBeAnInterface +import motif.models.ScopeMustBeAnInterfaceOrAbstractClass import motif.models.UnspreadableType import motif.models.VoidDependenciesMethod import motif.models.VoidFactoryMethod @@ -70,7 +70,7 @@ open class ScopeHierarchyErrorDescriptor( companion object { fun getElementFromError(error: MotifError): PsiElement = when (error) { - is ScopeMustBeAnInterface -> { + is ScopeMustBeAnInterfaceOrAbstractClass -> { (error.scopeClass as IntelliJClass).psiClass } is VoidScopeMethod -> { diff --git a/models/src/main/kotlin/motif/models/ParsingError.kt b/models/src/main/kotlin/motif/models/ParsingError.kt index 130e0737..460f971e 100644 --- a/models/src/main/kotlin/motif/models/ParsingError.kt +++ b/models/src/main/kotlin/motif/models/ParsingError.kt @@ -24,7 +24,7 @@ import motif.ast.IrType sealed class ParsingError : RuntimeException(), MotifError -class ScopeMustBeAnInterface(val scopeClass: IrClass) : ParsingError() +class ScopeMustBeAnInterfaceOrAbstractClass(val scopeClass: IrClass) : ParsingError() class VoidScopeMethod(val scope: Scope, val method: IrMethod) : ParsingError() diff --git a/models/src/main/kotlin/motif/models/Scope.kt b/models/src/main/kotlin/motif/models/Scope.kt index 71c78c92..d9b79342 100644 --- a/models/src/main/kotlin/motif/models/Scope.kt +++ b/models/src/main/kotlin/motif/models/Scope.kt @@ -49,7 +49,9 @@ class ValidScope internal constructor(clazz: IrClass, useNullFieldInitialization Scope(useNullFieldInitialization, clazz) { init { - if (clazz.kind != IrClass.Kind.INTERFACE) throw ScopeMustBeAnInterface(clazz) + if (clazz.kind != IrClass.Kind.INTERFACE && !clazz.isAbstract()) { + throw ScopeMustBeAnInterfaceOrAbstractClass(clazz) + } detectScopeSuperinterface(this) } diff --git a/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java b/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java index 88acbdbc..a2654cbd 100644 --- a/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java +++ b/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java @@ -22,14 +22,14 @@ import motif.sample.lib.db.Photo; @Scope(useNullFieldInitialization = true) -public interface PhotoGridScope { +public abstract class PhotoGridScope { - PhotoGridView view(); + public abstract PhotoGridView view(); - PhotoGridItemScope photoRow(PhotoGridItemView view, Photo photo); + abstract PhotoGridItemScope photoRow(PhotoGridItemView view, Photo photo); @motif.Objects - abstract class Objects extends ControllerObjects { + abstract static class Objects extends ControllerObjects { abstract PhotoGridAdapter adapter(); } diff --git a/tests/src/main/java/testcases/E024_scope_class/ERROR.txt b/tests/src/main/java/testcases/E024_scope_class/ERROR.txt index 4992f48e..8fd60dec 100644 --- a/tests/src/main/java/testcases/E024_scope_class/ERROR.txt +++ b/tests/src/main/java/testcases/E024_scope_class/ERROR.txt @@ -17,7 +17,7 @@ [SCOPE CLASS] - Scope must be an interface: + Scope must be an interface or abstract class: testcases.E024_scope_class.Scope