Giter Site home page Giter Site logo

kotlin-inject's Issues

Look at build performance

Not much though has been put into build performance, so there's def some low-hanging fruit here. Should profile and improve hot paths. Probably mostly unnecessary lookups/lack of caching.

Incorrect generated code for accessing an extension provides on a parent component

interface Foo

@Inject
class FooImpl : Foo


@Component
abstract class ParentComponent {
    @Provides
    val FooImpl.bind: Foo
        get() = this
}

@Component
abstract class ChildComponent(@Component val parentComponent: ParentComponent) {
    abstract val foo: Foo
}

expected:

class InjectChildComponent(
  parentComponent: ParentComponent
) : ChildComponent(parentComponent) {
  override val foo: Foo
    get() = with(parentComponent) { FooImpl().bind }}

actual:

class InjectChildComponent(
  parentComponent: ParentComponent
) : ChildComponent(parentComponent) {
  override val foo: Foo
    get() = parentComponent.FooImpl().bind}

Support injecting objects

@Inject object Foo

can be useful if you want to be able to change it from a singleton to an instance later.

Support annotating constructor with @Inject

Annotating the class selects the primary constructor which is usually what you want, but sometimes you'd want to annotate the constructor explicitly. This is useful if:

  1. You have more than one constructor
  2. You are migrating dagger code

Invalid code generated if parent component is missing val

If you have

@Component abstract class MyComponent(@Component parent: ParentComponent)

then generated code will fail to access parent as it's not a val. Quick fix: this should throw an error. May investigate supporting not having val here.

Scope defined in seperate module doesn't work.

If you define a scoped inject in module A:

@MyScope @Inject class Foo

and a scoped component in module B:

@MyScope @Component abstract class MyComponent {
   abstract val foo: Foo
}

then the generated component will not include the scoped dep

class InjectMyComponent : MyComponent {
   override val foo: Foo get() = _foo // _foo not generated.
}

this is because only the current module is scanned for @MyScope dependencies, not other dependent modules.

The above can be solved partially by noticing we need a Foo when generating MyComponent and emitting the generated field. However, we don't always know that the field is needed. For example say you have a component

@Component abstract class MyChildComponet(@Component val parent: MyComponent) {
   abstract val foo: Foo
}

in module C. It will expect to reference the field in MyComponent but it may not be generated if MyComponent never uses it itself.

Some possible solutions:

  1. Do what dagger does and force you to declare scoped dependencies in your component (i.e. always require you to declare abstract val foo: Foo). This guarantees MyComponent always knows about Foo when it's generated but increases boilerplate.

  2. Figure out how to scan the classpath for @MyScope annotated dependencies. Not sure how possible this is in the kapt/ksp backends and what the compiler costs are for doing it.

  3. Use a dynamic map to hold scoped dependencies instead of fields.

class InjectMyComponent : MyComponent {
   _scoped = mutableMapOf<String, Any?>()
}

class InjectMyChildComponent(val parent: MyComponent) : MyChildComponent(parent) {
    override val foo: Foo get() = synchronized(parent._scoped) { parent._scoped.getOrPut("Foo") { Foo() } as Foo }
}

this means MyComponent doesn't ever have to know about it's scoped dependencies. The downside is the generated code is less clear to read and there may be some performance costs (unclear).

Replacing dependencies for tests

Need to figure out a strategy for this that feels good. You can sorta get there with component interfaces but it can get clunky splitting out deps to replace a specific thing sometimes.

Function Reference injection doesn't work unless provided type is specified explicitly

If I want to inject a function reference into a class, the build fails unless I explicitly specify the provided type for the function reference in the component. Without the return type, it defaults to the behaviour of trying to generate a provider and in the following example complains that Any can't be provided.

i.e.
this does not work:

@Inject
class MyClass(private val myProvider: suspend () -> Any)

suspend fun theProvider(): Any { /* ... */ }

@Component
abstract class AppComponent {
  @Provides
  protected fun provideTheProvider() = ::theProvider
}

but this does:

@Inject
class MyClass(private val myProvider: suspend () -> Any)

