[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)
This commit is contained in:
@@ -102,6 +102,11 @@ class StorageComponentContainer(
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun registerClashResolvers(resolvers: List<PlatformExtensionsClashResolver<*>>): StorageComponentContainer {
|
||||
componentStorage.registerClashResolvers(resolvers)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun <T> create(request: Class<T>): T {
|
||||
val constructorBinding = request.bindToConstructor(unknownContext)
|
||||
val args = constructorBinding.argumentDescriptors.map { it.getValue() }.toTypedArray()
|
||||
|
||||
@@ -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 <reified T : Any> ComponentProvider.getValue(thisRef: Any?, desc: KProperty<*>): T {
|
||||
return getService(T::class.java)
|
||||
}
|
||||
|
||||
@@ -70,4 +70,20 @@ internal class ComponentRegistry {
|
||||
}
|
||||
registrationMap += other.registrationMap
|
||||
}
|
||||
|
||||
fun resolveClashesIfAny(container: ComponentContainer, clashResolvers: List<PlatformExtensionsClashResolver<*>>) {
|
||||
/*
|
||||
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<ComponentDescriptor> ?: continue
|
||||
if (clashedComponents.size <= 1) continue
|
||||
|
||||
val substituteDescriptor = ClashResolutionDescriptor(container, resolver, clashedComponents.toList())
|
||||
registrationMap[resolver.applicableTo] = substituteDescriptor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<E : PlatformSpecificExtension<E>>(
|
||||
container: ComponentContainer,
|
||||
private val resolver: PlatformExtensionsClashResolver<E>,
|
||||
private val clashedComponents: List<ComponentDescriptor>
|
||||
) : SingletonDescriptor(container) {
|
||||
|
||||
override fun createInstance(context: ValueResolveContext): Any {
|
||||
state = ComponentState.Initializing
|
||||
val extensions = computeArguments(clashedComponents) as List<E>
|
||||
val resolution = resolver.resolveExtensionsClash(extensions)
|
||||
state = ComponentState.Initialized
|
||||
return resolution
|
||||
}
|
||||
|
||||
override fun getRegistrations(): Iterable<Type> {
|
||||
throw IllegalStateException("Shouldn't be called")
|
||||
}
|
||||
|
||||
override fun getDependencies(context: ValueResolveContext): Collection<Type> {
|
||||
throw IllegalStateException("Shouldn't be called")
|
||||
}
|
||||
}
|
||||
|
||||
class ImplicitSingletonTypeComponentDescriptor(container: ComponentContainer, klass: Class<*>) : SingletonTypeComponentDescriptor(container, klass) {
|
||||
override fun toString(): String {
|
||||
return "Implicit: ${klass.simpleName}"
|
||||
|
||||
@@ -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<ComponentDescriptor>()
|
||||
private val dependencies = MultiMap.createLinkedSet<ComponentDescriptor, Type>()
|
||||
private val clashResolvers = ArrayList<PlatformExtensionsClashResolver<*>>()
|
||||
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<PlatformExtensionsClashResolver<*>>) {
|
||||
clashResolvers.addAll(resolvers)
|
||||
}
|
||||
|
||||
internal fun registerDescriptors(context: ComponentResolveContext, items: List<ComponentDescriptor>) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -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<JvmTypeSpecificityComparator>()
|
||||
container.useImpl<JvmDefaultSuperCallChecker>()
|
||||
container.useImpl<JvmSamConversionTransformer>()
|
||||
container.useInstance(FunctionWithBigAritySupport.LANGUAGE_VERSION_DEPENDENT)
|
||||
container.useInstance(FunctionWithBigAritySupport.LanguageVersionDependent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IdentifierChecker> {
|
||||
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>(IdentifierChecker::class.java) {
|
||||
override fun resolveExtensionsClash(extensions: List<IdentifierChecker>): IdentifierChecker = CompositeIdentifierChecker(extensions)
|
||||
}
|
||||
|
||||
// Launches every underlying checker
|
||||
class CompositeIdentifierChecker(private val identifierCheckers: List<IdentifierChecker>) : 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) }
|
||||
}
|
||||
}
|
||||
@@ -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<AdditionalAnnotationChecker>()
|
||||
|
||||
private val DEFAULT_CLASH_RESOLVERS = listOf<PlatformExtensionsClashResolver<*>>(
|
||||
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<AdditionalTypeChecker> = emptyList(),
|
||||
additionalClassifierUsageCheckers: List<ClassifierUsageChecker> = emptyList(),
|
||||
additionalAnnotationCheckers: List<AdditionalAnnotationChecker> = emptyList(),
|
||||
additionalClashResolvers: List<PlatformExtensionsClashResolver<*>> = 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<ClassifierUsageChecker> =
|
||||
DEFAULT_CLASSIFIER_USAGE_CHECKERS + additionalClassifierUsageCheckers
|
||||
private val annotationCheckers: List<AdditionalAnnotationChecker> = DEFAULT_ANNOTATION_CHECKERS + additionalAnnotationCheckers
|
||||
private val clashResolvers: List<PlatformExtensionsClashResolver<*>> = 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)
|
||||
|
||||
+18
-1
@@ -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<PlatformDiagnosticSuppressor>{
|
||||
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>) : 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>(
|
||||
PlatformDiagnosticSuppressor::class.java
|
||||
) {
|
||||
override fun resolveExtensionsClash(extensions: List<PlatformDiagnosticSuppressor>): PlatformDiagnosticSuppressor =
|
||||
CompositePlatformDiagnosticSuppressor(extensions)
|
||||
}
|
||||
@@ -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<CoroutineCompatibilitySupport>{
|
||||
@Suppress("unused")
|
||||
constructor() : this(true)
|
||||
|
||||
|
||||
+16
-9
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<TypeSpecificityComparator> {
|
||||
fun isDefinitelyLessSpecific(specific: KotlinTypeMarker, general: KotlinTypeMarker): Boolean
|
||||
|
||||
object NONE : TypeSpecificityComparator {
|
||||
|
||||
Reference in New Issue
Block a user