From 72e28d37fc4ef5cef81f38ee911477e6ba049f61 Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Wed, 13 Mar 2019 17:29:50 +0300 Subject: [PATCH] [Injection] Introduce PlatformSpecificExtensions and PlatformExtensionsClashResolver This commit introduces the ability to register a PlatformExtensionClashResolver in a container. Each PlatformExtensionClashResolver has a corresponding PlatformSpecificExtensions. If, during container composition, several instances of PlatformSpecificExtensions were registred, instead of throwing InvalidCardinalityException, corresponding PlatformExtensionClashResolver will be asked to resolve clash. This allows to make injection more composable and less coupled across different contributors of service, providing a basis for such motivating cases as composing containers with both JS and JVM services (for analysis of multiplatform modules). Previously, that would be impossible: a) JS would inject default instances for some services which would clash with non-default JVM services (like SyntheticScopes) b) Also, there are a very few services for which *both* platforms provide non-default implementations, so they should be merged manually on case-by-case basis (e.g., IdentifierChecker) --- .../jetbrains/kotlin/container/Container.kt | 5 ++ .../src/org/jetbrains/kotlin/container/Dsl.kt | 4 ++ .../jetbrains/kotlin/container/Registry.kt | 16 ++++++ .../jetbrains/kotlin/container/Singletons.kt | 24 +++++++++ .../org/jetbrains/kotlin/container/Storage.kt | 20 +++++-- .../jvm/platform/JvmPlatformConfigurator.kt | 9 +++- .../kotlin/resolve/IdentifierChecker.kt | 19 ++++++- .../resolve/PlatformConfiguratorBase.kt | 24 +++++++-- .../checkers/PlatformDiagnosticSuppressor.kt | 19 ++++++- .../resolve/deprecation/deprecationUtil.kt | 4 +- .../DoubleColonExpressionResolver.kt | 25 +++++---- .../resolve/calls/results/FlatSignature.kt | 4 +- .../java/components/SamConversionResolver.kt | 4 +- .../jetbrains/kotlin/types/dynamicTypes.kt | 4 +- .../kotlin/container/DefaultImplementation.kt | 2 + .../container/PlatformSpecificExtension.kt | 52 +++++++++++++++++++ 16 files changed, 209 insertions(+), 26 deletions(-) create mode 100644 core/util.runtime/src/org/jetbrains/kotlin/container/PlatformSpecificExtension.kt diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Container.kt b/compiler/container/src/org/jetbrains/kotlin/container/Container.kt index 7d6d0229cf4..12ea40228e7 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Container.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Container.kt @@ -102,6 +102,11 @@ class StorageComponentContainer( return this } + internal fun registerClashResolvers(resolvers: List>): StorageComponentContainer { + componentStorage.registerClashResolvers(resolvers) + return this + } + override fun create(request: Class): T { val constructorBinding = request.bindToConstructor(unknownContext) val args = constructorBinding.argumentDescriptors.map { it.getValue() }.toTypedArray() diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt b/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt index e600743e870..3f90482fdce 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt @@ -50,6 +50,10 @@ fun StorageComponentContainer.useInstanceIfNotNull(instance: Any?) { if (instance != null) registerInstance(instance) } +fun StorageComponentContainer.useClashResolver(clashResolver: PlatformExtensionsClashResolver<*>) { + registerClashResolvers(listOf(clashResolver)) +} + inline operator fun ComponentProvider.getValue(thisRef: Any?, desc: KProperty<*>): T { return getService(T::class.java) } diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt b/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt index 69e16ed1b16..1d8ffb26f8d 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt @@ -70,4 +70,20 @@ internal class ComponentRegistry { } registrationMap += other.registrationMap } + + fun resolveClashesIfAny(container: ComponentContainer, clashResolvers: List>) { + /* + The idea is to create descriptor, which is very similar to other SingletonDescriptor, but instead of calling + constructor we call 'resolveExtensionsClash' with values of clashed components as arguments. + + By mimicking the usual descriptors we get lazy evaluation and consistency checks for free. + */ + for (resolver in clashResolvers) { + val clashedComponents = registrationMap[resolver.applicableTo] as? Collection ?: continue + if (clashedComponents.size <= 1) continue + + val substituteDescriptor = ClashResolutionDescriptor(container, resolver, clashedComponents.toList()) + registrationMap[resolver.applicableTo] = substituteDescriptor + } + } } \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt index 59f87672a0c..775753e885b 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.container import java.io.Closeable +import java.lang.IllegalStateException import java.lang.reflect.Type import java.util.* @@ -144,6 +145,29 @@ open class SingletonTypeComponentDescriptor(container: ComponentContainer, val k override fun toString(): String = "Singleton: ${klass.simpleName}" } +internal class ClashResolutionDescriptor>( + container: ComponentContainer, + private val resolver: PlatformExtensionsClashResolver, + private val clashedComponents: List +) : SingletonDescriptor(container) { + + override fun createInstance(context: ValueResolveContext): Any { + state = ComponentState.Initializing + val extensions = computeArguments(clashedComponents) as List + val resolution = resolver.resolveExtensionsClash(extensions) + state = ComponentState.Initialized + return resolution + } + + override fun getRegistrations(): Iterable { + throw IllegalStateException("Shouldn't be called") + } + + override fun getDependencies(context: ValueResolveContext): Collection { + throw IllegalStateException("Shouldn't be called") + } +} + class ImplicitSingletonTypeComponentDescriptor(container: ComponentContainer, klass: Class<*>) : SingletonTypeComponentDescriptor(container, klass) { override fun toString(): String { return "Implicit: ${klass.simpleName}" diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt index bcd8e06dc8f..8f7deff93bc 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.container import com.intellij.util.containers.MultiMap import java.io.Closeable import java.io.PrintStream +import java.lang.IllegalStateException import java.lang.reflect.Modifier import java.lang.reflect.ParameterizedType import java.lang.reflect.Type @@ -37,13 +38,19 @@ internal class InvalidCardinalityException(message: String) : Exception(message) class ComponentStorage(private val myId: String, parent: ComponentStorage?) : ValueResolver { var state = ComponentStorageState.Initial - private val registry = ComponentRegistry() - init { - parent?.let { registry.addAll(it.registry) } - } private val descriptors = LinkedHashSet() private val dependencies = MultiMap.createLinkedSet() + private val clashResolvers = ArrayList>() + private val registry = ComponentRegistry() + + init { + parent?.let { + registry.addAll(it.registry) + clashResolvers.addAll(it.clashResolvers) + } + } + override fun resolve(request: Type, context: ValueResolveContext): ValueDescriptor? { fun ComponentDescriptor.isDefaultComponent(): Boolean = @@ -107,6 +114,10 @@ class ComponentStorage(private val myId: String, parent: ComponentStorage?) : Va return registry.tryGetEntry(request) } + internal fun registerClashResolvers(resolvers: List>) { + clashResolvers.addAll(resolvers) + } + internal fun registerDescriptors(context: ComponentResolveContext, items: List) { if (state == ComponentStorageState.Disposed) { throw ContainerConsistencyException("Cannot register descriptors in $state state") @@ -135,6 +146,7 @@ class ComponentStorage(private val myId: String, parent: ComponentStorage?) : Va val implicits = inspectDependenciesAndRegisterAdhoc(context, descriptors) + registry.resolveClashesIfAny(context.container, clashResolvers) injectProperties(context, descriptors + implicits) } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index 76dfa0b0bfb..216fc185ae3 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -6,9 +6,11 @@ package org.jetbrains.kotlin.resolve.jvm.platform import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useImpl import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.load.java.components.SamConversionResolver import org.jetbrains.kotlin.load.java.sam.JvmSamConversionTransformer import org.jetbrains.kotlin.load.java.sam.SamConversionResolverImpl import org.jetbrains.kotlin.resolve.PlatformConfiguratorBase @@ -18,7 +20,6 @@ import org.jetbrains.kotlin.resolve.jvm.* import org.jetbrains.kotlin.resolve.jvm.checkers.* import org.jetbrains.kotlin.resolve.jvm.multiplatform.JavaActualAnnotationArgumentExtractor import org.jetbrains.kotlin.synthetic.JavaSyntheticScopes -import org.jetbrains.kotlin.types.DynamicTypesSettings import org.jetbrains.kotlin.types.expressions.FunctionWithBigAritySupport object JvmPlatformConfigurator : PlatformConfiguratorBase( @@ -73,6 +74,10 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase( ExplicitMetadataChecker ), + additionalClashResolvers = listOf( + PlatformExtensionsClashResolver.FallbackToDefault(SamConversionResolver.Empty, SamConversionResolver::class.java) + ), + identifierChecker = JvmSimpleNameBacktickChecker, overloadFilter = JvmOverloadFilter, @@ -98,6 +103,6 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase( container.useImpl() container.useImpl() container.useImpl() - container.useInstance(FunctionWithBigAritySupport.LANGUAGE_VERSION_DEPENDENT) + container.useInstance(FunctionWithBigAritySupport.LanguageVersionDependent) } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/IdentifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/IdentifierChecker.kt index b440bd9f76f..83ffc2d425b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/IdentifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/IdentifierChecker.kt @@ -17,12 +17,14 @@ package org.jetbrains.kotlin.resolve import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtSimpleNameExpression @DefaultImplementation(impl = IdentifierChecker.Default::class) -interface IdentifierChecker { +interface IdentifierChecker : PlatformSpecificExtension { fun checkIdentifier(simpleNameExpression: KtSimpleNameExpression, diagnosticHolder: DiagnosticSink) fun checkDeclaration(declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) @@ -31,3 +33,18 @@ interface IdentifierChecker { override fun checkDeclaration(declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) {} } } + +class IdentifierCheckerClashesResolver : PlatformExtensionsClashResolver(IdentifierChecker::class.java) { + override fun resolveExtensionsClash(extensions: List): IdentifierChecker = CompositeIdentifierChecker(extensions) +} + +// Launches every underlying checker +class CompositeIdentifierChecker(private val identifierCheckers: List) : IdentifierChecker { + override fun checkIdentifier(simpleNameExpression: KtSimpleNameExpression, diagnosticHolder: DiagnosticSink) { + identifierCheckers.forEach { it.checkIdentifier(simpleNameExpression, diagnosticHolder) } + } + + override fun checkDeclaration(declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) { + identifierCheckers.forEach { it.checkDeclaration(declaration, diagnosticHolder) } + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index 1ece40f388c..0549370e12f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -6,12 +6,9 @@ package org.jetbrains.kotlin.resolve import org.jetbrains.kotlin.builtins.PlatformToKotlinClassMap -import org.jetbrains.kotlin.container.StorageComponentContainer -import org.jetbrains.kotlin.container.composeContainer -import org.jetbrains.kotlin.container.useImpl -import org.jetbrains.kotlin.container.useInstance -import org.jetbrains.kotlin.resolve.calls.checkers.* import org.jetbrains.kotlin.container.* +import org.jetbrains.kotlin.resolve.calls.checkers.* +import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator import org.jetbrains.kotlin.resolve.checkers.* import org.jetbrains.kotlin.resolve.lazy.DelegationFilter import org.jetbrains.kotlin.types.DynamicTypesSettings @@ -55,6 +52,20 @@ private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf( ) private val DEFAULT_ANNOTATION_CHECKERS = listOf() +private val DEFAULT_CLASH_RESOLVERS = listOf>( + IdentifierCheckerClashesResolver(), + + /** + * We should use NONE for clash resolution, because: + * - JvmTypeSpecificityComparator covers cases with flexible types and primitive types loaded from Java, and all this is irrelevant for + * non-JVM modules + * - JsTypeSpecificityComparator covers case with dynamics, which are not allowed in non-JS modules either + */ + PlatformExtensionsClashResolver.FallbackToDefault(TypeSpecificityComparator.NONE, TypeSpecificityComparator::class.java), + + PlatformExtensionsClashResolver.FallbackToDefault(DynamicTypesSettings(), DynamicTypesSettings::class.java) +) + abstract class PlatformConfiguratorBase( private val dynamicTypesSettings: DynamicTypesSettings? = null, @@ -63,6 +74,7 @@ abstract class PlatformConfiguratorBase( additionalTypeCheckers: List = emptyList(), additionalClassifierUsageCheckers: List = emptyList(), additionalAnnotationCheckers: List = emptyList(), + additionalClashResolvers: List> = emptyList(), private val identifierChecker: IdentifierChecker? = null, private val overloadFilter: OverloadFilter? = null, private val platformToKotlinClassMap: PlatformToKotlinClassMap? = null, @@ -76,6 +88,7 @@ abstract class PlatformConfiguratorBase( private val classifierUsageCheckers: List = DEFAULT_CLASSIFIER_USAGE_CHECKERS + additionalClassifierUsageCheckers private val annotationCheckers: List = DEFAULT_ANNOTATION_CHECKERS + additionalAnnotationCheckers + private val clashResolvers: List> = DEFAULT_CLASH_RESOLVERS + additionalClashResolvers override val platformSpecificContainer = composeContainer(this::class.java.simpleName) { useInstanceIfNotNull(dynamicTypesSettings) @@ -84,6 +97,7 @@ abstract class PlatformConfiguratorBase( typeCheckers.forEach { useInstance(it) } classifierUsageCheckers.forEach { useInstance(it) } annotationCheckers.forEach { useInstance(it) } + clashResolvers.forEach { useClashResolver(it) } useInstanceIfNotNull(identifierChecker) useInstanceIfNotNull(overloadFilter) useInstanceIfNotNull(platformToKotlinClassMap) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PlatformDiagnosticSuppressor.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PlatformDiagnosticSuppressor.kt index 6fa7a9e47ab..6dd3ac1cad4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PlatformDiagnosticSuppressor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PlatformDiagnosticSuppressor.kt @@ -17,11 +17,13 @@ package org.jetbrains.kotlin.resolve.checkers import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor @DefaultImplementation(PlatformDiagnosticSuppressor.Default::class) -interface PlatformDiagnosticSuppressor { +interface PlatformDiagnosticSuppressor : PlatformSpecificExtension{ fun shouldReportUnusedParameter(parameter: VariableDescriptor): Boolean fun shouldReportNoBody(descriptor: CallableMemberDescriptor): Boolean @@ -32,3 +34,18 @@ interface PlatformDiagnosticSuppressor { override fun shouldReportNoBody(descriptor: CallableMemberDescriptor): Boolean = true } } + +class CompositePlatformDiagnosticSuppressor(private val suppressors: List) : PlatformDiagnosticSuppressor { + override fun shouldReportUnusedParameter(parameter: VariableDescriptor): Boolean = + suppressors.all { it.shouldReportUnusedParameter(parameter) } + + override fun shouldReportNoBody(descriptor: CallableMemberDescriptor): Boolean = + suppressors.all { it.shouldReportNoBody(descriptor) } +} + +class PlatformDiagnosticSuppressorClashesResolver : PlatformExtensionsClashResolver( + PlatformDiagnosticSuppressor::class.java +) { + override fun resolveExtensionsClash(extensions: List): PlatformDiagnosticSuppressor = + CompositePlatformDiagnosticSuppressor(extensions) +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt index 2c6f9fb0981..bb2f9712fad 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt @@ -8,6 +8,8 @@ package org.jetbrains.kotlin.resolve.deprecation import com.intellij.psi.PsiElement import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors @@ -61,7 +63,7 @@ internal fun createDeprecationDiagnostic( } @DefaultImplementation(CoroutineCompatibilitySupport::class) -class CoroutineCompatibilitySupport private constructor(val enabled: Boolean) { +class CoroutineCompatibilitySupport private constructor(val enabled: Boolean) : PlatformSpecificExtension{ @Suppress("unused") constructor() : this(true) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt index 665372c50d7..8ffbca094c5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt @@ -11,6 +11,8 @@ import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor @@ -820,14 +822,19 @@ class DoubleColonExpressionResolver( } } -// By default, function types with big arity are supported. On platforms where they are not supported by default (e.g. JVM), -// LANGUAGE_VERSION_DEPENDENT should be used which makes the code check if the corresponding language feature is enabled. -@DefaultImplementation(FunctionWithBigAritySupport::class) -class FunctionWithBigAritySupport private constructor(val shouldCheckLanguageVersionSettings: Boolean) { - constructor() : this(false) +/** + * By default, function types with big arity are supported. On platforms where they are not supported by default (e.g. JVM), + * [LanguageVersionDependent] should be used which makes the code check if the corresponding language feature is enabled. + */ +@DefaultImplementation(FunctionWithBigAritySupport.Enabled::class) +interface FunctionWithBigAritySupport { + val shouldCheckLanguageVersionSettings: Boolean - companion object { - @JvmField - val LANGUAGE_VERSION_DEPENDENT = FunctionWithBigAritySupport(true) + object Enabled : FunctionWithBigAritySupport { + override val shouldCheckLanguageVersionSettings: Boolean = false } -} + + object LanguageVersionDependent : FunctionWithBigAritySupport { + override val shouldCheckLanguageVersionSettings: Boolean = true + } +} \ No newline at end of file diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/results/FlatSignature.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/results/FlatSignature.kt index c0d53aa14e8..465d736d301 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/results/FlatSignature.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/results/FlatSignature.kt @@ -17,6 +17,8 @@ package org.jetbrains.kotlin.resolve.calls.results import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.MemberDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor @@ -30,7 +32,7 @@ interface SpecificityComparisonCallbacks { } @DefaultImplementation(impl = TypeSpecificityComparator.NONE::class) -interface TypeSpecificityComparator { +interface TypeSpecificityComparator : PlatformSpecificExtension { fun isDefinitelyLessSpecific(specific: KotlinTypeMarker, general: KotlinTypeMarker): Boolean object NONE : TypeSpecificityComparator { diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/SamConversionResolver.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/SamConversionResolver.kt index 93f8ff29c0a..ccb97c0ec79 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/SamConversionResolver.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/SamConversionResolver.kt @@ -17,11 +17,13 @@ package org.jetbrains.kotlin.load.java.components import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.types.SimpleType @DefaultImplementation(impl = SamConversionResolver.Empty::class) -interface SamConversionResolver { +interface SamConversionResolver : PlatformSpecificExtension { object Empty : SamConversionResolver { override fun resolveFunctionTypeIfSamInterface(classDescriptor: JavaClassDescriptor): SimpleType? = null } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/dynamicTypes.kt b/core/descriptors/src/org/jetbrains/kotlin/types/dynamicTypes.kt index 04e4ff7fa08..0fd5245fa39 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/dynamicTypes.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/dynamicTypes.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.types import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.container.DefaultImplementation +import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver +import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRendererOptions @@ -25,7 +27,7 @@ import org.jetbrains.kotlin.types.model.DynamicTypeMarker import org.jetbrains.kotlin.types.typeUtil.builtIns @DefaultImplementation(impl = DynamicTypesSettings::class) -open class DynamicTypesSettings { +open class DynamicTypesSettings : PlatformSpecificExtension { open val dynamicTypesAllowed: Boolean get() = false } 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 a919b517603..4b5fec1f665 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt @@ -29,5 +29,7 @@ import kotlin.reflect.KClass * * 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. + * + * If you need more fine-grained control of clashes resolution, consider using [PlatformExtensionsClashResolver] **/ annotation class DefaultImplementation(val impl: KClass<*>) \ No newline at end of file diff --git a/core/util.runtime/src/org/jetbrains/kotlin/container/PlatformSpecificExtension.kt b/core/util.runtime/src/org/jetbrains/kotlin/container/PlatformSpecificExtension.kt new file mode 100644 index 00000000000..4777398c48d --- /dev/null +++ b/core/util.runtime/src/org/jetbrains/kotlin/container/PlatformSpecificExtension.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.container + +/** + * This is a marker-interface for components which are needed for common resolve + * facilities (like resolve, or deserialization), but are platform-specific. + * + * PlatformSpecificExtensions has to be present in the container in exactly one + * instance (hence common pattern with providing no-op DEFAULT/EMPTY implementation + * in the corresponding interface) + * + * In multiplatform modules such components require special treatment. Namely, + * if several components of the same type are provided, then it's not an illegal state; + * rather, we have to carefully resolve clash on case-by-case basis. + * See also [PlatformExtensionsClashResolver]. + */ +interface PlatformSpecificExtension> + +/** + * Allows to specify which [PlatformSpecificExtension] should be used if there were two or more registrations + * for [applicableTo] class in the container. + * + * [PlatformExtensionsClashResolver] should be registred in the container via [useClashResolver]-extension. + * + * NB. YOU DON'T NEED this mechanism for the most popular case of "one or several default vs. + * zero or one non-default". Just use [DefaultImplementation], and default instances will be automatically + * discriminated (see respective KDoc). + * Use [PlatformExtensionsClashResolver] only for cases when you need more invloved logic. + * + * Example: [org.jetbrains.kotlin.resolve.IdentifierChecker]. It is used in platform-agnostic code, + * which resolves and checks identifiers for correctness. Each platform has it's own rules + * regarding identifier correctness. In MPP modules we can't choose only one IdentifierChecker; + * instead, we have to provide a "composite" IdentifierChecker which will launch checks of *each* + * platform. + * + */ +abstract class PlatformExtensionsClashResolver>(val applicableTo: Class) { + abstract fun resolveExtensionsClash(extensions: List): E + + class FallbackToDefault>( + private val defaultValue: E, + applicableTo: Class + ) : PlatformExtensionsClashResolver(applicableTo) { + + override fun resolveExtensionsClash(extensions: List): E = defaultValue + } +} +