Repository: evant/kotlin-inject
Pull request #87 (2020-10-24 16:04:08)

Support injecting objects
Fixes https://github.com/evant/kotlin-inject/issues/59


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 5f87d42..7afd8da 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
@@ -55,6 +55,8 @@ abstract class AstClass : AstElement(), AstHasModifiers {
 
     abstract val companion: AstClass?
 
+    abstract val isObject: Boolean
+
     abstract val isInterface: Boolean
 
     abstract val superTypes: List<AstClass>
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 fc06ee0..089395b 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
@@ -275,6 +275,7 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
             is Result.Container -> provideContainer(result, context)
             is Result.Function -> provideFunction(result, context)
             is Result.NamedFunction -> provideNamedFunction(result, context)
+            is Result.Object -> provideObject(result.astClass)
             is Result.Arg -> provideArg(result)
             is Result.Lazy -> provideLazy(result, context)
         }
@@ -416,6 +417,8 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
         }.build()
     }
 
+    private fun provideObject(astClass: AstClass) = CodeBlock.builder().add("%T", astClass.type.asTypeName()).build()
+
     private fun provideFunction(result: Result.Function, context: Context): CodeBlock =
         context.use(result.element) { context ->
             CodeBlock.builder().apply {
@@ -548,6 +551,7 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
                 creator = creator.toString(),
                 args = args.map { it.toResult(skipScoped) as Result.Provides }
             )
+            is TypeCreator.Object -> Result.Object(astClass)
         }
     }
 
@@ -595,6 +599,7 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
         class Container(val creator: String, val args: List<Provides>) : Result(null)
         class Function(val element: AstElement, val key: TypeKey, val args: List<AstType>) : Result(null)
         class NamedFunction(val function: AstFunction, val args: List<AstType>) : Result(null)
+        class Object(val astClass: AstClass) : Result(astClass.name)
 
         class Arg(val argName: String) : Result(null)
         class Lazy(val key: TypeKey) : Result(null)
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt
index d449b84..16fe129 100644
--- a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt
@@ -1,5 +1,6 @@
 package me.tatarka.inject.compiler
 
+import me.tatarka.inject.annotations.Inject
 import me.tatarka.inject.annotations.IntoMap
 import me.tatarka.inject.annotations.IntoSet
 import me.tatarka.inject.compiler.ContainerCreator.mapOf
@@ -247,6 +248,9 @@ class TypeCollector private constructor(private val provider: AstProvider, priva
                 scopedComponent = scopedComponent?.type
             )
         }
+        if (astClass.hasAnnotation<Inject>() && astClass.isObject) {
+            return TypeCreator.Object(astClass)
+        }
         return null
     }
 }
