KOIN-D001 false positive on DSL definitions that don't use the bound type's constructor (providerOnly ignored in full-graph validation)

Open
#83 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
kotlin
Domain
compilers

Research direction

Start in CompileSafetyValidator.validateFullGraph and compare its allDefinitions handling with the providerOnly filter in CallSiteValidator's Phase 3.1 path. Add regression coverage for lambda and zero-argument create(::makeProbe) definitions; done means both compile without KOIN-D001 while the bound type remains a provider.

Written by the indexing model from the issue text.

Description

Summary

Since 1.1.0 made full-graph validation the sole verifier, a DSL definition whose instance is not built by the bound type's own constructor still has that constructor's parameters treated as dependencies.

The plugin already models this correctly — KoinDSLTransformer sets providerOnly = true on these definitions, and the Phase 3.1 DSL-graph path in CallSiteValidator filters them out with reachableDefs.filter { !(it is Definition.DslDef && it.providerOnly) } (~line 509). But CompileSafetyValidator.validateFullGraph does not apply that filter, so every constructor parameter of the bound type is reported as a missing dependency.

Because 1.1.0 removed per-module validation, the A3 path is now the only one that runs whenever a compilation has an entry point, and Phase 3.1 is skipped once A3 has populated assembledGraphTypes. So the code path that respects the flag appears to be unreachable for any compilation that has an entry point.

Environment
  • Koin: 4.2.2
  • Koin Compiler Plugin: 1.1.0
  • Kotlin: 2.4.0 (a listed supported version)
  • Gradle 9.6.1, JDK 21, Kotlin Multiplatform project, failure observed on the JVM target
  • koinCompiler { } otherwise at defaults (compileSafety default true)
Reproduction

No external dependencies needed. In any compilation that contains a Koin entry point (startKoin / koinApplication / @KoinApplication) — in our case a koinApplication<TestApp> { } in a test source set — declare:

class Probe(val a: String, val b: Int)

private fun makeProbe(): Probe = Probe("x", 1)

private val probeModule = module {
    single { Probe("x", 1) }          // lambda builds it
    single { create(::makeProbe) }    // zero-arg factory function
}
Actual
e: [Koin][KOIN-D001] Missing dependency: kotlin.String
    required by: dsl:Probe (parameter 'a')
    in module: probeModule
e: [Koin][KOIN-D001] Missing dependency: kotlin.Int
    required by: dsl:Probe (parameter 'b')
    in module: probeModule
Expected

No diagnostic. Neither definition resolves a or b from the container — the first constructs Probe inside its own lambda, and the second delegates to a factory function that takes no parameters. Koin never invokes Probe's constructor itself in either case, and both resolve correctly at runtime.

create(::fn) does not work around it

This is the difference from #36. That issue was about definitions not being recognised as providers (single<Vertx> { vertx } reporting Vertx itself missing), and create(::provideVertx) was the accepted answer — it's also what the docs recommend for building an instance from something other than its own constructor.

Here the definition is recognised as a provider (the bound type itself is never reported missing); it is additionally treated as a consumer of its own constructor. create(::makeProbe) with a zero-arg function produces the identical error, so requirements appear to be read from the bound type rather than from the factory function's parameter list. Verified on 1.1.0 with both forms.

Real-world impact

Our case is a test fixture overriding a builder with an in-memory one:

private val inMemoryDatabaseModule = module {
    single<RoomDatabase.Builder<LedgerDatabase>> { Room.inMemoryDatabaseBuilder<LedgerDatabase>() }
}

Missing dependency: kotlin.reflect.KClass … (parameter 'klass') and kotlin.Function0 … (parameter 'factory')RoomDatabase.Builder's own constructor parameters, which no application should ever provide. The tests pass at runtime; only the compile fails.

Any framework or third-party type with a public constructor that you bind via a hand-written lambda hits this: builders, ObjectMapper, Vertx, an SDK client, a test double. Interfaces are unaffected (no constructor to walk), which is why it can look sporadic.

The only available escape is koinCompiler { compileSafety = false }, and since that setting is per-Gradle-project it also disables validation for the module's main source set — the same complaint raised in #58. That is a heavy price for a test-only fixture.

Possible fix

Apply the existing providerOnly filter in CompileSafetyValidator.validateFullGraph (or when building allDefinitions) so it matches the Phase 3.1 path. A providerOnly definition should still be registered as a provider of its bound type — it just shouldn't contribute requirements.

Related
  • #36 — same family (DSL definitions not built by their own constructor), closed in 1.0.2; the create(::fn) remedy accepted there does not cover this case.
  • #58 — different root cause, but the same end-user situation: false KOIN-D001 in a test source set, with compileSafety = false being too blunt a workaround.
  • #74 — unrelated (@InjectedParam / parametersOf).

Thanks for 1.1.0 — the entry-point-only model fixed a real cross-module false positive for us, and the improved KOIN-D001 messages (owning module + file:line) are what made this one diagnosable.

Dominant language
Kotlin
Stars
82
Forks
15
Avg merge
1m
Merged PRs (30d)
1

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from InsertKoinIO/koin-compiler-plugin

All issues in InsertKoinIO/koin-compiler-plugin

Similar issues

More Kotlin issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.