suspend fun theProvider(): Any { /* ... */ }

@Component
abstract class AppComponent {
  @Provides
  protected fun provideTheProvider(): suspend () -> Any = ::theProvider
}

I'm not sure if this is intended behaviour or not, but if it is then it might be worth documenting.

Incorrectly allowing the same scope on a child and parent component.

It should be an error to declare the same scope on a child and parent component as it's not clear which one should provide the dependency. Apparently the following does not produce such an error and instead leads to surprising results.

@Scope
annotation class ScreenScope

@ScreenScope
@Inject
class Foo

class ParentScreen {
  private val component = ParentScreenComponent::class.create()

  private val childScreen by lazy {
    component.childScreen
  }
}

@ScreenScope
@Component
abstract class ParentScreenComponent {
  abstract val childScreen: ChildScreen

  @ScreenScope
  @Provides
  fun parentComponent() = this
}

@Inject
class Bar(foo: Foo)

@Inject
class ChildScreen(
  private val parentComponent
) {
  private val childComponent = ChildScreenComponent::class.create(parentComponent)
  private val bar: Bar by lazy {
    childComponent.bar
  }
}

@ScreenScope
@Component
abstract class ChildScreenComponent(
  @Component val parentComponent: ParentScreenComponent
) {
  abstract val bar: Bar
}

Support typealias for @IntoMap

Normally typealiases are treated as separate types, but here it should look through them to see the Pair as it's useful to re-use across provides.

typealias MyEntry = Pair<MyKey, MyValue>

@Component
abstract class MyComponent {
   @Provides @IntoMap
   fun entry1(): MyEntry = ...
   @Provides @IntoMap
   fun entry2(): MyEntry = ...
}

Leak when child dependency is provided to a parent dependency that has a longer lifecycle

If a child dependency is provided to a parent dependency, and that parent has a longer lifecycle than the child (e.g. the parent is scoped to an Android application, and the child is scoped to an Android activity), then the child dependency can leak (in the example below, the activity context is leaked):

@Inject
class ResolverWrapper(val resolver: ContentResolver)

@Singleton
@Component abstract class ParentComponent {
  @Provides @Singleton fun providesContentResolver(
    context: Context
  ): ContentResolver = context.contentResolver
}

@Component abstract class ChildComponent(
  @Component val parent: ParentComponent,
  @get:Provides val activityContext: Context
) {
  abstract val wrapper: ResolverWrapper
}

Implement abstract functions in components

Can kotlin-inject implement abstract functions in components, just the way how abstract values are implemented? I wanna expose some factory lambdas from my component but using them as vals is a bit inconvenient from usage sites because kotlin doesn't allow named arguments for lambdas.

Here's what I'm currently doing:

@Component
abstract class SomeComponent) {
  protected abstract val presenterFactory: (navigator: Navigator) -> SomePresenter

  fun presenter(navigator: Navigator): SomePresenter {
    return presenterFactory(navigator)
  }
}

can this be reduced to:

@Component
abstract class SomeComponent) {
  abstract fun presenter(navigator: Navigator): SomePresenter
}

Injected nullable function argument loses nullability

Expected:

@Inject class Foo(bar: Bar?)

@Component abstract class MyComponent { abstract val foo: (Bar?) -> Foo }

to compile but fails because it generates:

class InjectMyComponent { override val foo: Function0<Bar> get() = ... }

graph validation

Need to validate the graph (ex: detect cycles) to give better information on failure cases.

Allow annotating interfaces with @Component

while they won't be able to take arguments, there's nothing otherwise stopping you from using an interface instead of an abstract class for your @Component impl.

@Component
interface MyComponent {
   val foo: Foo
}

Properly handle nullable/platform types.

Kotlin has 3 versions of a type: non-nullable, nullable, and platform, represented as:
Foo, Foo?, and Foo! respectively. We need to handle these correctly and consistently across backends. The current behavior is:

  • kapt: all 3 types match with each other. This 'works' sometimes but generates incorrect code when it matches Foo? to Foo.
  • ksp: all 3 types are distinct. This works, but can be surprising in the case of platform times where @Provides fun foo() = javaMethodThatReturnsFoo() won't match Foo.

