Skip to content

Commit

Permalink
Handle generic classes when generating member injectors
Browse files Browse the repository at this point in the history
Generate correct member injectors for parameterized classes using the general star (`*`) for all type arguments of the target class.

Member injectors for non generic/parameterized classes works as before.

Fixes #29.
  • Loading branch information
arctouch-ianribas committed Jul 29, 2024
1 parent 0da9fdc commit 35518e8
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
Expand Down Expand Up @@ -60,6 +62,7 @@ internal class MemberInjectorGenerator(
}
}

private val parameterizedSourceClass: TypeName = sourceClass.maybeParameterizedClassName()
val sourceClassName: ClassName = sourceClass.toClassName()
val generatedClassName: ClassName = sourceClassName.memberInjectorClassName

Expand All @@ -70,7 +73,7 @@ internal class MemberInjectorGenerator(
.addOriginatingKSFile(sourceClass.containingFile!!)
.addModifiers(sourceClass.getVisibility().toKModifier() ?: KModifier.PUBLIC)
.addSuperinterface(
MemberInjector::class.asClassName().parameterizedBy(sourceClassName)
MemberInjector::class.asClassName().parameterizedBy(parameterizedSourceClass)
)
.addAnnotation(
AnnotationSpec.builder(Suppress::class)
Expand All @@ -85,6 +88,14 @@ internal class MemberInjectorGenerator(
)
}

private fun KSClassDeclaration.maybeParameterizedClassName(): TypeName {
return if (this.typeParameters.isEmpty()) {
toClassName()
} else {
toClassName().parameterizedBy(this.typeParameters.map { STAR })
}
}

private fun TypeSpec.Builder.emitSuperMemberInjectorFieldIfNeeded(): TypeSpec.Builder = apply {
if (superClassThatNeedsInjection == null) {
return this
Expand All @@ -111,7 +122,7 @@ internal class MemberInjectorGenerator(
addFunction(
FunSpec.builder("inject")
.addModifiers(KModifier.OVERRIDE)
.addParameter("target", sourceClassName)
.addParameter("target", parameterizedSourceClass)
.addParameter("scope", Scope::class)
.apply {
if (superClassThatNeedsInjection != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1622,4 +1622,138 @@ class FieldMemberInjectorTest {
"Class test.TestFieldInjection has invalid property: invalid"
)
}

@Test
fun testSimpleFieldInjectionInParameterizedClass_java() {
val source = javaSource(
"TestFieldInjection",
"""
package test;
import javax.inject.Inject;
public class TestFieldInjection<T> {
@Inject Foo foo;
public TestFieldInjection() {}
}
class Foo {}
"""
)

compilationAssert()
.that(source)
.processedWith(MemberInjectorProcessorProvider())
.compilesWithoutError()
.generatesSources(testSimpleFieldInjectionParameterizedClass_expected)
}

@Test
fun testSimpleFieldInjectionInParameterizedClass_kotlin() {
val source = ktSource(
"TestFieldInjection",
"""
package test
import javax.inject.Inject
class TestFieldInjection<T> {
@Inject lateinit var foo: Foo
}
class Foo
"""
)

compilationAssert()
.that(source)
.processedWith(MemberInjectorProcessorProvider())
.compilesWithoutError()
.generatesSources(testSimpleFieldInjectionParameterizedClass_expected)
}

private val testSimpleFieldInjectionParameterizedClass_expected = expectedKtSource(
"test/TestFieldInjection__MemberInjector",
"""
package test
import kotlin.Suppress
import kotlin.Unit
import toothpick.MemberInjector
import toothpick.Scope
@Suppress(
"ClassName",
"RedundantVisibilityModifier",
"UNCHECKED_CAST",
)
public class TestFieldInjection__MemberInjector : MemberInjector<TestFieldInjection<*>> {
public override fun inject(target: TestFieldInjection<*>, scope: Scope): Unit {
target.foo = scope.getInstance(Foo::class.java) as Foo
}
}
"""
)

@Test
fun testSimpleFieldInjectionInMultipleParameterizedClass_java() {
val source = javaSource(
"TestFieldInjection",
"""
package test;
import java.io.Reader;
import javax.inject.Inject;
public class TestFieldInjection<T, U extends StringBuilder, V extends Reader> {
@Inject Foo foo;
public TestFieldInjection() {}
}
class Foo {}
"""
)

compilationAssert()
.that(source)
.processedWith(MemberInjectorProcessorProvider())
.compilesWithoutError()
.generatesSources(testSimpleFieldInjectionMultipleParameterizedClass_expected)
}

@Test
fun testSimpleFieldInjectionInMultipleParameterizedClass_kotlin() {
val source = ktSource(
"TestFieldInjection",
"""
package test
import java.io.Reader
import javax.inject.Inject
class TestFieldInjection<T, U: StringBuilder, V: java.io.Reader> {
@Inject lateinit var foo: Foo
}
class Foo
"""
)

compilationAssert()
.that(source)
.processedWith(MemberInjectorProcessorProvider())
.compilesWithoutError()
.generatesSources(testSimpleFieldInjectionMultipleParameterizedClass_expected)
}

private val testSimpleFieldInjectionMultipleParameterizedClass_expected = expectedKtSource(
"test/TestFieldInjection__MemberInjector",
"""
package test
import kotlin.Suppress
import kotlin.Unit
import toothpick.MemberInjector
import toothpick.Scope
@Suppress(
"ClassName",
"RedundantVisibilityModifier",
"UNCHECKED_CAST",
)
public class TestFieldInjection__MemberInjector : MemberInjector<TestFieldInjection<*, *, *>> {
public override fun inject(target: TestFieldInjection<*, *, *>, scope: Scope): Unit {
target.foo = scope.getInstance(Foo::class.java) as Foo
}
}
"""
)
}

0 comments on commit 35518e8

Please sign in to comment.