Do not require experimental propagation for body usages in same module

Unless it's a usage inside the body of an effectively public inline
function

 #KT-22759 In Progress
This commit is contained in:
Alexander Udalov
2018-01-02 18:49:34 +01:00
parent 6d4e8f3781
commit 29c35e6686
37 changed files with 511 additions and 50 deletions
@@ -227,7 +227,7 @@ class LazyTopDownAnalyzer(
checkClassifierUsages(
declarations, classifierUsageCheckers,
ClassifierUsageCheckerContext(trace, languageVersionSettings, deprecationResolver)
ClassifierUsageCheckerContext(trace, languageVersionSettings, deprecationResolver, moduleDescriptor)
)
return c
@@ -278,8 +278,9 @@ public class ModifiersChecker {
public void runDeclarationCheckers(@NotNull KtDeclaration declaration, @NotNull DeclarationDescriptor descriptor) {
DeclarationCheckerContext context =
new DeclarationCheckerContext(trace, languageVersionSettings, deprecationResolver, expectActualTracker);
DeclarationCheckerContext context = new DeclarationCheckerContext(
trace, languageVersionSettings, deprecationResolver, moduleDescriptor, expectActualTracker
);
for (DeclarationChecker checker : declarationCheckers) {
checker.check(declaration, descriptor, context);
}
@@ -301,19 +302,22 @@ public class ModifiersChecker {
private final LanguageVersionSettings languageVersionSettings;
private final ExpectActualTracker expectActualTracker;
private final DeprecationResolver deprecationResolver;
private final ModuleDescriptor moduleDescriptor;
public ModifiersChecker(
@NotNull AnnotationChecker annotationChecker,
@NotNull Iterable<DeclarationChecker> declarationCheckers,
@NotNull LanguageVersionSettings languageVersionSettings,
@NotNull ExpectActualTracker expectActualTracker,
@NotNull DeprecationResolver deprecationResolver
@NotNull DeprecationResolver deprecationResolver,
@NotNull ModuleDescriptor moduleDescriptor
) {
this.annotationChecker = annotationChecker;
this.declarationCheckers = declarationCheckers;
this.languageVersionSettings = languageVersionSettings;
this.expectActualTracker = expectActualTracker;
this.deprecationResolver = deprecationResolver;
this.moduleDescriptor = moduleDescriptor;
}
@NotNull
@@ -16,12 +16,12 @@
package org.jetbrains.kotlin.resolve.calls
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.contracts.EffectSystem
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.BindingContext.CONSTRAINT_SYSTEM_COMPLETER
@@ -59,7 +59,7 @@ class CallCompleter(
private val candidateResolver: CandidateResolver,
private val dataFlowAnalyzer: DataFlowAnalyzer,
private val callCheckers: Iterable<CallChecker>,
private val builtIns: KotlinBuiltIns,
private val moduleDescriptor: ModuleDescriptor,
private val deprecationResolver: DeprecationResolver,
private val effectSystem: EffectSystem
) {
@@ -87,7 +87,7 @@ class CallCompleter(
if (calleeExpression != null && !calleeExpression.isFakeElement) calleeExpression
else resolvedCall.call.callElement
val callCheckerContext = CallCheckerContext(context, deprecationResolver)
val callCheckerContext = CallCheckerContext(context, deprecationResolver, moduleDescriptor)
for (callChecker in callCheckers) {
callChecker.check(resolvedCall, reportOn, callCheckerContext)
@@ -202,7 +202,7 @@ class CallCompleter(
updateSystemIfNeeded { builder ->
val returnTypeInSystem = builder.typeInSystem(returnType)
if (returnTypeInSystem != null) {
builder.addSubtypeConstraint(returnTypeInSystem, builtIns.unitType, EXPECTED_TYPE_POSITION.position())
builder.addSubtypeConstraint(returnTypeInSystem, moduleDescriptor.builtIns.unitType, EXPECTED_TYPE_POSITION.position())
val system = builder.build()
if (system.status.isSuccessful()) system else null
} else null
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.calls.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DeprecationResolver
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
@@ -39,6 +40,7 @@ interface CallChecker {
class CallCheckerContext @JvmOverloads constructor(
val resolutionContext: ResolutionContext<*>,
override val deprecationResolver: DeprecationResolver,
override val moduleDescriptor: ModuleDescriptor,
override val trace: BindingTrace = resolutionContext.trace
) : CheckerContext {
val scope: LexicalScope
@@ -65,7 +65,8 @@ class KotlinToResolvedCallTransformer(
private val deprecationResolver: DeprecationResolver,
private val expressionTypingServices: ExpressionTypingServices,
private val doubleColonExpressionResolver: DoubleColonExpressionResolver,
private val additionalDiagnosticReporter: AdditionalDiagnosticReporter
private val additionalDiagnosticReporter: AdditionalDiagnosticReporter,
private val moduleDescriptor: ModuleDescriptor
) {
companion object {
private val REPORT_MISSING_NEW_INFERENCE_DIAGNOSTIC
@@ -97,8 +98,8 @@ class KotlinToResolvedCallTransformer(
CallResolutionResult.Type.ERROR, CallResolutionResult.Type.COMPLETED -> {
val resultSubstitutor = baseResolvedCall.constraintSystem.buildResultingSubstitutor()
val ktPrimitiveCompleter = ResolvedAtomCompleter(
resultSubstitutor, context.trace, context, this,
expressionTypingServices, argumentTypeResolver, doubleColonExpressionResolver, deprecationResolver
resultSubstitutor, context.trace, context, this, expressionTypingServices, argumentTypeResolver,
doubleColonExpressionResolver, deprecationResolver, moduleDescriptor
)
for (subKtPrimitive in candidate.subResolvedAtoms) {
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.resolve.calls.tower
import org.jetbrains.kotlin.builtins.replaceReturnType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl
import org.jetbrains.kotlin.psi.KtElement
@@ -53,9 +54,10 @@ class ResolvedAtomCompleter(
private val expressionTypingServices: ExpressionTypingServices,
private val argumentTypeResolver: ArgumentTypeResolver,
private val doubleColonExpressionResolver: DoubleColonExpressionResolver,
deprecationResolver: DeprecationResolver
deprecationResolver: DeprecationResolver,
moduleDescriptor: ModuleDescriptor
) {
private val callCheckerContext = CallCheckerContext(topLevelCallContext, deprecationResolver)
private val callCheckerContext = CallCheckerContext(topLevelCallContext, deprecationResolver, moduleDescriptor)
private fun complete(resolvedAtom: ResolvedAtom) {
when (resolvedAtom) {
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DeprecationResolver
@@ -26,4 +27,6 @@ interface CheckerContext {
val languageVersionSettings: LanguageVersionSettings
val deprecationResolver: DeprecationResolver
val moduleDescriptor: ModuleDescriptor
}
@@ -35,7 +35,8 @@ interface ClassifierUsageChecker {
class ClassifierUsageCheckerContext(
override val trace: BindingTrace,
override val languageVersionSettings: LanguageVersionSettings,
override val deprecationResolver: DeprecationResolver
override val deprecationResolver: DeprecationResolver,
override val moduleDescriptor: ModuleDescriptor
) : CheckerContext
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingTrace
@@ -31,5 +32,6 @@ class DeclarationCheckerContext(
override val trace: BindingTrace,
override val languageVersionSettings: LanguageVersionSettings,
override val deprecationResolver: DeprecationResolver,
override val moduleDescriptor: ModuleDescriptor,
val expectActualTracker: ExpectActualTracker
) : CheckerContext
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.resolve.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.name.FqName
@@ -37,6 +34,7 @@ import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -54,7 +52,12 @@ object ExperimentalUsageChecker : CallChecker {
private val LINKAGE_IMPACT = Name.identifier("LINKAGE")
private val RUNTIME_IMPACT = Name.identifier("RUNTIME")
internal data class Experimentality(val annotationFqName: FqName, val severity: Severity, private val impact: List<Impact>) {
internal data class Experimentality(
val markerDescriptor: ClassDescriptor,
val annotationFqName: FqName,
val severity: Severity,
private val impact: List<Impact>
) {
val isCompilationOnly: Boolean get() = impact.all(Impact.COMPILATION::equals)
enum class Severity { WARNING, ERROR }
@@ -67,22 +70,33 @@ object ExperimentalUsageChecker : CallChecker {
}
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
checkExperimental(resolvedCall.resultingDescriptor, reportOn, context.trace)
checkExperimental(resolvedCall.resultingDescriptor, reportOn, context.trace, context.moduleDescriptor)
}
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, trace: BindingTrace) {
for (experimentality in descriptor.loadExperimentalities()) {
val (annotationFqName, severity) = experimentality
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, trace: BindingTrace, module: ModuleDescriptor) {
val experimentalities = descriptor.loadExperimentalities()
if (experimentalities.isEmpty()) return
val isBodyUsageExceptPublicInline = element.isBodyUsage(trace.bindingContext, allowPublicInline = false)
val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(trace.bindingContext, allowPublicInline = true)
for (experimentality in experimentalities) {
val isBodyUsageOfCompilationExperimentality =
experimentality.isCompilationOnly && element.isBodyUsage()
experimentality.isCompilationOnly && isBodyUsage
val isBodyUsageInSameModule =
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
val annotationFqName = experimentality.annotationFqName
val isExperimentalityAccepted =
isBodyUsageInSameModule ||
(isBodyUsageOfCompilationExperimentality &&
element.hasContainerAnnotatedWithUseExperimental(annotationFqName, trace.bindingContext)) ||
element.propagates(annotationFqName, trace.bindingContext)
if (!isExperimentalityAccepted) {
val diagnostic = when (severity) {
val diagnostic = when (experimentality.severity) {
ExperimentalUsageChecker.Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
ExperimentalUsageChecker.Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
}
@@ -125,16 +139,18 @@ object ExperimentalUsageChecker : CallChecker {
}
} ?: Experimentality.DEFAULT_IMPACT
return Experimentality(fqNameSafe, severity, impact)
return Experimentality(this, fqNameSafe, severity, impact)
}
// Returns true if this element appears in the body of some function and is not visible in any non-local declaration signature.
// If that's the case, one can opt-in to using the corresponding experimental API by annotating this element (or any of its
// enclosing declarations) with @UseExperimental(X::class), not requiring propagation of the experimental annotation to the call sites.
// (Note that this is allowed only if X's impact is [COMPILATION].)
private fun PsiElement.isBodyUsage(): Boolean {
private fun PsiElement.isBodyUsage(bindingContext: BindingContext, allowPublicInline: Boolean): Boolean {
return anyParentMatches { element, parent ->
element == (parent as? KtDeclarationWithBody)?.bodyExpression ||
element == (parent as? KtDeclarationWithBody)?.bodyExpression?.takeIf {
allowPublicInline || !parent.isPublicInline(bindingContext)
} ||
element == (parent as? KtDeclarationWithInitializer)?.initializer ||
element == (parent as? KtClassInitializer)?.body ||
element == (parent as? KtParameter)?.defaultValue ||
@@ -144,6 +160,19 @@ object ExperimentalUsageChecker : CallChecker {
}
}
private fun PsiElement.isPublicInline(bindingContext: BindingContext): Boolean {
val descriptor = when (this) {
is KtFunction -> bindingContext.get(BindingContext.FUNCTION, this)
is KtPropertyAccessor -> bindingContext.get(BindingContext.PROPERTY_ACCESSOR, this)
else -> null
}
return descriptor != null && descriptor.isInline && descriptor.effectiveVisibility().let {
it == EffectiveVisibility.Public ||
it == EffectiveVisibility.ProtectedBound ||
it is EffectiveVisibility.Protected
}
}
// Checks whether any of the non-local enclosing declarations is annotated with annotationFqName, effectively requiring
// propagation for the experimental annotation to the call sites
private fun PsiElement.propagates(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
@@ -185,7 +214,7 @@ object ExperimentalUsageChecker : CallChecker {
object ClassifierUsage : ClassifierUsageChecker {
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
checkExperimental(targetDescriptor, element, context.trace)
checkExperimental(targetDescriptor, element, context.trace, context.moduleDescriptor)
}
}
@@ -938,7 +938,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
if (resolvedCall != null && trace.wantsDiagnostics()) {
// Call must be validated with the actual, not temporary trace in order to report operator diagnostic
// Only unary assignment expressions (++, --) and +=/... must be checked, normal assignments have the proper trace
CallCheckerContext callCheckerContext = new CallCheckerContext(context, components.deprecationResolver, trace);
CallCheckerContext callCheckerContext =
new CallCheckerContext(context, components.deprecationResolver, components.moduleDescriptor, trace);
for (CallChecker checker : components.callCheckers) {
checker.check(resolvedCall, expression, callCheckerContext);
}
@@ -1015,7 +1016,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
@NotNull
private CallCheckerContext createCallCheckerContext(@NotNull ExpressionTypingContext context) {
return new CallCheckerContext(context, components.deprecationResolver);
return new CallCheckerContext(context, components.deprecationResolver, components.moduleDescriptor);
}
@Override