Repository: evant/kotlin-inject (kotlin)

Issue:
Multi-binding does not work with lambdas
Let's say we have the following component:

```kotlin
@Component
abstract class MyComponent {
  @Provides @IntoSet fun provides1(): () -> Unit = {}
  @Provides @IntoSet fun provides2(): () -> Unit = {}

  abstract val mySet: Set<() -> Unit>
}
```

This fails to compile with the following error message:

>[ksp] Cannot find an @Inject constructor or provider for: Set<kotlin.Function0<kotlin.Unit>>

Ideally we would be able to have this kind of type in a multibinding setup. There are two possible workarounds currently:
- Use a typealias (it doesn't work right now but would most likely work once https://github.com/evant/kotlin-inject/issues/240 is fixed?)
- Wrap the lambda in another type (e.g. `class Wrapper(val value: () -> Unit)`)

Pull request:
Normally types themselves are looked up before lambda and lazy versions of that type so that if for example you are providing a lambda it resolves first to the provided type. Sets were doing it backwards which means it always treated lambdas specially instead of allowing you to provide them.

Fixes #242

Patch:
diff --git a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultResolver.kt b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultResolver.kt
index 48fe6c68..b7d87570 100644
--- a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultResolver.kt
+++ b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultResolver.kt
@@ -246,6 +246,19 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
 
     private fun Context.set(key: TypeKey): TypeResult? {
         val innerType = key.type.arguments[0]
+
+        val containerKey = ContainerKey.SetKey(innerType, key.qualifier)
+        val args = types.containerArgs(containerKey)
+        if (args.isNotEmpty()) {
+            return Container(
+                creator = containerKey.creator,
+                args = args,
+                mapArg = { key, arg, types ->
+                    Provides(withTypes(types), arg.accessor, arg.method, key)
+                }
+            )
+        }
+
         if (innerType.isFunction()) {
             val containerKey = ContainerKey.SetKey(innerType.arguments.last(), key.qualifier)
             val args = types.containerArgs(containerKey)
@@ -260,6 +273,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
                 }
             )
         }
+
         if (innerType.isLazy()) {
             val containerKey = ContainerKey.SetKey(innerType.arguments[0], key.qualifier)
             val args = types.containerArgs(containerKey)
@@ -274,17 +288,7 @@ class TypeResultResolver(private val provider: AstProvider, private val options:
                 }
             )
         }
-
-        val containerKey = ContainerKey.SetKey(innerType, key.qualifier)
-        val args = types.containerArgs(containerKey)
-        if (args.isEmpty()) return null
-        return Container(
-            creator = containerKey.creator,
-            args = args,
-            mapArg = { key, arg, types ->
-                Provides(withTypes(types), arg.accessor, arg.method, key)
-            }
-        )
+        return null
     }
 
     private fun Context.map(key: TypeKey): TypeResult? {


Test patch:
diff --git a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InjectLambdaTest.kt b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InjectLambdaTest.kt
new file mode 100644
index 00000000..d936e436
--- /dev/null
+++ b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/InjectLambdaTest.kt
@@ -0,0 +1,75 @@
+package me.tatarka.inject.test
+
+import assertk.assertThat
+import assertk.assertions.containsOnly
+import assertk.assertions.extracting
+import assertk.assertions.isEqualTo
+import me.tatarka.inject.annotations.Component
+import me.tatarka.inject.annotations.IntoMap
+import me.tatarka.inject.annotations.IntoSet
+import me.tatarka.inject.annotations.Provides
+import kotlin.test.Test
+
+@Component
+abstract class InjectLambdaComponent {
+    abstract val lambda: () -> String
+
+    @Provides
+    fun provideLambda(): () -> String = { "foo" }
+}
+
+@Component
+abstract class InjectLambdaSetComponent {
+    abstract val lambdaSet: Set<() -> String>
+
+    @Provides
+    @IntoSet
+    fun lambda1(): () -> String = { "one" }
+
+    @Provides
+    @IntoSet
+    fun lambda2(): () -> String = { "two" }
+}
+
+@Component
+abstract class InjectLambdaMapComponent {
+    abstract val mapSet: Map<String, () -> String>
+
+    @Provides
+    @IntoMap
+    fun lambda1(): Pair<String, () -> String> = "1" to { "one" }
+
+    @Provides
+    @IntoMap
+    fun lambda2(): Pair<String, () -> String> = "2" to { "two" }
+}
+
+class InjectLambdaTest {
+
+    @Test
+    fun generates_a_component_that_provides_a_lambda() {
+        val component = InjectLambdaComponent::class.create()
+
+        assertThat(component.lambda.invoke()).isEqualTo("foo")
+    }
+
+    @Test
+    fun generates_a_component_that_provides_a_set_of_lambdas() {
+        val component = InjectLambdaSetComponent::class.create()
+
+        assertThat(component.lambdaSet)
+            .extracting { it.invoke() }
+            .containsOnly("one", "two")
+    }
+
+    @Test
+    fun generates_a_component_that_provides_a_map_of_lambdas() {
+        val component = InjectLambdaMapComponent::class.create()
+
+        assertThat(component.mapSet.mapValues { (_, value) -> value() })
+            .containsOnly(
+                "1" to "one",
+                "2" to "two"
+            )
+    }
+}
\ No newline at end of file