Skip to content

Commit

Permalink
introduce InjectByConstructor interface
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeshustoff committed Feb 12, 2023
1 parent ebc996d commit ef1ebbc
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package dev.shustoff.dikt

interface InjectByConstructor
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.shustoff.dikt.dependency

import dev.shustoff.dikt.message_collector.ErrorCollector
import dev.shustoff.dikt.utils.Annotations
import dev.shustoff.dikt.utils.VisibilityChecker
import org.jetbrains.kotlin.backend.jvm.codegen.anyTypeArgument
import org.jetbrains.kotlin.ir.backend.js.utils.asString
Expand All @@ -10,8 +11,6 @@ import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.checker.SimpleClassicTypeSystemContext.argumentsCount

data class AvailableDependencies(
private val errorCollector: ErrorCollector,
Expand Down Expand Up @@ -53,23 +52,25 @@ data class AvailableDependencies(
forFunction.error("Generic types can't be singletons")
}
val isSingleton = isClassInSingletonList && !id.type.anyTypeArgument { true }
if (isClassInSingletonList || id.type.classOrNull?.defaultType in providedByConstructor) {

val providedDependency = findProvidedDependency(id, forFunction)
?.takeIf { !forbidFunctionParams || it !is ProvidedDependency.Parameter }

val canInjectByConstructor = isClassInSingletonList ||
id.type.classOrNull?.defaultType in providedByConstructor ||
Annotations.isInjectableByConstructor(id.type)

// only inject by constructor if not already provided from nested module
if (canInjectByConstructor && providedDependency?.fromNestedModule == null) {
return buildResolvedConstructor(forFunction, id, usedTypes, providedByConstructor, singletons, forbidFunctionParams = forbidFunctionParams, isSingleton = isSingleton)
} else if (providedDependency != null) {
// TODO: maybe return something even in case of error to postpone throwing it? In this case we can try different type of injection
return resolveProvidedDependency(id, forFunction, providedDependency, usedTypes, providedByConstructor, singletons)
} else if (defaultValue != null) {
return ResolvedDependency.ParameterDefaultValue(id.type, defaultValue)
} else {
val dependency = findProvidedDependency(id, forFunction)
?.takeIf { !forbidFunctionParams || it !is ProvidedDependency.Parameter }
if (dependency == null) {
if (defaultValue == null) {
forFunction.error(
"Can't resolve dependency ${id.asErrorString()}",
)
return null
} else {
return ResolvedDependency.ParameterDefaultValue(id.type, defaultValue)
}
} else {
return resolveProvidedDependency(id, forFunction, dependency, usedTypes, providedByConstructor, singletons)
}
forFunction.error("Can't resolve dependency ${id.asErrorString()}")
return null
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isAnnotation
import org.jetbrains.kotlin.ir.util.superTypes
import org.jetbrains.kotlin.name.FqName

object Annotations {
Expand All @@ -22,6 +24,7 @@ object Annotations {
private val injectByConstructorsAnnotation = FqName("dev.shustoff.dikt.InjectByConstructors")
private val oldUseConstructorsAnnotation = FqName("dev.shustoff.dikt.UseConstructors")
private val moduleSingletonsAnnotation = FqName("dev.shustoff.dikt.InjectSingleByConstructors")
private val injectableByConstructor = FqName("dev.shustoff.dikt.InjectByConstructor") //TODO: rename to injectable

fun getUsedModules(descriptor: IrAnnotationContainer): List<IrType> {
val annotation = descriptor.getAnnotation(useModulesAnnotation)
Expand Down Expand Up @@ -73,4 +76,8 @@ object Annotations {
}
.mapNotNull { it.classOrNull?.defaultType }
}

fun isInjectableByConstructor(type: IrType): Boolean {
return type.superTypes().any { it.classFqName == injectableByConstructor }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package dev.shustoff.dikt.compiler

import com.google.common.truth.Truth
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.SourceFile
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

@OptIn(ExperimentalCompilerApi::class)
class InjectByConstructorTest {
@Rule
@JvmField
var folder: TemporaryFolder = TemporaryFolder()

@Test
fun `allow constructor calls for InjectByConstructor implementations`() {
val result = compile(
folder.root,
SourceFile.kotlin(
"MyModule.kt",
"""
package dev.shustoff.dikt.compiler
import dev.shustoff.dikt.*
class Dependency : InjectByConstructor
class Injectable(val dependency: Dependency): InjectByConstructor
class MyModule {
fun injectable(): Injectable = resolve()
}
"""
)
)
Truth.assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
}

@Test
fun `resolve InjectByConstructor from nested module if parameters for InjectByConstructor not available`() {
val result = compile(
folder.root,
SourceFile.kotlin(
"MyModule.kt",
"""
package dev.shustoff.dikt.compiler
import dev.shustoff.dikt.*
class Dependency
class Injectable(val dependency: Dependency): InjectByConstructor
class NestedModule(private val dependency: Dependency) {
fun injectable() = resolve<Injectable>()
}
class MyModule(@ProvidesMembers val module: NestedModule) {
fun injectable(): Injectable = resolve()
}
"""
)
)
Truth.assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
}
}

0 comments on commit ef1ebbc

Please sign in to comment.