From 3db6d16dca991abbbbed5e7441606966c4d44810 Mon Sep 17 00:00:00 2001 From: Ralf Wondratschek Date: Mon, 28 Oct 2024 13:00:54 -0700 Subject: [PATCH] Generate binding method for subcomponent factory Generate a binding method for types annotated with `@ContributesSubcomponent.Factory` in the parent scope. This allows us to inject the factory type itself. Fixes #49 --- ...ContributesSubcomponentFactoryProcessor.kt | 17 +++++++- .../ContributesSubcomponentProcessorTest.kt | 41 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentFactoryProcessor.kt b/compiler/src/main/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentFactoryProcessor.kt index 4b1ba7a..5242e00 100644 --- a/compiler/src/main/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentFactoryProcessor.kt +++ b/compiler/src/main/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentFactoryProcessor.kt @@ -10,10 +10,12 @@ import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.ksp.addOriginatingKSFile import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.writeTo +import me.tatarka.inject.annotations.Provides import software.amazon.lastmile.kotlin.inject.anvil.ContextAware import software.amazon.lastmile.kotlin.inject.anvil.ContributesSubcomponent import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo @@ -27,7 +29,8 @@ import software.amazon.lastmile.kotlin.inject.anvil.internal.Subcomponent * In the lookup package [LOOKUP_PACKAGE] a new interface is generated extending the contributed * interface. To avoid name clashes the package name of the original interface is encoded in the * interface name. This is very similar to [ContributesTo] with the key differences that there - * are more strict checks and that [Subcomponent] is added as marker. + * are more strict checks and that [Subcomponent] is added as marker. Further, an `@Provides` + * (binding) function is generated to be able to inject the factory type. * ``` * package software.amazon.test * @@ -46,7 +49,10 @@ import software.amazon.lastmile.kotlin.inject.anvil.internal.Subcomponent * * @Origin(Subcomponent.Factory::class) * @Subcomponent - * interface SoftwareAmazonTestSubcomponentFactory : Subcomponent.Factory + * interface SoftwareAmazonTestSubcomponentFactory : Subcomponent.Factory { + * @Provides + * fun provideSubcomponentFactory(): Subcomponent.Factory = this + * } * ``` */ @OptIn(KspExperimental::class) @@ -95,6 +101,13 @@ internal class ContributesSubcomponentFactoryProcessor( .addOriginAnnotation(factory) .addAnnotation(Subcomponent::class) .addSuperinterface(factory.toClassName()) + .addFunction( + FunSpec.builder("provide${factory.innerClassNames()}") + .addAnnotation(Provides::class) + .returns(factory.toClassName()) + .addStatement("return this") + .build() + ) .build(), ) .build() diff --git a/compiler/src/test/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentProcessorTest.kt b/compiler/src/test/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentProcessorTest.kt index f351ccf..3a1e415 100644 --- a/compiler/src/test/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentProcessorTest.kt +++ b/compiler/src/test/kotlin/software/amazon/lastmile/kotlin/inject/anvil/processor/ContributesSubcomponentProcessorTest.kt @@ -490,6 +490,47 @@ class ContributesSubcomponentProcessorTest { } } + @Test + fun `the factory interface is provided and can be injected in the parent scope`() { + compile( + """ + package software.amazon.test + + import software.amazon.lastmile.kotlin.inject.anvil.AppScope + import software.amazon.lastmile.kotlin.inject.anvil.ContributesSubcomponent + import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo + import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent + import software.amazon.lastmile.kotlin.inject.anvil.SingleIn + import me.tatarka.inject.annotations.Component + import me.tatarka.inject.annotations.Provides + + @MergeComponent(AppScope::class) + @Component + @SingleIn(AppScope::class) + interface ComponentInterface : ComponentInterfaceMerged { + val factory: LoggedInComponent.Factory + } + + @ContributesSubcomponent(LoggedInScope::class) + @SingleIn(LoggedInScope::class) + interface LoggedInComponent { + @ContributesSubcomponent.Factory(AppScope::class) + interface Factory { + fun loggedInComponent(): LoggedInComponent + } + } + """, + scopesSource, + ) { + val component = componentInterface.newComponent() + val loggedInComponent = component::class.java.methods + .single { it.name == "getFactory" } + .invoke(component) + + assertThat(loggedInComponent).isNotNull() + } + } + private val JvmCompilationResult.componentInterface2: Class<*> get() = classLoader.loadClass("software.amazon.test.ComponentInterface2")