@@ -261,12 +265,13 @@ class TypeInfo(
 
 sealed class TypeCreator(val source: AstElement) {
 
+    class Object(val astClass: AstClass) : TypeCreator(astClass)
+
     class Constructor(
         val constructor: AstConstructor,
         val accessor: String? = null,
         val scopedComponent: AstClass? = null
-    ) :
-        TypeCreator(constructor)
+    ) : TypeCreator(constructor)
 
     class Method(
         val method: AstMethod,
@@ -274,8 +279,11 @@ sealed class TypeCreator(val source: AstElement) {
         val scopedComponent: AstClass? = null
     ) : TypeCreator(method)
 
-    class Container(val creator: ContainerCreator, source: AstElement, val args: MutableList<Method>) :
-        TypeCreator(source)
+    class Container(
+        val creator: ContainerCreator,
+        val args: MutableList<Method>,
+        source: AstElement
+    ) : TypeCreator(source)
 }
 
 @Suppress("EnumNaming")
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 17419b5..b94c3c4 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
@@ -203,6 +203,7 @@ private class PrimitiveModelAstClass(
     override val isPrivate: Boolean = false
     override val isInterface: Boolean = false
     override val companion: AstClass? = null
+    override val isObject: Boolean = false
     override val superTypes: List<AstClass> = emptyList()
     override val primaryConstructor: AstConstructor? = null
     override val constructors: List<AstConstructor> = emptyList()
@@ -249,6 +250,9 @@ private class ModelAstClass(
     override val isInterface: Boolean
         get() = kmClass?.isInterface() ?: false
 
+    override val isObject: Boolean
+        get() = kmClass?.isObject() ?: false
+
     override val companion: AstClass?
         get() {
             val companionName = kmClass?.companionObject ?: return null
@@ -277,7 +281,7 @@ private class ModelAstClass(
 
     override val primaryConstructor: AstConstructor?
         get() {
-            if (kmClass == null) return null
+            if (kmClass == null || isObject) return null
 
             val primaryKmCtor = kmClass.constructors.find(KmConstructor::isPrimary)
             val primaryKmCtorSignature = primaryKmCtor?.signature?.simpleSig ?: return null
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 5ca1581..39671be 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
@@ -314,6 +314,8 @@ inline fun KmClass.isPrivate() = Flag.Common.IS_PRIVATE(flags)
 
 inline fun KmClass.isInterface() = Flag.Class.IS_INTERFACE(flags)
 
+inline fun KmClass.isObject() = Flag.Class.IS_OBJECT(flags)
+
 inline fun KmFunction.isAbstract() = Flag.Common.IS_ABSTRACT(flags)
 
 inline fun KmFunction.isPrivate() = Flag.Common.IS_PRIVATE(flags)
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 6d971d8..cd02e7f 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
@@ -189,6 +189,9 @@ private class KSAstClass(provider: KSAstProvider, override val declaration: KSCl
             .find { it is KSClassDeclaration && it.isCompanionObject }
             ?.let { KSAstClass(this, it as KSClassDeclaration) }
 
+    override val isObject: Boolean
+        get() = declaration.classKind == ClassKind.OBJECT
+
     override val superTypes: List<AstClass>
         get() {
             return declaration


Test patch:
diff --git a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InjectObjectTest.kt b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InjectObjectTest.kt
new file mode 100644
index 0000000..badae87
--- /dev/null
+++ b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InjectObjectTest.kt
@@ -0,0 +1,41 @@
+package me.tatarka.inject.test
+
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import me.tatarka.inject.annotations.Component
+import me.tatarka.inject.annotations.Inject
+import me.tatarka.inject.test.different.DifferentPackageFoo
+import org.junit.Test
+
+@Inject object MyInjectObject
+
+@Inject class DependsOnMyInjectObject(val myInjectObject: MyInjectObject)
+
+@Component interface MyComponent {
+    val injectObject: MyInjectObject
+    val differentPackageObject: DifferentPackageFoo.MyObject
+    fun injectObject(): MyInjectObject
+    fun dependsOnMyInjectObject(): DependsOnMyInjectObject
+}
+
+class InjectObjectTest {
+    @Test
+    fun inject_annotated_object_can_be_provided_in_component_function() {
+        assertThat(InjectMyComponent().injectObject()).isEqualTo(MyInjectObject)
+    }
+
+    @Test
+    fun inject_annotated_object_can_be_provided_in_component_property() {
+        assertThat(InjectMyComponent().injectObject).isEqualTo(MyInjectObject)
+    }
+
+    @Test
+    fun inject_annotated_object_can_be_provided_to_class_constructors() {
+        assertThat(InjectMyComponent().dependsOnMyInjectObject().myInjectObject).isEqualTo(MyInjectObject)
+    }
+
+    @Test
+    fun inject_annotated_object_can_be_provided_from_a_different_package() {
+        assertThat(InjectMyComponent().differentPackageObject).isEqualTo(DifferentPackageFoo.MyObject)
+    }
+}
\ No newline at end of file
diff --git a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/different/DifferentPackage.kt b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/different/DifferentPackage.kt
index 4aedfd8..4936efb 100644
--- a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/different/DifferentPackage.kt
+++ b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/different/DifferentPackage.kt
@@ -7,6 +7,7 @@ import me.tatarka.inject.test.CustomScope
 
 class DifferentPackageFoo {
     @Inject class Factory
+    @Inject object MyObject
 }
 
 @CustomScope @Component abstract class DifferentPackageScopedComponent {