evant / kotlin-inject

Dependency injection lib for kotlin
Apache License 2.0
1.14k stars 51 forks source link

Kotlin Delegation Support? #366

Open jamesalee213 opened 3 months ago

jamesalee213 commented 3 months ago

Hi. does kotlin-inject not support kotlin delegation? If it doesn't, will the support for kotlin delegation for creating component ever be added?

I'm trying to use component inheritance (link) with kotlin delegation. Here's my setup:

@CustomScope
interface FooComponent {
  @CustomScope
  @Provides
  fun foo(): Foo
}

// the delegate gets created somewhere else in the code and is an instance of `FooComponent`, but it not kotlin-inject component.
@Component
abstract RealFooComponent(delegate: FooComponent) : FooComponent by delegate

but when I try to build, I get this error:

e: [ksp] @Provides method must have a concrete implementation
e: Error occurred in KSP, check log for detail
evant commented 3 months ago

interesting, seems like ksp doesn't emit the synthentic methods generated for delegation, I wonder if there's another way to detect it.

dellisd commented 3 months ago

Unfortunately it looks like KSP won't report on delegation (because it's technically an expression), but working with James we came up with this heuristic which generally works:

// Defined in AstMember:
override fun isDelegated(enclosingClass: AstClass): Boolean {
    return declaration.parentDeclaration != (enclosingClass as KSAstClass).declaration
}

// TypeCollector:
if (isComponent && abstract && !method.isDelegated(astClass)) {
    provider.error("@Provides method must have a concrete implementation", method)
    continue
}

If you manually override the interface method in your component, the parent declaration will be the component (enclosing) class. (i.e. the declarations match and it is definitely not delegated).

If the method is delegated, the parent declaration will be the interface instead of the component class. (i.e. the declarations are not the same, and it is probably not delegated).

If you don't delegate the method and don't implement the method in your component class, then this will pass kotlin-inject because it will think that it is "delegated" however you get a compiler error after the fact because there the method was never implemented.

I don't know if it's worth the trade-off of introducing a more cryptic error for something that kotlin-inject tries to explicitly check for... but that's our only idea so far 🤷

dellisd commented 3 months ago

I think I'm mistaken about interface delegation being an "expression". That would apply to checking if a simple property is delegated, but checking if an abstract property or function is implemented via delegation is definitely compile-time information.

Either way, KSP doesn't support this at the moment, but maybe it's something that could be requested.

evant commented 3 months ago

Thanks for looking into it! Yeah I think opening an issue on the ksp side to get this information would be the way to go.