To say another way, which ones of these should compile, and which ones should complain about a missing binding?

@Component MyComponent {
  abstract val foo: Foo
  @Provides fun foo() = javaMethodThatReturnsFoo()
}
@Component MyComponent {
  abstract val foo: Foo?
  @Provides fun foo() = javaMethodThatReturnsFoo()
}
@Component MyComponent {
  abstract val foo: Foo?
  @Provides fun foo(): Foo = javaMethodThatReturnsFoo()
}

Incorrect code generated when scoped compoent has a non-scoped parent component

@CustomScope @Inject class Foo

@Component abstract class ParentComponent

@CustomScope @Component abstract class ChildComponent(@Component val parent: ParentComponent) {
    abstract val foo: Foo
}

expected:

class InjectChildComponent(parent: ParentComponent): ChildComponent(parent) {
    val _foo by lazy { Foo() }
}

actual:

class InjectChildComponent(parent: ParentComponent): ChildComponent(parent) {
    val _foo: Foo by lazy { (parent as InjectFoo)._foo }
}

error: @Component class: AppComponent must be abstract

build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.5.0"
    kotlin("kapt") version "1.5.0"
}

repositories {
    mavenCentral()
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

dependencies {
    kapt("me.tatarka.inject:kotlin-inject-compiler-kapt:0.3.3")
    implementation("me.tatarka.inject:kotlin-inject-runtime:0.3.3")
}

Code:

import me.tatarka.inject.annotations.Component

@Component
abstract class AppComponent

fun main() {

}

Full error:

> Task :kaptKotlin FAILED
D:\.projects\test\build\tmp\kapt3\stubs\main\AppComponent.java:6: error: @Component class: AppComponent must be abstract
public abstract class AppComponent {
                ^
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':kaptKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
   > java.lang.reflect.InvocationTargetException (no error message)

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':kaptKotlin'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:187)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:268)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:185)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:408)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:395)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:388)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:374)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: org.gradle.workers.internal.DefaultWorkerExecutor$WorkExecutionException: A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
        at org.gradle.workers.internal.DefaultWorkerExecutor$WorkItemExecution.waitForCompletion(DefaultWorkerExecutor.java:342)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:142)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:94)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForAll(DefaultAsyncWorkTracker.java:80)
        at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForCompletion(DefaultAsyncWorkTracker.java:68)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:506)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:483)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:466)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$300(ExecuteActionsTaskExecuter.java:105)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:270)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:248)
        at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:83)
        at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:37)
        at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
        at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:47)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:47)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:37)
        at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
        at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:50)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
        at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
        at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
        at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
        at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:54)
        at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:35)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:60)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
        at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:174)
        at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:74)
        at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:45)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:40)
        at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:29)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:99)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:92)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:52)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:36)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:84)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:41)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:91)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:49)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:78)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:49)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:105)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:50)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:86)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:86)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:32)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:43)
        at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:31)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution$2.withWorkspace(ExecuteActionsTaskExecuter.java:283)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:49)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)
        at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:184)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:408)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:395)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:388)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:374)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at org.jetbrains.kotlin.gradle.internal.KaptExecution.run(KaptWithoutKotlincTask.kt:227)
        at org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction.execute(KaptWithoutKotlincTask.kt:193)
        at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
        at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:97)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
        at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
        at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
        at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
        at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
        at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$2(DefaultWorkerExecutor.java:206)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:214)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:131)
        ... 3 more
Caused by: org.jetbrains.kotlin.kapt3.base.util.KaptBaseError: Error while annotation processing
        at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:121)
        at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:31)
        at org.jetbrains.kotlin.kapt3.base.Kapt.kapt(Kapt.kt:45)
        ... 31 more


* Get more help at https://help.gradle.org

BUILD FAILED in 952ms
2 actionable tasks: 1 executed, 1 up-to-date

What am I doing wrong?

Conflicting declarations for scoped `@Provides` functions with parameterized return type

If I have:

import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Scope

@Scope annotation class MyScope

@MyScope @Component abstract class MyComponent {
  abstract val foo: Foo
 
