Repository: evant/kotlin-inject (kotlin)

Issue:
Injection fails for class who have a dependency declared in an inherited component
Hi, 

I tried to refactor my injection in a Multiplatform project, so instead of composing different components for every platform (which forced me to have different scope annotations in the common and the platform-specific components) I inherit the common component in every single platform and provide there the few dependencies that remain unprovided. 

This is, IIUC, supported by [Component Inheritance](https://github.com/evant/kotlin-inject/tree/main#component-inheritance), even if the use case shown there is just testing-related. 

My problem is that I think I've hit a bug, as both of these attempts fail with the same `Cannot find an @Inject constructor or provider for: A` error.

```kotlin
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import me.tatarka.inject.annotations.Scope
import kotlin.annotation.Target
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
                
@Scope
@Target(CLASS, FUNCTION, PROPERTY_GETTER)
annotation class MyScope

class A
                
@Inject
class B(val a: A)

@MyScope
abstract class SuperComponent {      
       abstract val b : B
          
      @Provides abstract fun providesA() : A
}
               
@Component
abstract class ImplementationComponent: SuperComponent() {
       override fun providesA() = A()
}
```

```kotlin
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import me.tatarka.inject.annotations.Scope
import kotlin.annotation.Target
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
                
@Scope
@Target(CLASS, FUNCTION, PROPERTY_GETTER)
annotation class MyScope

class A
                
@Inject
class B(val a: A)

@MyScope
abstract class SuperComponent {                
      @Provides abstract fun providesA() : A
}
               
@Component
abstract class ImplementationComponent: SuperComponent() {
       abstract val b : B
                
       override fun providesA() = A()
}
```

Ideally the first one would be my desired approach, the second one is just an attempt to create a workaround.

Am I doing something wrong here or is it a limitation of the library?

Pull request:
Fixes #269

Patch:
diff --git a/ast/kapt/src/main/kotlin/me/tatarka/kotlin/ast/ModelAst.kt b/ast/kapt/src/main/kotlin/me/tatarka/kotlin/ast/ModelAst.kt
index 302a41e7..a5d3eaa6 100644
--- a/ast/kapt/src/main/kotlin/me/tatarka/kotlin/ast/ModelAst.kt
+++ b/ast/kapt/src/main/kotlin/me/tatarka/kotlin/ast/ModelAst.kt
@@ -334,9 +334,7 @@ private class ModelAstClass(
             addAll(methods)
             for (superType in superTypes) {
                 for (superMethod in superType.allMethods) {
-                    if (none { it.overrides(superMethod) }) {
-                        add(superMethod)
-                    }
+                    add(superMethod)
                 }
             }
         }
diff --git a/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSAst.kt b/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSAst.kt
index 21a910a8..bc5060a2 100644
--- a/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSAst.kt
+++ b/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSAst.kt
@@ -206,13 +206,9 @@ private class KSAstClass(override val resolver: Resolver, override val declarati
             for (type in declaration.inheritanceChain()) {
                 for (declaration in type.declarations) {
                     if (declaration is KSPropertyDeclaration) {
-                        if (declarations.none { resolver.overrides(it, declaration) }) {
-                            declarations.add(declaration)
-                        }
+                        declarations.add(declaration)
                     } else if (declaration is KSFunctionDeclaration && !declaration.isConstructor()) {
-                        if (declarations.none { resolver.overrides(it, declaration) }) {
-                            declarations.add(declaration)
-                        }
+                        declarations.add(declaration)
                     }
                 }
             }
diff --git a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
index 138cc98d..f4a7bbce 100644
--- a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
+++ b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
@@ -232,7 +232,7 @@ fun AstAnnotated.qualifier(options: Options): AstAnnotation? {
 }
 
 fun AstMember.isProvider(): Boolean =
-    !isProvides() && isAbstract && when (this) {
+    isAbstract && when (this) {
         is AstFunction -> parameters.isEmpty()
         is AstProperty -> true
     } && receiverParameterType == null && returnType.isNotUnit()
diff --git a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt
index b8b1ab07..955642dd 100644
--- a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt
+++ b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeCollector.kt
@@ -267,9 +267,21 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
                 }
             }
 
-            for (method in astClass.allMethods) {
+            val allMethods = astClass.allMethods
+            // some methods may override others
+            val methods = mutableMapOf<AstMember, AstMember?>()
+            for (method in allMethods) {
+                val overrides = methods.keys.find { it.overrides(method) }
+                if (overrides != null) {
+                    methods[overrides] = method
+                } else {
+                    methods[method] = null
+                }
+            }
+            for (method in methods.keys) {
                 val abstract = method.isAbstract
-                if (method.isProvides()) {
+                val overriden = methods[method]
+                if (method.isProvides() || overriden?.isProvides() == true) {
                     if (method.visibility == AstVisibility.PRIVATE) {
                         provider.error("@Provides method must not be private", method)
                         continue
@@ -296,8 +308,7 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
                     } else {
                         providesMethods.add(method)
                     }
-                }
-                if (method.isProvider()) {
+                } else if (method.isProvider()) {
                     val scope = method.scopeType(options)
                     if (scope != null) {
                         provider.warn(


Test patch:
diff --git a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InheritanceTest.kt b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InheritanceTest.kt
index e0c9e3db..ea882f60 100644
--- a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InheritanceTest.kt
+++ b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InheritanceTest.kt
@@ -5,7 +5,9 @@ import assertk.assertions.hasClass
 import assertk.assertions.isNotNull
 import assertk.assertions.isSameAs
 import me.tatarka.inject.annotations.Component
+import me.tatarka.inject.annotations.Inject
 import me.tatarka.inject.annotations.Provides
+import me.tatarka.inject.annotations.Scope
 import kotlin.test.Test
 
 interface ComponentInterface {
@@ -59,6 +61,19 @@ abstract class ProvidesScopedInterfaceComponent : ProvidesScopedComponentInterfa
     abstract val foo: IFoo
 }
 
+
+interface AbstractProvidesInterface {
+    @get:Provides val foo: IFoo
+}
+
+@Component
+abstract class AbstractProvidesImplComponent: AbstractProvidesInterface {
+    abstract val bar: BarImpl
+
+    override val foo: IFoo
+        get() = Foo()
+}
+
 class InheritanceTest {
     @Test
     fun generates_a_component_that_provides_a_dep_defined_in_an_implemented_interface() {