[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:
Dmitry Savvinov
2019-03-13 17:29:50 +03:00
parent b631e89ea7
commit 72e28d37fc
16 changed files with 209 additions and 26 deletions
@@ -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)
@@ -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)
@@ -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
}
}