diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index 05d795ecb..d9a1b487e 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -208,9 +208,13 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { .filter { it.isAbstract } .toList() - if (functions.size != 1 || functions[0].returnType?.resolve() - ?.resolveKSClassDeclaration() != this - ) { + val returnType = functions.singleOrNull()?.returnType?.resolve()?.resolveKSClassDeclaration() + if (returnType != this) { + + val isReturnSuperType = returnType != null && this.superTypes + .any { type -> type.resolve().resolveKSClassDeclaration() == returnType } + if (isReturnSuperType) return + throw KspAnvilException( node = factory, message = "A factory must have exactly one abstract function returning the " + @@ -393,6 +397,11 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { ?.asClassReference() if (returnType != this) { + + val isReturnSuperType = returnType != null && this.directSuperTypeReferences() + .any { it.asClassReference() == returnType } + if (isReturnSuperType) return + throw AnvilCompilationExceptionClassReference( classReference = factory, message = "A factory must have exactly one abstract function returning the " + diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt index 814292ed2..c5396754e 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt @@ -340,6 +340,10 @@ internal class ContributesSubcomponentHandlerGenerator( ) } + val superTypes by lazy { + contribution.clazz.directSuperTypeReferences() + .map { it.asClassReference() } + } val createComponentFunctions = factory.memberFunctions // filter by `isAbstract` even for interfaces, // otherwise we get `toString()`, `equals()`, and `hashCode()`. @@ -349,7 +353,7 @@ internal class ContributesSubcomponentHandlerGenerator( ?.asClassReference() ?: return@filter false - returnType.fqName == contributionFqName + returnType.fqName == contributionFqName || superTypes.any { it == returnType } } if (createComponentFunctions.size != 1) { diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt index 95b52152a..2de7c3e54 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt @@ -110,7 +110,7 @@ class ContributesSubcomponentGeneratorTest( } } - @Test fun `there is a hint for contributed subcomponents with an interace factory`() { + @Test fun `there is a hint for contributed subcomponents with an interface factory`() { compile( """ package com.squareup.test @@ -571,6 +571,46 @@ class ContributesSubcomponentGeneratorTest( } } + @Test fun `a factory function may returns the component super type`() { + compile( + """ + package com.squareup.test + + import com.squareup.anvil.annotations.ContributesSubcomponent + import com.squareup.anvil.annotations.ContributesSubcomponent.Factory + import com.squareup.anvil.annotations.ContributesTo + import com.squareup.anvil.annotations.MergeComponent + + interface BaseSubcomponentInterface { + interface Factory { + fun createComponent(): BaseSubcomponentInterface + } + } + + @ContributesSubcomponent(Any::class, parentScope = Unit::class) + interface SubcomponentInterface : BaseSubcomponentInterface { + @Factory + interface ComponentFactory: BaseSubcomponentInterface.Factory + + @ContributesTo(Unit::class) + interface ParentComponent { + fun createFactory(): ComponentFactory + } + } + + @MergeComponent(Unit::class) + interface ComponentInterface + """, + mode = mode, + ) { + assertThat(subcomponentInterface.hintSubcomponent?.java).isEqualTo(subcomponentInterface) + assertThat(subcomponentInterface.hintSubcomponentParentScope).isEqualTo(Unit::class) + + assertThat(subcomponentInterface.componentFactoryInterface.methods.map { it.name }) + .containsExactly("createComponent") + } + } + @Test fun `using Dagger's @Subcomponent_Factory is an error`() { compile( diff --git a/sample/app/src/test/java/com/squareup/anvil/sample/UserComponentTest.kt b/sample/app/src/test/java/com/squareup/anvil/sample/UserComponentTest.kt new file mode 100644 index 000000000..c074e080a --- /dev/null +++ b/sample/app/src/test/java/com/squareup/anvil/sample/UserComponentTest.kt @@ -0,0 +1,15 @@ +package com.squareup.anvil.sample + +import org.junit.Test + +class UserComponentTest { + + @Test fun `UserComponent is generated`() { + val parent = DaggerAppComponent.create() as UserComponent.Parent + val baseComponent = parent.user().create() + val userComponent = baseComponent as UserComponent + + assert(baseComponent.description() == UserDescriptionModule.provideDescription()) + assert(userComponent.username() == UserDescriptionModule.provideName()) + } +} diff --git a/sample/library/src/main/java/com/squareup/anvil/sample/UserComponent.kt b/sample/library/src/main/java/com/squareup/anvil/sample/UserComponent.kt new file mode 100644 index 000000000..cf58df92d --- /dev/null +++ b/sample/library/src/main/java/com/squareup/anvil/sample/UserComponent.kt @@ -0,0 +1,47 @@ +package com.squareup.anvil.sample + +import com.squareup.anvil.annotations.ContributesSubcomponent +import com.squareup.anvil.annotations.ContributesTo +import com.squareup.scopes.AppScope +import com.squareup.scopes.UserScope +import dagger.Module +import dagger.Provides +import javax.inject.Named + +interface UserDescriptionProvider { + @Named("userDesc") + fun description(): String +} + +@ContributesTo(UserScope::class) +@Module +object UserDescriptionModule { + + @Named("userName") + @Provides + fun provideName(): String = "Anvil User" + + @Named("userDesc") + @Provides + fun provideDescription(): String = "User description" +} + +@ContributesSubcomponent( + scope = UserScope::class, + parentScope = AppScope::class, +) +interface UserComponent : UserDescriptionProvider { + + @Named("userName") + fun username(): String + + @ContributesSubcomponent.Factory + interface Factory { + fun create(): UserDescriptionProvider + } + + @ContributesTo(AppScope::class) + interface Parent { + fun user(): Factory + } +} diff --git a/sample/scopes/src/main/java/com/squareup/scopes/AppScope.kt b/sample/scopes/src/main/java/com/squareup/scopes/AppScope.kt index 85adffeef..b3c1eaa29 100644 --- a/sample/scopes/src/main/java/com/squareup/scopes/AppScope.kt +++ b/sample/scopes/src/main/java/com/squareup/scopes/AppScope.kt @@ -1,3 +1,5 @@ package com.squareup.scopes abstract class AppScope private constructor() + +abstract class UserScope private constructor()