Add internal WasExperimental for stdlib
Usages of declarations annotated with WasExperimental are allowed even if the API version requirement is not satisfied, provided that the opt-in to all mentioned markers is given. This is needed for smooth graduation of API in kotlin-stdlib
This commit is contained in:
+6
-3
@@ -20,6 +20,7 @@ import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.API_NOT_AVAILABLE
|
||||
import org.jetbrains.kotlin.resolve.SinceKotlinAccessibility
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
|
||||
import org.jetbrains.kotlin.resolve.checkSinceKotlinVersionAccessibility
|
||||
@@ -34,13 +35,15 @@ object ApiVersionCallChecker : CallChecker {
|
||||
// Objects will be checked by ApiVersionClassifierUsageChecker
|
||||
if (targetDescriptor is FakeCallableDescriptorForObject) return
|
||||
|
||||
val accessible = targetDescriptor.checkSinceKotlinVersionAccessibility(context.languageVersionSettings) { version ->
|
||||
val accessibility = targetDescriptor.checkSinceKotlinVersionAccessibility(context.languageVersionSettings)
|
||||
if (accessibility is SinceKotlinAccessibility.NotAccessible) {
|
||||
context.trace.report(
|
||||
API_NOT_AVAILABLE.on(element, version.versionString, context.languageVersionSettings.apiVersion.versionString)
|
||||
API_NOT_AVAILABLE.on(element, accessibility.version.versionString, context.languageVersionSettings.apiVersion.versionString)
|
||||
)
|
||||
}
|
||||
|
||||
if (accessible && targetDescriptor is PropertyDescriptor && DeprecatedCallChecker.shouldCheckPropertyGetter(element)) {
|
||||
if (accessibility == SinceKotlinAccessibility.Accessible &&
|
||||
targetDescriptor is PropertyDescriptor && DeprecatedCallChecker.shouldCheckPropertyGetter(element)) {
|
||||
targetDescriptor.getter?.let { check(it, context, element) }
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isConventionCall
|
||||
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInfixCall
|
||||
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isSuperOrDelegatingConstructorCall
|
||||
import org.jetbrains.kotlin.resolve.calls.components.KotlinResolutionCallbacks
|
||||
import org.jetbrains.kotlin.resolve.calls.components.KotlinResolutionStatelessCallbacks
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.isCoroutineCallWithAdditionalInference
|
||||
import org.jetbrains.kotlin.resolve.calls.model.CallableReferenceKotlinCallArgument
|
||||
@@ -51,8 +52,15 @@ class KotlinResolutionStatelessCallbacksImpl(
|
||||
override fun isSuperOrDelegatingConstructorCall(kotlinCall: KotlinCall) =
|
||||
kotlinCall is PSIKotlinCallImpl && isSuperOrDelegatingConstructorCall(kotlinCall.psiCall)
|
||||
|
||||
override fun isHiddenInResolution(descriptor: DeclarationDescriptor, kotlinCall: KotlinCall) =
|
||||
deprecationResolver.isHiddenInResolution(descriptor, isSuperOrDelegatingConstructorCall(kotlinCall))
|
||||
override fun isHiddenInResolution(
|
||||
descriptor: DeclarationDescriptor, kotlinCall: KotlinCall, resolutionCallbacks: KotlinResolutionCallbacks
|
||||
) =
|
||||
deprecationResolver.isHiddenInResolution(
|
||||
descriptor,
|
||||
(kotlinCall as? PSIKotlinCall)?.psiCall,
|
||||
(resolutionCallbacks as? KotlinResolutionCallbacksImpl)?.trace?.bindingContext,
|
||||
isSuperOrDelegatingConstructorCall(kotlinCall)
|
||||
)
|
||||
|
||||
override fun isSuperExpression(receiver: SimpleKotlinCallArgument?): Boolean =
|
||||
receiver?.psiExpression is KtSuperExpression
|
||||
|
||||
+12
-4
@@ -35,7 +35,10 @@ import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInfixCall
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.createLookupLocation
|
||||
import org.jetbrains.kotlin.resolve.calls.context.*
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.CoroutineInferenceSupport
|
||||
import org.jetbrains.kotlin.resolve.calls.model.*
|
||||
import org.jetbrains.kotlin.resolve.calls.model.KotlinCallDiagnostic
|
||||
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCallImpl
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCallImpl
|
||||
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsImpl
|
||||
import org.jetbrains.kotlin.resolve.calls.results.ResolutionResultsHandler
|
||||
import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
|
||||
@@ -219,7 +222,10 @@ class NewResolutionOldInference(
|
||||
val candidateTrace = TemporaryBindingTrace.create(basicCallContext.trace, "Context for resolve candidate")
|
||||
val resolvedCall = ResolvedCallImpl.create(candidate, candidateTrace, tracing, basicCallContext.dataFlowInfoForArguments)
|
||||
|
||||
if (deprecationResolver.isHiddenInResolution(candidate.descriptor, basicCallContext.isSuperCall)) {
|
||||
if (deprecationResolver.isHiddenInResolution(
|
||||
candidate.descriptor, basicCallContext.call, basicCallContext.trace.bindingContext, basicCallContext.isSuperCall
|
||||
)
|
||||
) {
|
||||
return@map MyCandidate(listOf(HiddenDescriptor), resolvedCall)
|
||||
}
|
||||
|
||||
@@ -448,8 +454,10 @@ class NewResolutionOldInference(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (deprecationResolver.isHiddenInResolution(towerCandidate.descriptor, basicCallContext.isSuperCall)) {
|
||||
if (deprecationResolver.isHiddenInResolution(
|
||||
towerCandidate.descriptor, basicCallContext.call, basicCallContext.trace.bindingContext, basicCallContext.isSuperCall
|
||||
)
|
||||
) {
|
||||
return MyCandidate(listOf(HiddenDescriptor), candidateCall)
|
||||
}
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ class PSICallResolver(
|
||||
override fun factoryForVariable(stripExplicitReceiver: Boolean): CandidateFactory<KotlinResolutionCandidate> {
|
||||
val explicitReceiver = if (stripExplicitReceiver) null else kotlinCall.explicitReceiver
|
||||
val variableCall = PSIKotlinCallForVariable(kotlinCall, explicitReceiver, kotlinCall.name)
|
||||
return SimpleCandidateFactory(callComponents, scopeTower, variableCall, context.inferenceSession)
|
||||
return SimpleCandidateFactory(callComponents, scopeTower, variableCall, createResolutionCallbacks(context))
|
||||
}
|
||||
|
||||
override fun factoryForInvoke(variable: KotlinResolutionCandidate, useExplicitReceiver: Boolean):
|
||||
@@ -406,7 +406,7 @@ class PSICallResolver(
|
||||
}
|
||||
|
||||
return variableCallArgument.receiver to SimpleCandidateFactory(
|
||||
callComponents, scopeTower, callForInvoke, context.inferenceSession
|
||||
callComponents, scopeTower, callForInvoke, createResolutionCallbacks(context)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -19,13 +19,17 @@ package org.jetbrains.kotlin.resolve.checkers
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.resolve.SinceKotlinAccessibility
|
||||
import org.jetbrains.kotlin.resolve.checkSinceKotlinVersionAccessibility
|
||||
|
||||
object ApiVersionClassifierUsageChecker : ClassifierUsageChecker {
|
||||
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
|
||||
targetDescriptor.checkSinceKotlinVersionAccessibility(context.languageVersionSettings) { version ->
|
||||
val accessibility = targetDescriptor.checkSinceKotlinVersionAccessibility(context.languageVersionSettings)
|
||||
if (accessibility is SinceKotlinAccessibility.NotAccessible) {
|
||||
context.trace.report(
|
||||
Errors.API_NOT_AVAILABLE.on(element, version.versionString, context.languageVersionSettings.apiVersion.versionString)
|
||||
Errors.API_NOT_AVAILABLE.on(
|
||||
element, accessibility.version.versionString, context.languageVersionSettings.apiVersion.versionString
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+26
-6
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.config.AnalysisFlag
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -62,7 +63,9 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
companion object {
|
||||
val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental")
|
||||
internal val USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
|
||||
private val WAS_EXPERIMENTAL_FQ_NAME = FqName("kotlin.WasExperimental")
|
||||
internal val USE_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
||||
private val WAS_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
||||
|
||||
private val LEVEL = Name.identifier("level")
|
||||
private val WARNING_LEVEL = Name.identifier("WARNING")
|
||||
@@ -119,21 +122,24 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
return Experimentality(fqNameSafe, severity)
|
||||
}
|
||||
|
||||
private fun PsiElement.isExperimentalityAccepted(annotationFqName: FqName, context: CheckerContext): Boolean =
|
||||
isExperimentalityAccepted(annotationFqName, context.languageVersionSettings, context.trace.bindingContext)
|
||||
|
||||
/**
|
||||
* Checks whether there's an element lexically above in the tree, annotated with `@UseExperimental(X::class)`, or a declaration
|
||||
* annotated with `@X` where [annotationFqName] is the FQ name of X
|
||||
*/
|
||||
private fun PsiElement.isExperimentalityAccepted(annotationFqName: FqName, context: CheckerContext): Boolean {
|
||||
val languageVersionSettings = context.languageVersionSettings
|
||||
val bindingContext = context.trace.bindingContext
|
||||
|
||||
return annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.experimental) ||
|
||||
fun PsiElement.isExperimentalityAccepted(
|
||||
annotationFqName: FqName,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
bindingContext: BindingContext
|
||||
): Boolean =
|
||||
annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.experimental) ||
|
||||
annotationFqName.asString() in languageVersionSettings.getFlag(AnalysisFlag.useExperimental) ||
|
||||
anyParentMatches { element ->
|
||||
element.isDeclarationAnnotatedWith(annotationFqName, bindingContext) ||
|
||||
element.isElementAnnotatedWithUseExperimentalOf(annotationFqName, bindingContext)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement.isDeclarationAnnotatedWith(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
|
||||
if (this !is KtDeclaration) return false
|
||||
@@ -163,6 +169,20 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Annotated.loadWasExperimentalMarkerFqNames(): List<FqName> {
|
||||
val wasExperimental = annotations.findAnnotation(WAS_EXPERIMENTAL_FQ_NAME)
|
||||
if (wasExperimental != null) {
|
||||
val annotationClasses = wasExperimental.allValueArguments[WAS_EXPERIMENTAL_ANNOTATION_CLASS]
|
||||
if (annotationClasses is ArrayValue) {
|
||||
return annotationClasses.value.mapNotNull { annotationClass ->
|
||||
(annotationClass as? KClassValue)?.value?.constructor?.declarationDescriptor?.fqNameSafe
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun checkCompilerArguments(
|
||||
module: ModuleDescriptor,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
|
||||
@@ -32,10 +32,12 @@ import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.VersionRequirement
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.Call
|
||||
import org.jetbrains.kotlin.resolve.DeprecationLevelValue.*
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.isOperatorMod
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.shouldWarnAboutDeprecatedModFromBuiltIns
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
|
||||
import org.jetbrains.kotlin.resolve.constants.AnnotationValue
|
||||
import org.jetbrains.kotlin.resolve.constants.EnumValue
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
@@ -210,6 +212,8 @@ class DeprecationResolver(
|
||||
@JvmOverloads
|
||||
fun isHiddenInResolution(
|
||||
descriptor: DeclarationDescriptor,
|
||||
call: Call? = null,
|
||||
bindingContext: BindingContext? = null,
|
||||
isSuperCall: Boolean = false
|
||||
): Boolean {
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
@@ -217,7 +221,19 @@ class DeprecationResolver(
|
||||
if (descriptor.isHiddenForResolutionEverywhereBesideSupercalls && !isSuperCall) return true
|
||||
}
|
||||
|
||||
if (!isHiddenBecauseOfKotlinVersionAccessibility(descriptor.original)) return true
|
||||
val sinceKotlinAccessibility = isHiddenBecauseOfKotlinVersionAccessibility(descriptor.original)
|
||||
if (sinceKotlinAccessibility is SinceKotlinAccessibility.NotAccessible) return true
|
||||
|
||||
if (sinceKotlinAccessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) {
|
||||
if (call != null && bindingContext != null) {
|
||||
return with(ExperimentalUsageChecker) {
|
||||
sinceKotlinAccessibility.annotationFqNames.any { fqName ->
|
||||
!call.callElement.isExperimentalityAccepted(fqName, languageVersionSettings, bindingContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return isDeprecatedHidden(descriptor)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.isChildOf
|
||||
import org.jetbrains.kotlin.name.isSubpackageOf
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import org.jetbrains.kotlin.resolve.SinceKotlinAccessibility
|
||||
import org.jetbrains.kotlin.resolve.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.checkSinceKotlinVersionAccessibility
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
@@ -69,7 +70,7 @@ class DefaultImportProvider(
|
||||
val excludedImports: List<FqName> by storageManager.createLazyValue {
|
||||
val builtinTypeAliases =
|
||||
moduleDescriptor.findTypeAliasesInPackages(PACKAGES_WITH_ALIASES)
|
||||
.filter { it.checkSinceKotlinVersionAccessibility(languageVersionSettings) }
|
||||
.filter { it.checkSinceKotlinVersionAccessibility(languageVersionSettings) == SinceKotlinAccessibility.Accessible }
|
||||
|
||||
val nonKotlinDefaultImportedPackages =
|
||||
defaultImports
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.resolve
|
||||
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForTypeAliasObject
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
|
||||
|
||||
sealed class SinceKotlinAccessibility {
|
||||
object Accessible : SinceKotlinAccessibility()
|
||||
|
||||
data class NotAccessibleButWasExperimental(
|
||||
val version: ApiVersion,
|
||||
val annotationFqNames: List<FqName>
|
||||
) : SinceKotlinAccessibility()
|
||||
|
||||
data class NotAccessible(
|
||||
val version: ApiVersion
|
||||
) : SinceKotlinAccessibility()
|
||||
}
|
||||
|
||||
fun DeclarationDescriptor.checkSinceKotlinVersionAccessibility(languageVersionSettings: LanguageVersionSettings): SinceKotlinAccessibility {
|
||||
val version =
|
||||
if (this is CallableMemberDescriptor && !kind.isReal) getSinceKotlinVersionByOverridden(this)
|
||||
else getOwnSinceKotlinVersion()
|
||||
|
||||
// Allow access in the following cases:
|
||||
// 1) There's no @SinceKotlin annotation for this descriptor
|
||||
// 2) There's a @SinceKotlin annotation but its value is some unrecognizable nonsense
|
||||
// 3) The value as a version is not greater than our API version
|
||||
if (version == null || version <= languageVersionSettings.apiVersion) return SinceKotlinAccessibility.Accessible
|
||||
|
||||
val wasExperimentalFqNames = with(ExperimentalUsageChecker) { loadWasExperimentalMarkerFqNames() }
|
||||
if (wasExperimentalFqNames.isNotEmpty()) {
|
||||
return SinceKotlinAccessibility.NotAccessibleButWasExperimental(version, wasExperimentalFqNames)
|
||||
}
|
||||
|
||||
return SinceKotlinAccessibility.NotAccessible(version)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if there are no overridden members or if there's at least one declaration in the hierarchy not annotated with [SinceKotlin],
|
||||
* or the minimal value of the version from all declarations annotated with [SinceKotlin] otherwise.
|
||||
*/
|
||||
private fun getSinceKotlinVersionByOverridden(descriptor: CallableMemberDescriptor): ApiVersion? {
|
||||
return DescriptorUtils.getAllOverriddenDeclarations(descriptor).map { it.getOwnSinceKotlinVersion() ?: return null }.min()
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor.getOwnSinceKotlinVersion(): ApiVersion? {
|
||||
// TODO: use-site targeted annotations
|
||||
fun DeclarationDescriptor.loadAnnotationValue(): ApiVersion? =
|
||||
(annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME)?.allValueArguments?.values?.singleOrNull()?.value as? String)
|
||||
?.let(ApiVersion.Companion::parse)
|
||||
|
||||
val ownVersion = loadAnnotationValue()
|
||||
val ctorClass = (this as? ConstructorDescriptor)?.containingDeclaration?.loadAnnotationValue()
|
||||
val property = (this as? PropertyAccessorDescriptor)?.correspondingProperty?.loadAnnotationValue()
|
||||
|
||||
val typeAliasDescriptor = (this as? TypeAliasDescriptor) ?: (this as? TypeAliasConstructorDescriptor)?.typeAliasDescriptor
|
||||
?: (this as? FakeCallableDescriptorForTypeAliasObject)?.typeAliasDescriptor
|
||||
|
||||
val typeAlias = typeAliasDescriptor?.loadAnnotationValue()
|
||||
|
||||
// We should check only the upper-most classifier ('A' in 'A<B<C>>') to guarantee binary compatibility.
|
||||
val underlyingClass = typeAliasDescriptor?.classDescriptor?.loadAnnotationValue()
|
||||
|
||||
val underlyingConstructor = (this as? TypeAliasConstructorDescriptor)?.underlyingConstructorDescriptor?.loadAnnotationValue()
|
||||
val underlyingObject = (this as? FakeCallableDescriptorForTypeAliasObject)?.getReferencedObject()?.loadAnnotationValue()
|
||||
|
||||
return listOfNotNull(ownVersion, ctorClass, property, typeAlias, underlyingClass, underlyingConstructor, underlyingObject).max()
|
||||
}
|
||||
Reference in New Issue
Block a user