Skip to content

Support definition annotations on objects via INSTANCE - #78

Open
exoego wants to merge 1 commit into
InsertKoinIO:mainfrom
exoego:fix/issue-77-object-definition
Open

Support definition annotations on objects via INSTANCE#78
exoego wants to merge 1 commit into
InsertKoinIO:mainfrom
exoego:fix/issue-77-object-definition

Conversation

@exoego

@exoego exoego commented Jul 29, 2026

Copy link
Copy Markdown

Closes #77

A definition annotation (e.g. @Single) on a Kotlin object made the plugin emit a constructor call for the object. An object has no public constructor, so this produced invalid IR. With compiler assertions on, it failed lowering with an AssertionError. With assertions off, it compiled and then crashed at runtime with NoSuchMethodError, while Gradle still reported BUILD SUCCESSFUL.

The definition body now references the singleton INSTANCE with irGetObject when the target is an object. This mirrors the existing object handling in ModuleFunctionResolver.buildModuleGetCall. Both the annotation path (DefinitionCallBuilder.createDefinitionLambda, used by the root and scoped builders) and the DSL path (KoinDSLTransformer.handleTypeParameterCall for single() and friends) are covered. Interface binding on an object still emits the .bind() chain around the INSTANCE reference.

Tests

  • box/annotations/object_binding.kt mirrors the real use case from the issue. An object is bound to a framework interface with @Single([Loader::class]), extends a shared abstract base, and is collected through the interface with getAll. It fails on the unfixed plugin with an IR validation error and passes after the fix.
  • box/annotations/object_singleton.kt is the minimal @Single object case.
  • box/dsl/single_object.kt covers the same case for single().
  • playground-apps/app-kmp-klib adds an interface-bound @Single object so the change is compiled on the KLIB targets iosArm64 and wasmJs.

Docs updated to note that objects are supported and resolve to INSTANCE.

…NSTANCE

A definition annotation on a Kotlin object (@single, @factory, @Scoped,
@KoinViewModel, @KoinWorker) made the plugin emit a constructor call for the
object. An object has no public constructor, so this produced invalid IR. With
compiler assertions on it failed lowering with an AssertionError. With assertions
off it compiled and then crashed at runtime with NoSuchMethodError, while Gradle
still reported BUILD SUCCESSFUL.

The definition body now references the singleton INSTANCE with irGetObject when
the target is an object. This mirrors the existing object handling in
ModuleFunctionResolver.buildModuleGetCall. Both the annotation path
(DefinitionCallBuilder.createDefinitionLambda, used by the root and scoped
builders) and the DSL path (KoinDSLTransformer.handleTypeParameterCall for
single<T>() and friends) are covered. Interface binding on an object still emits
the .bind() chain around the INSTANCE reference.

Tests
- box/annotations/object_binding.kt mirrors the real use case from the issue. An
  object is bound to a framework interface with @single([Loader::class]), extends a
  shared abstract base, and is collected through the interface with getAll. It
  fails on the unfixed plugin with an IR validation error and passes after the fix.
- box/annotations/object_singleton.kt is the minimal @single object case.
- box/dsl/single_object.kt covers the same case for single<T>().
- playground-apps/app-kmp-klib adds an interface-bound @single object so the change
  is compiled on the KLIB targets iosArm64 and wasmJs.

Docs updated to note that objects are supported and resolve to INSTANCE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@Single on an object generates a constructor call instead of INSTANCE

1 participant