  @MyScope @Provides protected fun t1() = T<Int>()
 
  @MyScope @Provides protected fun t2() = T<String>()
}

class T<T>

@MyScope @Inject class Foo(
  private val t1: T<Int>,
  private val t2: T<String>,
  private val bar: Bar
)

@MyScope @Inject class Bar(
  private val t1: T<Int>,
  private val t2: T<String>
)

then the generated properties for both are named _t.

Parameters with default values

class Foo(
  val generator: () -> String = { "bar" }
)

For constructors (or functions) with parameters that have default values, can kotlin-inject ignore injecting them? I have a lambda parameter with a default value that I'm only overriding in tests so I don't wanna add a provider for it to my component.

Generates incorrect impl when 2 inner classes have the same name

Had:

class FirstTest {
  @Component abstract class TestComponent
}

class SecondTest {
  @Component abstract class TestComponent
}

it correctly generated the files:

InjectFirstTest_TestComponent
InjectSecondTest_TestComponent

but the impls where both for FirstTest.TestComponent

note: ran into the issue when classes where at the top-level (not within a package), haven't investigate if it still happens otherwise.

can workaround it by renaming one of the components.

Support injecting companion object

This generates incorrect code

interface IFoo {
  @Inject
  companion object : IFoo
}

@Component
abstract class MyComponent {
   abstract val foo: IFoo
   val IFoo.Companion.bind: IFoo @Provides get() = this
}

expected:

class InjectMyComponent : MyComponent {
  override val foo: IFoo get() = IFoo.bind
}

actual:

class InjectMyComponent : MyComponent {
  override val foo: IFoo get() = IFoo.Companion().bind
}

Support suspend

These should all work

@Component abstract class MyComponent {
   abstract val foo: suspend () -> Foo

   @Provides fun provideFoo(): suspend () -> Foo = { Foo() }

   abstract suspend fun bar(): Bar

   @Provides suspend fun providesSuspendBar(): Bar = Bar()
}

@Inject class Baz(foo: suspend () -> Foo, bar: suspend () -> Bar)

Support Set<() -> Foo> & Set<Lazy<Foo>>

Extend @IntoSet to work with lambda & lazy like you can with single values.

If you have

@Component
abstract class MyComponent {
  abstract fun provideFooSet() : Set<() -> Foo>
  abstract fun lazyFooSet() : Set<Lazy<Foo>>

  @Provides @IntoSet fun foo1() : Foo = ...
  @Provides @IntoSet fun foo2(): Foo = ...
}

It should generate

class InjectMyComponent : MyComponent {
  override fun provideFooSet() : Set<() -> Foo> = setOf({ foo1() }, { foo2() })
  override fun lazyFooSet(): Set<Lazy<Foo>> = setOf(lazy { foo1() }, lazy { foo2() })
}

Allow mutlithreading on kotlin native

Right now components can only be accessed on the main thread due to the LazyMap implementation. Need to figure out how to allow multithreading like what's available on the jvm. The solution should also work with kotlin's new memory model.

Namespace generated inner classes

If you have two components with the same name with different parent classes, they generate conflicting classes.

class Foo1 {
  @Component abstract class MyComponent()
}
class Foo2 {
  @Component abstract class MyComponent()
}

both generates InjectMyComponent() which causes a compile error.

Runtime annotation retention causes warning in JS targets

All of the annotations in the runtime library are marked with @Retention(RUNTIME). It's not immediately obvious to me what purpose this serves since my understanding is that they're only used at compile time(?), but either way; using these annotations in a JS sourceset produces the following warning:

Reflection is not supported in JavaScript target, therefore you won't be able to read this annotation in run-time

Ideally it would produce no warnings.

Happy to open a PR to change the retention annotations, but just wanted to open a discussion first.

Add default values for component constructor arguments which are components with no constructor arguments

Example Code:

@Component abstract class NoArgComponent {
@get:Provides val string: String = "Hi"
}

@Component abstract class MyComponent(@Component component: NoArgComponent)

Generates:

InjectMyComponent(component: NoArgComponent = InjectNoArgComponent()) : MyComponent(component)

