diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt index 87534a5fb..fe058d492 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.psiUtil.parents @@ -61,12 +62,7 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() { .asSequence() .filter { it.hasAnnotation(daggerProvidesFqName) } .onEach { function -> - if (clazz.isInterface() || function.hasModifier(ABSTRACT_KEYWORD)) { - throw AnvilCompilationException( - "@Provides methods cannot be abstract", - element = function - ) - } + checkFunctionIsNotAbstract(clazz, function) } .also { functions -> // Check for duplicate function names. @@ -293,4 +289,28 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() { return createGeneratedFile(codeGenDir, packageName, className, content) } + + private fun checkFunctionIsNotAbstract( + clazz: KtClassOrObject, + function: KtFunction + ) { + fun fail(): Nothing = throw AnvilCompilationException( + "@Provides methods cannot be abstract", + element = function + ) + + // If the function is abstract, then it's an error. + if (function.hasModifier(ABSTRACT_KEYWORD)) fail() + + // If the class is not an interface and doesn't use the abstract keyword, then there is + // no issue. + if (!clazz.isInterface()) return + + // If the parent of the function is a companion object, then the function inside of the + // interface is not abstract. + val parentClass = function.parents.filterIsInstance().firstOrNull() ?: return + if (parentClass is KtObjectDeclaration && parentClass.isCompanion()) return + + fail() + } } diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt index 846bc10a0..e2a23cef8 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/dagger/ProvidesMethodFactoryGeneratorTest.kt @@ -2883,6 +2883,27 @@ public final class DaggerModule1_GetStringFactory implements Factory { } } + @Test fun `an interface with a companion object is allowed to contain a provider method`() { + compile( + """ + package com.squareup.test + + import dagger.Module + import dagger.Provides + + @Module + interface DaggerModule1 { + companion object { + @Provides fun provideString(): String = "" + } + } + """ + ) { + val factoryClass = daggerModule1.moduleFactoryClass("provideString", companion = true) + assertThat(factoryClass).isNotNull() + } + } + @Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS") private fun compile( vararg sources: String,