Repository: evant/kotlin-inject
Pull request #70 (2020-09-25 17:54:40)

Error out if parent component is missing val
 #67


Patch:
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Ast.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Ast.kt
index 9fb994f..a645b75 100644
--- a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Ast.kt
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Ast.kt
@@ -189,6 +189,10 @@ abstract class AstParam : AstElement() {
 
     abstract val type: AstType
 
+    abstract val isVal: Boolean
+
+    abstract val isPrivate: Boolean
+
     abstract fun asParameterSpec(): ParameterSpec
 
     override fun toString(): String {
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
index 4655b02..157abdb 100644
--- a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
@@ -66,6 +66,14 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
                 if (constructor != null) {
                     val funSpec = FunSpec.constructorBuilder()
                     for (parameter in constructor.parameters) {
+                        if (parameter.isComponent()) {
+                            if (!parameter.isVal) {
+                                error("@Component parameter: ${parameter.name} must be val", parameter)
+                            } else if (parameter.isPrivate) {
+                                error("@Component parameter: ${parameter.name} must not be private", parameter)
+                            }
+                        }
+
                         val p = parameter.asParameterSpec()
                         funSpec.addParameter(p)
                         addSuperclassConstructorParameter("%N", p)
diff --git a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt
index 19eecb9..241d063 100644
--- a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt
+++ b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt
@@ -47,7 +47,7 @@ interface ModelAstProvider :
                 ) {
                     val metadata = element.metadata?.toKmPackage() ?: continue
                     val kmFunction = metadata.functions.find { it.name == functionName } ?: continue
-                    results.add(ModelAstFunction(this, element.toAstClass(), function, kmFunction))
+                    results.add(ModelAstFunction(this, element.toAstClass() as ModelAstClass, function, kmFunction))
                 }
             }
         }
@@ -293,7 +293,7 @@ private class ModelAstClass(
 
 private class ModelAstConstructor(
     provider: ModelAstProvider,
-    parent: AstClass,
+    private val parent: ModelAstClass,
     override val element: ExecutableElement,
     private val kmConstructor: KmConstructor?
 ) : AstConstructor(parent),
@@ -308,6 +308,7 @@ private class ModelAstConstructor(
                 ModelAstParam(
                     this,
                     param,
+                    parent.kmClass,
                     kmParam
                 )
             }
@@ -375,6 +376,7 @@ private class ModelAstFunction(
                 ModelAstParam(
                     this,
                     param,
+                    null,
                     kmParam
                 )
             }
@@ -599,6 +601,7 @@ private class ModelAstAnnotation(
 private class ModelAstParam(
     provider: ModelAstProvider,
     override val element: VariableElement,
+    val kmParent: KmClass?,
     val kmValueParameter: KmValueParameter?
 ) : AstParam(),
     ModelAstElement, ModelAstProvider by provider {
@@ -611,6 +614,18 @@ private class ModelAstParam(
     override val type: AstType
         get() = ModelAstType(this, element.asType(), kmValueParameter?.type)
 
+    override val isVal: Boolean get() {
+        val param = kmValueParameter ?: return false
+        val parent = kmParent ?: return false
+        return parent.properties.any { it.name == param.name }
+    }
+
+    override val isPrivate: Boolean get() {
+        val param = kmValueParameter ?: return false
+        val parent = kmParent ?: return false
+        return parent.properties.find { it.name == param.name }?.isPrivate() ?: false
+    }
+
     override fun asParameterSpec(): ParameterSpec {
         return ParameterSpec(name, type.asTypeName())
     }
diff --git a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt
index f9ce390..7022c45 100644
--- a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt
+++ b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt
@@ -268,4 +268,6 @@ inline fun KmFunction.isPrivate() = Flag.Common.IS_PRIVATE(flags)
 
 inline fun KmProperty.isAbstract() = Flag.Common.IS_ABSTRACT(flags)
 
-inline fun KmProperty.isPrivate() = Flag.Common.IS_PRIVATE(flags)
\ No newline at end of file
+inline fun KmProperty.isPrivate() = Flag.Common.IS_PRIVATE(flags)
+
+inline fun KmConstructor.isVal() =Flag.Constructor.IS_PRIMARY
\ No newline at end of file
diff --git a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt
index 8c28ee6..b78b83f 100644
--- a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt
+++ b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt
@@ -217,13 +217,13 @@ private class KSAstClass(provider: KSAstProvider, override val declaration: KSCl
 
 private class KSAstConstructor(
     provider: KSAstProvider,
-    parent: AstClass,
+    private val parent: KSAstClass,
     override val declaration: KSFunctionDeclaration
 ) : AstConstructor(parent), KSAstAnnotated, KSAstProvider by provider {
 
     override val parameters: List<AstParam>
         get() {
-            return declaration.parameters.map { param -> KSAstParam(this, declaration, param) }
+            return declaration.parameters.map { param -> KSAstParam(this, parent.declaration, declaration, param) }
         }
 
     override fun equals(other: Any?): Boolean {
@@ -240,7 +240,7 @@ private class KSAstFunction(provider: KSAstProvider, override val declaration: K
 
     override val parameters: List<AstParam>
         get() = declaration.parameters.map { param ->
-            KSAstParam(this, declaration, param)
+            KSAstParam(this, null, declaration, param)
         }
 
     override val name: String
@@ -428,6 +428,7 @@ private class KSAstType(provider: KSAstProvider) : AstType(), KSAstAnnotated, KS
 
 private class KSAstParam(
     provider: KSAstProvider,
+    val parentClass: KSClassDeclaration?,
     val parent: KSDeclaration,
     override val declaration: KSVariableParameter
 ) : AstParam(),
@@ -439,6 +440,12 @@ private class KSAstParam(
     override val type: AstType
         get() = KSAstType(this, declaration.type!!.resolve()!!)
 
+    override val isVal: Boolean
+        get() = declaration.isVal
+
+    override val isPrivate: Boolean
+        get() = parentClass?.getDeclaredProperties()?.find { it.simpleName == declaration.name }?.isPrivate() ?: false
+
     override fun asParameterSpec(): ParameterSpec {
         return ParameterSpec.builder(name, type.asTypeName())
             .build()


Test patch:
diff --git a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt b/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
index 9125956..1c9c0c2 100644
--- a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
+++ b/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
@@ -243,6 +243,38 @@ class FailureTest(val target: Target) {
             "Cannot find an @Inject constructor or provider for: String", "foo", "Foo"
         )
     }
+
+    @Test
+    fun fails_if_parent_component_is_missing_val() {
+       assertThat {
+           projectCompiler.source("MyComponent.kt", """
+               import me.tatarka.inject.annotations.Component
+               
+               @Component abstract class ParentComponent()
+               
+               @Component abstract class MyComponent(@Component parent: ParentComponent)
+           """.trimIndent()
+           ).compile()
+       }.isFailure().output().contains(
+           "@Component parameter: parent must be val"
+       )
+    }
+
+    @Test
+    fun fails_if_parent_component_is_private() {
+        assertThat {
+            projectCompiler.source("MyComponent.kt", """
+               import me.tatarka.inject.annotations.Component
+               
+               @Component abstract class ParentComponent()
+               
+               @Component abstract class MyComponent(@Component private val parent: ParentComponent)
+           """.trimIndent()
+            ).compile()
+        }.isFailure().output().contains(
+            "@Component parameter: parent must not be private"
+        )
+    }
 }
 
 private fun Target.sourceExt() = when (this) {