This feature provides functionality similar to how dagger can build modules which don't have any constructor params.

Proposal: make assisted arguments explicit

kotlin-inject's way of injecting dependencies with assisted arguments is really clever but I've been thinking it's very implicit and hidden for readers. Thoughts on making it explicit in some manner? Using dagger-like @Assisted annotation is one option, but I'm not sure if you'd want that. It'll also remove the restriction of keeping assisted arguments at the end of a constructor, which is another hidden restriction.

Generate getters for common object construction

Right now the entire constructor/provides call is generated every place a dependency is used. This leads to a bit of code bloat. Should be able to pull up the construction into a private getter if it's used more than once in a component.

ex:

class InjectMyComponent : MyComponent() {
  private val _foo: Foo
    get() = Foo(...)

  override val bar1: Bar1
    get() = Bar1(_foo)

  override val bar2: Bar2
    get() = Bar2(_foo)

Support receivers on inject functions

Currently the following generates incorrect code:

typealias foo = String.() -> String
@Inject
fun String.foo(bar: Bar): String = ...

@Component
abstract class MyComponent {
  abstract val foo: foo
}

Allow recusive dependencies with lazy/functions

Right now this stackoverflows but it should be able to be resolved:

class LazyCycleFoo(val bar: LazyCycleBar)
class LazyCycleBar(val foo: Lazy<LazyCycleFoo>)

@Component abstract class LazyCycleComponent {
    abstract val bar: LazyCycleBar

    @Provides fun bar(foo: Lazy<LazyCycleFoo>) = LazyCycleBar(foo)

    @Provides fun foo(bar: LazyCycleBar) = LazyCycleFoo(bar)
}

should generate:

class InjectLazyCycleComponent : LazyCycleComponent() {
    override val bar: LazyCycleBar
        get() {
            lateinit var bar: LazyCycleBar
            return bar(lazy { foo(bar) }).also { bar = it }
        }
}

Still Alive?

Hi, thanks for this neat injection framework. Is it still under active development?

Print full name of nested types in error messages

When kotlin-inject is unable to find a provider for a type that's nested inside another type, it only prints its simple name. For example, Factory instead of SomeClass.Factory. This makes it slightly difficult to immediately recognize the type's name. Can the name of nested types prefixed with their parent class(es)?

- e: [ksp] Cannot find an @Inject constructor or provider for: Factory
+ e: [ksp] Cannot find an @Inject constructor or provider for: SomeClass.Factory

Use type alias in generated override if available

for:

typealis App = @Composable () -> Unit

class MyComponent {
    val app: App
}

expected:

class InjectMyComponent {
    override val app: App
        get() = { ...
}

actual:

class InjectMyComponent {
    override val app: Function0<Unit>
        get() = { ...
}

this ensures any annotations (@Composable in this case) are applied to the implementation.

Remove java.lang.String import from generated components

This snippet

import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides

fun main() {
   TestComponent::class.create("Hello World").factory.print()
}

@Component
abstract class TestComponent(@get:Provides protected val string: String) {
   abstract val factory: HelloWorldFactory
}

@Inject class HelloWorldFactory(private val string: String) {

   fun print() = println(string)
}

Generates this:

import java.lang.String
import kotlin.reflect.KClass

class InjectTestComponent(
  string: String
) : TestComponent(string) {
  override val factory: HelloWorldFactory
    get() = HelloWorldFactory(string)}

fun KClass<TestComponent>.create(string: String): TestComponent = InjectTestComponent(string)

Which yields this error:

Type mismatch.
Required:java.lang.String
Found:kotlin.String

image

Unable to resolve viewbinding classes.

If you reference a generated viewbinding class in your component or @Inject constructor, it will cause an error. I suspect this is because ksp runs before the viewbinding code gen. You can work around it by wrapping the class so it is resolved:

@JvmInline
value class Binding(val value: MainActivityBinding)

@Component MainActivityComponent(@get:Provides val binding: Binding) { ... }

@Inject class MainScreen(binding: Binding) {
   private val binding = binding.value
   ...
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.