diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt index 486b00a33aa..bcd8e06dc8f 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt @@ -33,7 +33,7 @@ enum class ComponentStorageState { Disposed } -internal class InvalidCardinalityException(message: String, val descriptors: Collection) : Exception(message) +internal class InvalidCardinalityException(message: String) : Exception(message) class ComponentStorage(private val myId: String, parent: ComponentStorage?) : ValueResolver { var state = ComponentStorageState.Initial @@ -46,6 +46,9 @@ class ComponentStorage(private val myId: String, parent: ComponentStorage?) : Va private val dependencies = MultiMap.createLinkedSet() override fun resolve(request: Type, context: ValueResolveContext): ValueDescriptor? { + fun ComponentDescriptor.isDefaultComponent(): Boolean = + this is DefaultInstanceComponentDescriptor || this is DefaultSingletonTypeComponentDescriptor + if (state == ComponentStorageState.Initial) throw ContainerConsistencyException("Container was not composed before resolving") @@ -53,9 +56,16 @@ class ComponentStorage(private val myId: String, parent: ComponentStorage?) : Va if (entry.isNotEmpty()) { registerDependency(request, context) - if (entry.size > 1) - throw InvalidCardinalityException("Request $request cannot be satisfied because there is more than one type registered", entry) - return entry.singleOrNull() + if (entry.size == 1) return entry.single() + + val nonDefault = entry.filterNot { it.isDefaultComponent() } + if (nonDefault.isEmpty()) return entry.first() + + return nonDefault.singleOrNull() + ?: throw InvalidCardinalityException( + "Request $request cannot be satisfied because there is more than one type registered\n" + + "Clashed registrations: ${entry.joinToString()}" + ) } return null } diff --git a/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt b/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt index cab9fde5e5e..a919b517603 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt @@ -18,8 +18,16 @@ package org.jetbrains.kotlin.container import kotlin.reflect.KClass -/*Use to assist injection to provide a default implementation for a certain component and reduce boilerplate in injector code. -* Argument class must be a non-abstract component class or a kotlin object implementing target interface. -* Avoid using when there is no clear 'default' behaviour for a component. -* */ +/** + * Use to assist injection to provide a default implementation for a certain component and reduce boilerplate in injector code. + * Argument class must be a non-abstract component class or a kotlin object implementing target interface. + * Avoid using when there is no clear 'default' behaviour for a component. + * + * NB: DefaultImplementation are *discriminated* during resolution of components, meaning that: + * - if there is exactly one non-default implementation and zero or several default, non-default will be chosen. + * - if there is none non-default implementations, default will be chosen + * + * Such configurations may arise, for example, for multiplatform modules: consider analyzing JVM+JS module, where JS contributes + * default implementation of some particular service, and JVM contributes non-default. + **/ annotation class DefaultImplementation(val impl: KClass<*>) \ No newline at end of file