Repository: evant/kotlin-inject (kotlin)

Issue:
Providing a function instead of property seems to not always work
Hello there 👋
Thanks for the fantastic library - made it really easy to convert an android app using dagger to a multi-platform app using kotlin-inject. I couldn't wish for anything more.

## Background
We need to have our component expose a type `Foo` _that is also_ used inside the component by another type `Bar`. This did work when the provisioning was declared as a property on the component, but seems to not work when instead using a function. It looks like the generated code still tries to call the function as a property. At first I thought we are having caching issues here.

## Minimal example
I created a minimal example repo here: https://github.com/degill/kotlin-inject-fun-bug-example

But it boils down to these lines:

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val appComponent = AppComponent::class.create()
        appComponent.provideBar()
    }
}

@Component
abstract class AppComponent {
    @Provides
    fun FooImpl.bind(): Foo = this
//    abstract fun provideFoo(): Foo // Will not compile with error "Function invocation 'provideFoo()' expected"
    abstract val provideFoo: Foo // Will compile fine
    abstract fun provideBar(): Bar
}

interface Foo
@Inject
class FooImpl : Foo

@Inject
class Bar(val foo: Foo)
```

## Actual behaviour:
The code that gets generated tries to call `provideFoo` as a property:
```kotlin
public class InjectAppComponent() : AppComponent() {
  override fun provideFoo(): Foo = FooImpl().bind()

  override fun provideBar(): Bar = Bar(
    foo = provideFoo // With error "Function invocation 'provideFoo()' expected"
  )
}
```

## Expected behaviour
In the `README.md` it says using either way should work: 
> In your component you can declare abstract read-only properties or functions to return an instance of a given type.

But maybe I overlooked something.

Pull request:
Fixes #321

Patch:
diff --git a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultOptimizer.kt b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultOptimizer.kt
index 96888267..7e05b3df 100644
--- a/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultOptimizer.kt
+++ b/kotlin-inject-compiler/core/src/main/kotlin/me/tatarka/inject/compiler/TypeResultOptimizer.kt
@@ -31,7 +31,7 @@ fun List<TypeResult.Provider>.optimize(context: Context): List<TypeResult.Provid
         val newResult = TypeResult.Provides(
             className = context.className,
             methodName = name,
-            isProperty = true,
+            isProperty = provider?.isProperty ?: true,
         )
         if (provider == null) {
             provider = TypeResult.Provider(


Test patch:
diff --git a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/ProvidesTest.kt b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/ProvidesTest.kt
index dcbdf2b0..3b8f1a10 100644
--- a/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/ProvidesTest.kt
+++ b/integration-tests/common/src/test/kotlin/me/tatarka/inject/test/ProvidesTest.kt
@@ -209,6 +209,22 @@ abstract class ProvidesInnerClassComponent {
     abstract val fooFactory: DifferentPackageFoo.Factory
 }
 
+// https://github.com/evant/kotlin-inject/issues/321
+@Component
+abstract class OptimizesProvides {
+    @Provides
+    fun OFooImpl.bind(): OFoo = this
+    abstract fun provideFoo(): OFoo
+    abstract fun provideBar(): OBar
+}
+
+interface OFoo
+@Inject
+class OFooImpl : OFoo
+
+@Inject
+class OBar(val foo: OFoo)
+
 class ProvidesTest {
 
     @Test
@@ -307,4 +323,12 @@ class ProvidesTest {
 
         assertThat(component.fooFactory).isNotNull()
     }
+
+    @Test
+    fun generates_a_component_where_a_provides_fun_optimizes_to_references_another_provides_fun() {
+        val component = OptimizesProvides::class.create()
+
+        assertThat(component.provideFoo()).isNotNull()
+        assertThat(component.provideBar()).isNotNull()
+    }
 }
\ No newline at end of file