Repository: evant/kotlin-inject (kotlin)

Issue:
Scope annotations aren't picked up when scanning superclasses
In the following case `FooSingleton` isn't picked up from the `BaseFooComponent` interface:

```kotlin
@Scope
annotation class FooSingleton

interface Foo

@Inject
class MyFoo : Foo

@Inject
class FooHolder(
  private val foo: Foo
)

interface BaseFooComponent {
  @Provides @FooSingleton fun provideFoo(): Foo
}

@FooSingleton
@Component
abstract class FooComponent : BaseFooComponent {
  override fun provideFoo(): Foo = MyFoo()

  abstract val holder: FooHolder

  companion object
}
```

The following is generated, without `provideFoo` being scoped:

```kotlin
public fun FooComponent.Companion.create(): FooComponent = InjectFooComponent()

public class InjectFooComponent() : FooComponent(), ScopedComponent {
  override val _scoped: LazyMap = LazyMap()

  override val holder: FooHolder
    get() = FooHolder(
      foo = provideFoo()
    )
}
```

Adding `@FooSingleton` to `provideFoo` in `FooComponent` does cause it to get scoped:

```kotlin
public fun FooComponent.Companion.create(): FooComponent = InjectFooComponent()

public class InjectFooComponent() : FooComponent(), ScopedComponent {
  override val _scoped: LazyMap = LazyMap()

  override val holder: FooHolder
    get() = FooHolder(
      foo = _scoped.get("com.example.Foo") {
        provideFoo()
      }
    )
}
```

Pull request description:
Fixes #354

Patch:
diff --git a/ast/core/src/main/kotlin/me/tatarka/kotlin/ast/Ast.kt b/ast/core/src/main/kotlin/me/tatarka/kotlin/ast/Ast.kt
index ab909fb1..f889333c 100644
--- a/ast/core/src/main/kotlin/me/tatarka/kotlin/ast/Ast.kt
+++ b/ast/core/src/main/kotlin/me/tatarka/kotlin/ast/Ast.kt
@@ -35,9 +35,12 @@ sealed class AstElement
 interface AstAnnotated {
     fun hasAnnotation(packageName: String, simpleName: String): Boolean
 
-    fun annotationAnnotatedWith(packageName: String, simpleName: String): AstAnnotation?
+    fun annotationsAnnotatedWith(packageName: String, simpleName: String): Sequence<AstAnnotation>
 }
 
+fun AstAnnotated.annotationAnnotatedWith(packageName: String, simpleName: String): AstAnnotation? =
+    annotationsAnnotatedWith(packageName, simpleName).firstOrNull()
+
 abstract class AstBasicElement : AstElement() {
     abstract val simpleName: String
 }
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 820f68d9..4325a04c 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
@@ -107,8 +107,8 @@ private interface KSAstAnnotated : AstAnnotated {
         return declaration.hasAnnotation(packageName, simpleName)
     }
 
