From d80eba31be97e71be8f91b66bc617850a5cbafd1 Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Mon, 13 May 2019 19:05:22 +0300 Subject: [PATCH] [Injection] Discriminate default instances during clashes resolution The idea is to try to resolve the dependency without considering default instances first. This makes containers more composable, e.g., it is now possible to put several containers together as long as all except one provide default implemenetation for some particular service (non-default implementation will be automatically chosen, and all defaults will be discarded). --- .../org/jetbrains/kotlin/container/Storage.kt | 18 ++++++++++++++---- .../kotlin/container/DefaultImplementation.kt | 16 ++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) 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