From 26282906c90f595f7c46a271cbfc9c2135d9d20f Mon Sep 17 00:00:00 2001 From: Ian Ribas Date: Mon, 29 Jul 2024 20:14:08 -0300 Subject: [PATCH] Handle generic classes when generating member injectors 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. --- .../generators/MemberInjectorGenerator.kt | 15 +- .../memberinjector/FieldMemberInjectorTest.kt | 132 ++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/compiler-memberinjector/src/main/java/toothpick/compiler/memberinjector/generators/MemberInjectorGenerator.kt b/compiler-memberinjector/src/main/java/toothpick/compiler/memberinjector/generators/MemberInjectorGenerator.kt index 7693960..ca8fc49 100644 --- a/compiler-memberinjector/src/main/java/toothpick/compiler/memberinjector/generators/MemberInjectorGenerator.kt +++ b/compiler-memberinjector/src/main/java/toothpick/compiler/memberinjector/generators/MemberInjectorGenerator.kt @@ -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 @@ -60,6 +62,7 @@ internal class MemberInjectorGenerator( } } + private val parameterizedSourceClass: TypeName = sourceClass.maybeParameterizedClassName() val sourceClassName: ClassName = sourceClass.toClassName() val generatedClassName: ClassName = sourceClassName.memberInjectorClassName @@ -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) @@ -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 @@ -111,7 +122,7 @@ internal class MemberInjectorGenerator( addFunction( FunSpec.builder("inject") .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE) - .addParameter("target", sourceClassName) + .addParameter("target", parameterizedSourceClass) .addParameter("scope", Scope::class) .apply { if (superClassThatNeedsInjection != null) { diff --git a/compiler-memberinjector/src/test/java/toothpick/compiler/memberinjector/FieldMemberInjectorTest.kt b/compiler-memberinjector/src/test/java/toothpick/compiler/memberinjector/FieldMemberInjectorTest.kt index 0813faf..4013230 100644 --- a/compiler-memberinjector/src/test/java/toothpick/compiler/memberinjector/FieldMemberInjectorTest.kt +++ b/compiler-memberinjector/src/test/java/toothpick/compiler/memberinjector/FieldMemberInjectorTest.kt @@ -1605,4 +1605,136 @@ 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 { + @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 { + @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 toothpick.MemberInjector + import toothpick.Scope + + @Suppress( + "ClassName", + "RedundantVisibilityModifier", + "UNCHECKED_CAST", + ) + public class TestFieldInjection__MemberInjector : MemberInjector> { + public override fun inject(target: TestFieldInjection<*>, scope: Scope) { + 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 { + @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 { + @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 toothpick.MemberInjector + import toothpick.Scope + + @Suppress( + "ClassName", + "RedundantVisibilityModifier", + "UNCHECKED_CAST", + ) + public class TestFieldInjection__MemberInjector : MemberInjector> { + public override fun inject(target: TestFieldInjection<*, *, *>, scope: Scope) { + target.foo = scope.getInstance(Foo::class.java) as Foo + } + } + """ + ) }