-    override fun annotationAnnotatedWith(packageName: String, simpleName: String): AstAnnotation? {
-        return declaration.annotationAnnotatedWith(packageName, simpleName)?.let {
+    override fun annotationsAnnotatedWith(packageName: String, simpleName: String): Sequence<AstAnnotation> {
+        return declaration.annotationsAnnotatedWith(packageName, simpleName).map {
             KSAstAnnotation(resolver, it)
         }
     }
@@ -362,9 +362,15 @@ private class KSAstProperty(override val resolver: Resolver, override val declar
         return declaration.getter?.hasAnnotation(packageName, simpleName) == true
     }
 
-    override fun annotationAnnotatedWith(packageName: String, simpleName: String): AstAnnotation? {
-        return declaration.getter?.annotationAnnotatedWith(packageName, simpleName)?.let {
-            KSAstAnnotation(resolver, it)
+    override fun annotationsAnnotatedWith(packageName: String, simpleName: String): Sequence<AstAnnotation> {
+        val declarationAnnotations = super.annotationsAnnotatedWith(packageName, simpleName)
+        val getter = declaration.getter
+        return if (getter == null) {
+            declarationAnnotations
+        } else {
+            getter.annotationsAnnotatedWith(packageName, simpleName).map { annotation ->
+                KSAstAnnotation(resolver, annotation)
+            } + declarationAnnotations
         }
     }
 
@@ -514,4 +520,4 @@ private class KSAstAnnotation(private val resolver: Resolver, val annotation: KS
             annotation.arguments.joinToString(", ") { arg -> "${arg.name?.asString()}=${arg.value}" }
         })"
     }
-}
\ No newline at end of file
+}
diff --git a/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSUtil.kt b/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSUtil.kt
index e4c828ba..49c3b6cc 100644
--- a/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSUtil.kt
+++ b/ast/ksp/src/main/kotlin/me/tatarka/kotlin/ast/KSUtil.kt
@@ -14,14 +14,15 @@ import me.tatarka.kotlin.ast.internal.collectHash
 import me.tatarka.kotlin.ast.internal.eqv
 import me.tatarka.kotlin.ast.internal.eqvItr
 
-internal fun KSAnnotated.annotationAnnotatedWith(packageName: String, simpleName: String): KSAnnotation? {
-    for (annotation in annotations) {
+internal fun KSAnnotated.annotationsAnnotatedWith(packageName: String, simpleName: String): Sequence<KSAnnotation> {
+    return annotations.mapNotNull { annotation ->
         val t = annotation.annotationType.resolve()
         if (t.declaration.hasAnnotation(packageName, simpleName)) {
-            return annotation
+            annotation
+        } else {
+            null
         }
     }
-    return null
 }
 
 internal fun KSAnnotated.hasAnnotation(packageName: String, simpleName: String): Boolean {
@@ -126,4 +127,4 @@ internal fun signatureEquals(left: KSFunctionDeclaration, right: KSFunctionDecla
         left.extensionReceiver?.resolve() == right.extensionReceiver?.resolve() &&
         left.parameters.size == right.parameters.size &&
         left.parameters.map { it.type.resolve() } == right.parameters.map { it.type.resolve() }
-}
\ No newline at end of file
+}
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 f4a7bbce..3a78cdd5 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
@@ -19,6 +19,7 @@ import me.tatarka.kotlin.ast.AstProvider
 import me.tatarka.kotlin.ast.AstType
 import me.tatarka.kotlin.ast.AstVisibility
 import me.tatarka.kotlin.ast.Messenger
+import me.tatarka.kotlin.ast.annotationAnnotatedWith
 
 private const val ANNOTATION_PACKAGE_NAME = "me.tatarka.inject.annotations"
 val COMPONENT = ClassName(ANNOTATION_PACKAGE_NAME, "Component")
@@ -183,13 +184,20 @@ class InjectGenerator(
 }
 
 fun AstAnnotated.scopeType(options: Options): AstType? {
+    return scopeTypes(options).firstOrNull()
+}
+
+fun AstAnnotated.scopeTypes(options: Options): Sequence<AstType> {
+    val scopeAnnotations = annotationsAnnotatedWith(SCOPE.packageName, SCOPE.simpleName).map { annotation ->
+        annotation.type
+    }
+
     if (options.enableJavaxAnnotations) {
-        val annotation = annotationAnnotatedWith(JAVAX_SCOPE.packageName, JAVAX_SCOPE.simpleName)
-        if (annotation != null) {
-            return annotation.type
-        }
+        return annotationsAnnotatedWith(JAVAX_SCOPE.packageName, JAVAX_SCOPE.simpleName).map { annotation ->
+            annotation.type
+        } + scopeAnnotations
     }
-    return annotationAnnotatedWith(SCOPE.packageName, SCOPE.simpleName)?.type
+    return scopeAnnotations
 }
 
 fun AstAnnotated.isComponent() = hasAnnotation(COMPONENT.packageName, COMPONENT.simpleName)
@@ -282,4 +290,4 @@ private fun dumpGraph(astClass: AstClass, entries: List<TypeResult.Provider>): S
         }
     }
     return out.toString()
-}
\ No newline at end of file
+}
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 bd6ddde0..900d5edc 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
@@ -99,8 +99,7 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
                 }
             }
 
-            for (method in typeInfo.providesMethods) {
-                val scope = method.scopeType(options)
+            for ((method, scope) in typeInfo.providesMethods) {
                 if (scope != null && scope != typeInfo.elementScope) {
                     if (typeInfo.elementScope != null) {
                         provider.error(
@@ -253,7 +252,7 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
         return typeInfoCache.getOrPut(astClass.toString()) {
             val isComponent = astClass.isComponent()
 
-            val providesMethods = mutableListOf<AstMember>()
+            val providesMethods = mutableListOf<Pair<AstMember, AstType?>>()
             val providerMethods = mutableListOf<AstMember>()
 
             var scopeClass: AstClass? = null
@@ -279,7 +278,10 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
             // some methods may override others
             val methods = mutableListOf<AstMember>()
             val isProvides = mutableMapOf<AstMember, Boolean>()
+            val scopeTypes = mutableMapOf<AstMember, Set<AstType>>()
+            val scopedMethodsWithScopedSuperMethod = mutableMapOf<AstMember, Pair<AstMember, Set<AstType>>>()
             for (method in allMethods) {
+                val methodScopeTypes = method.scopeTypes(options).toSet()
                 val existing = methods.firstOrNull {
                     it.name == method.name && (it.signatureEquals(method) || it.overrides(method))
                 }
@@ -288,14 +290,48 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
                     if (method.isProvides()) {
                         isProvides[existing] = true
                     }
+
+                    if (methodScopeTypes.isNotEmpty()) {
+                        val existingScopeTypes = scopeTypes[existing]
+                        if (existingScopeTypes == null) {
+                            scopeTypes[existing] = methodScopeTypes
+                        } else {
+                            if (existingScopeTypes != methodScopeTypes) {
+                                scopedMethodsWithScopedSuperMethod[existing] = method to methodScopeTypes
+                            }
+                        }
+                    }
                 } else {
                     methods.add(method)
                     isProvides[method] = method.isProvides()
+                    if (methodScopeTypes.isNotEmpty()) {
+                        scopeTypes[method] = methodScopeTypes
+                    }
                 }
             }
             for (method in methods) {
                 val abstract = method.isAbstract
+                val methodScopeTypes = scopeTypes[method]
                 if (isProvides.getValue(method)) {
+                    var methodScope: AstType? = null
+                    if (methodScopeTypes != null) {
+                        val scopedMethodWithScopedSuperMethod = scopedMethodsWithScopedSuperMethod[method]
+                        if (methodScopeTypes.size > 1) {
+                            provider.error("Cannot apply multiple scopes: $methodScopeTypes", method)
+                            continue
+                        } else if (scopedMethodWithScopedSuperMethod != null) {
+                            val (scopedSuperMethod, superMethodScopes) = scopedMethodWithScopedSuperMethod
+                            provider.error("Cannot apply scope: ${methodScopeTypes.first()}", method)
+                            provider.error(
+                                "as scope: ${(superMethodScopes - methodScopeTypes).first()} is already applied",
+                                scopedSuperMethod
+                            )
+                            continue
+                        } else {
+                            methodScope = methodScopeTypes.first()
+                        }
+                    }
+
                     if (method.visibility == AstVisibility.PRIVATE) {
                         provider.error("@Provides method must not be private", method)
                         continue
@@ -320,13 +356,12 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
                         provider.error("@Provides method must have a concrete implementation", method)
                         continue
                     } else {
-                        providesMethods.add(method)
+                        providesMethods.add(method to methodScope)
                     }
                 } else if (method.isProvider()) {
-                    val scope = method.scopeType(options)
-                    if (scope != null) {
+                    if (methodScopeTypes != null) {
                         provider.warn(
-                            "Scope: @${scope.simpleName} has no effect." +
+                            "Scope: @${methodScopeTypes.first().simpleName} has no effect." +
                                 " Place on @Provides function or @Inject constructor instead.",
                             method
                         )
@@ -346,7 +381,7 @@ class TypeCollector(private val provider: AstProvider, private val options: Opti
 }
 
 class TypeInfo(
-    val providesMethods: List<AstMember> = emptyList(),
+    val providesMethods: List<Pair<AstMember, AstType?>> = emptyList(),
     val providerMethods: List<AstMember> = emptyList(),
     val scopeClass: AstClass? = null,
     val elementScope: AstType? = null,
@@ -401,4 +436,4 @@ sealed class ContainerKey {
 data class ScopedComponent(
     val type: AstClass,
     val accessor: Accessor,
-)
\ No newline at end of file
+)


Test patch:
diff --git a/integration-tests/common-test/src/test/kotlin/me/tatarka/inject/test/ScopeTest.kt b/integration-tests/common-test/src/test/kotlin/me/tatarka/inject/test/ScopeTest.kt
index 6c804855..d8e8299f 100644
--- a/integration-tests/common-test/src/test/kotlin/me/tatarka/inject/test/ScopeTest.kt
+++ b/integration-tests/common-test/src/test/kotlin/me/tatarka/inject/test/ScopeTest.kt
@@ -134,4 +134,12 @@ class ScopeTest {
 
         assertThat(component.foo).isNotNull()
     }
+
+    @Test
+    fun generates_a_component_that_scopes_a_provided_function_that_is_annotated_in_a_superclass() {
+        val component = SuperclassScopeFooComponent::class.create()
+
+        assertThat(component.holder.foo).isSameInstanceAs(component.holder.foo)
+        assertThat(component.holder.foo2).isSameInstanceAs(component.holder.foo2)
+    }
 }
diff --git a/integration-tests/common/src/main/kotlin/me/tatarka/inject/test/Scope.kt b/integration-tests/common/src/main/kotlin/me/tatarka/inject/test/Scope.kt
index f1a57d2f..3dd8a4a5 100644
--- a/integration-tests/common/src/main/kotlin/me/tatarka/inject/test/Scope.kt
+++ b/integration-tests/common/src/main/kotlin/me/tatarka/inject/test/Scope.kt
@@ -202,3 +202,37 @@ interface DuplicateScopeInterface {
 abstract class DuplicateScopeComponent : DuplicateScopeInterface {
     abstract val foo: IFoo
 }
+
+@Scope
+annotation class FooSingleton
+
+interface SuperclassScopeFoo
+interface SuperclassScopeFoo2
+
+@Inject
+class SuperclassScopeFooImpl : SuperclassScopeFoo
+
+@Inject
+class SuperclassScopeFoo2Impl : SuperclassScopeFoo2
+
+@Inject
+class SuperclassScopeFooHolder(
+    val foo: SuperclassScopeFoo,
+    val foo2: SuperclassScopeFoo2
+)
+
+interface BaseSuperclassScopeFooComponent {
+    @Provides @FooSingleton fun provideFoo(): SuperclassScopeFoo
+    @get:Provides @FooSingleton val foo: SuperclassScopeFoo2
+}
+
+@FooSingleton
+@Component
+abstract class SuperclassScopeFooComponent : BaseSuperclassScopeFooComponent {
+    @FooSingleton override fun provideFoo(): SuperclassScopeFoo = SuperclassScopeFooImpl()
+    override val foo: SuperclassScopeFoo2 get() = SuperclassScopeFoo2Impl()
+
+    abstract val holder: SuperclassScopeFooHolder
+
+    companion object
+}
diff --git a/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt b/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
index ccc0d8fb..99e2a349 100644
--- a/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
+++ b/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
@@ -851,4 +851,188 @@ class FailureTest {
             contains("@Provides scoped with @MyScope cannot be suspend, consider returning Deferred<T> instead.")
         }
     }
-}
\ No newline at end of file
+
+    @ParameterizedTest
+    @EnumSource(Target::class)
+    fun fails_if_there_are_multiple_scopes_applied(target: Target) {
+        val projectCompiler = ProjectCompiler(target, workingDir)
+
+        assertFailure {
+            projectCompiler.source(
+                "MyComponent.kt",
+                """
+                import me.tatarka.inject.annotations.Component
+                import me.tatarka.inject.annotations.Inject
+                import me.tatarka.inject.annotations.Provides
+                import me.tatarka.inject.annotations.Scope
+                
+                @Scope
+                annotation class FooSingleton
+                
+                @Scope
+                annotation class FooSingleton2
+                
+                @Scope
+                annotation class FooSingleton3
+                
+                interface Foo
+                
+                @Inject
+                class FooImpl : Foo
+                
+                @Inject
+                class FooHolder(
+                    val foo: Foo
+                )
+                
+                @FooSingleton
+                @Component
+                abstract class MyFunctionProviderComponent {
+                    @Provides @FooSingleton @FooSingleton2 fun provideFoo(): Foo = FooImpl()
+                
+                    abstract val holder: FooHolder
+                
+                    companion object
+                }
+                
+                @FooSingleton2
+                @Component
+                abstract class MyPropertyProviderComponent {
+                    @get:Provides @FooSingleton @FooSingleton3 val provideFoo: Foo get() = FooImpl()
+                
+                    abstract val holder: FooHolder
+                
+                    companion object
+                }
+                """.trimIndent()
+            ).compile()
+        }.output().all {
+            contains("Cannot apply multiple scopes: [FooSingleton, FooSingleton2]")
+            contains("Cannot apply multiple scopes: [FooSingleton, FooSingleton3]")
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(Target::class)
+    fun fails_if_there_are_scopes_applied_to_a_super_type(target: Target) {
+        val projectCompiler = ProjectCompiler(target, workingDir)
+
+        assertFailure {
+            projectCompiler.source(
+                "MyComponent.kt",
+                """
+                import me.tatarka.inject.annotations.Component
+                import me.tatarka.inject.annotations.Inject
+                import me.tatarka.inject.annotations.Provides
+                import me.tatarka.inject.annotations.Scope
+                
+                @Scope
+                annotation class FooSingleton
+                
+                @Scope
+                annotation class FooSingleton2
+                
+                
+                interface SuperclassScopeFoo
+                interface SuperclassScopeFoo2
+                
+                @Inject
+                class SuperclassScopeFooImpl : SuperclassScopeFoo
+                
+                @Inject
+                class SuperclassScopeFoo2Impl : SuperclassScopeFoo2
+                
+                @Inject
+                class SuperclassScopeFooHolder(
+                    val foo: SuperclassScopeFoo,
+                    val foo2: SuperclassScopeFoo2
+                )
+                
+                interface BaseSuperclassScopeFooComponent {
+                    @Provides @FooSingleton fun provideFoo(): SuperclassScopeFoo
+                    @get:Provides @FooSingleton2 val foo: SuperclassScopeFoo2
+                }
+                
+                @FooSingleton
+                @Component
+                abstract class SuperclassScopeFooComponent : BaseSuperclassScopeFooComponent {
+                    @FooSingleton2 override fun provideFoo(): SuperclassScopeFoo = SuperclassScopeFooImpl()
+                    @FooSingleton override val foo: SuperclassScopeFoo2 get() = SuperclassScopeFoo2Impl()
+                
+                    abstract val holder: SuperclassScopeFooHolder
+                
+                    companion object
+                }
+                """.trimIndent()
+            ).compile()
+        }.output().all {
+            contains("Cannot apply scope: FooSingleton2")
+            contains("as scope: FooSingleton is already applied")
+
+            contains("Cannot apply scope: FooSingleton")
+            contains("as scope: FooSingleton2 is already applied")
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(Target::class)
+    fun fails_with_correct_error_if_there_are_multiple_scopes_applied_to_a_super_type(target: Target) {
+        val projectCompiler = ProjectCompiler(target, workingDir)
+
+        assertFailure {
+            projectCompiler.source(
+                "MyComponent.kt",
+                """
+                import me.tatarka.inject.annotations.Component
+                import me.tatarka.inject.annotations.Inject
+                import me.tatarka.inject.annotations.Provides
+                import me.tatarka.inject.annotations.Scope
+                
+                @Scope
+                annotation class FooSingleton
+                
+                @Scope
+                annotation class FooSingleton2
+                
+                
+                interface SuperclassScopeFoo
+                interface SuperclassScopeFoo2
+                
+                @Inject
+                class SuperclassScopeFooImpl : SuperclassScopeFoo
+                
+                @Inject
+                class SuperclassScopeFoo2Impl : SuperclassScopeFoo2
+                
+                @Inject
+                class SuperclassScopeFooHolder(
+                    val foo: SuperclassScopeFoo,
+                    val foo2: SuperclassScopeFoo2
+                )
+                
+                interface BaseSuperclassScopeFooComponent {
+                    @Provides @FooSingleton @FooSingleton2 fun provideFoo(): SuperclassScopeFoo
+                    @get:Provides @FooSingleton2 @FooSingleton val foo: SuperclassScopeFoo2
+                }
+                
+                @FooSingleton
+                @Component
+                abstract class SuperclassScopeFooComponent : BaseSuperclassScopeFooComponent {
+                    @FooSingleton2 override fun provideFoo(): SuperclassScopeFoo = SuperclassScopeFooImpl()
+                    @FooSingleton override val foo: SuperclassScopeFoo2 get() = SuperclassScopeFoo2Impl()
+                
+                    abstract val holder: SuperclassScopeFooHolder
+                
+                    companion object
+                }
+                """.trimIndent()
+            ).compile()
+        }.output().all {
+            contains("Cannot apply scope: FooSingleton2")
+            contains("as scope: FooSingleton is already applied")
+
+            contains("Cannot apply scope: FooSingleton")
+            contains("as scope: FooSingleton2 is already applied")
+        }
+    }
+}
diff --git a/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/WarningTest.kt b/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/WarningTest.kt
index c4c0b8c7..682c7c49 100644
--- a/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/WarningTest.kt
+++ b/kotlin-inject-compiler/test/src/test/kotlin/me/tatarka/inject/test/WarningTest.kt
@@ -79,4 +79,40 @@ class WarningTest {
             contains("Scope: @MyScope2 has no effect. Place on @Provides function or @Inject constructor instead.")
         }
     }
-}
\ No newline at end of file
+
+    @ParameterizedTest
+    @EnumSource(Target::class)
+    fun warns_on_multiple_scope_on_provider_method_which_is_ignored(target: Target) {
+        val projectCompiler = ProjectCompiler(target, workingDir)
+
+        assertThat(
+            projectCompiler.source(
+                "MyComponent.kt",
+                """
+                import me.tatarka.inject.annotations.Component
+                import me.tatarka.inject.annotations.Scope
+                import me.tatarka.inject.annotations.Inject
+                import me.tatarka.inject.annotations.Provides
+                
+                @Scope annotation class MyScope1
+                @Scope annotation class MyScope2
+                
+                @MyScope1 @Component abstract class MyComponent1 {
+                    @get:MyScope1 @get:MyScope2 abstract val foo: String
+                    
+                    @Provides fun str(): String = ""
+                }
+                
+                @MyScope2 @Component abstract class MyComponent2 {
+                    @MyScope2 @MyScope1 abstract fun bar(): String
+                    
+                    @Provides fun str(): String = ""
+                }
+                """.trimIndent()
+            ).compile()
+        ).warnings().all {
+            contains("Scope: @MyScope1 has no effect. Place on @Provides function or @Inject constructor instead.")
+            contains("Scope: @MyScope2 has no effect. Place on @Provides function or @Inject constructor instead.")
+        }
+    }
+}