Forbid using experimental markers on override declarations

#KT-45844 Fixed
This commit is contained in:
Mikhail Glukhikh
2021-05-21 15:30:51 +03:00
committed by teamcityserver
parent bb9efab3c4
commit 603afe89a2
10 changed files with 294 additions and 3 deletions
@@ -287,6 +287,7 @@ public interface Errors {
DiagnosticFactory0<PsiElement> EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> EXPERIMENTAL_ANNOTATION_ON_GETTER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> EXPERIMENTAL_ANNOTATION_ON_OVERRIDE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR);
@@ -171,6 +171,7 @@ public class DefaultErrorMessages {
MAP.put(EXPERIMENTAL_ANNOTATION_WITH_WRONG_RETENTION, "Opt-in requirement marker annotation cannot be used with SOURCE retention. Please replace retention with BINARY");
MAP.put(EXPERIMENTAL_ANNOTATION_ON_GETTER, "Opt-in requirement marker annotation cannot be used on getter. Please annotate property instead");
MAP.put(EXPERIMENTAL_ANNOTATION_ON_OVERRIDE, "Opt-in requirement marker annotation on override requires the same marker on base declaration");
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "{0}", STRING);
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "{0}", STRING);
@@ -6,13 +6,20 @@
package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.BindingContext
@@ -50,12 +57,27 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
isAnnotatedWithExperimental = true
}
}
if (annotation.annotationClass?.annotations?.any { it.fqName in ExperimentalUsageChecker.EXPERIMENTAL_FQ_NAMES } == true) {
val annotationClass = annotation.annotationClass ?: continue
if (annotationClass.annotations.any { it.fqName in ExperimentalUsageChecker.EXPERIMENTAL_FQ_NAMES }) {
if (KotlinTarget.PROPERTY_GETTER in actualTargets ||
entry.useSiteTarget?.getAnnotationUseSiteTarget() == AnnotationUseSiteTarget.PROPERTY_GETTER
) {
trace.report(Errors.EXPERIMENTAL_ANNOTATION_ON_GETTER.on(entry))
}
val annotated = entry.getStrictParentOfType<KtAnnotated>() ?: continue
val useSiteTarget = entry.useSiteTarget?.getAnnotationUseSiteTarget()
if (annotated is KtCallableDeclaration &&
annotated !is KtPropertyAccessor &&
useSiteTarget == null &&
annotated.hasModifier(KtTokens.OVERRIDE_KEYWORD)
) {
val descriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated)
if (descriptor is CallableMemberDescriptor &&
!descriptor.hasExperimentalOverriddenDescriptors(annotationClass.fqNameSafe)
) {
trace.report(Errors.EXPERIMENTAL_ANNOTATION_ON_OVERRIDE.on(entry))
}
}
}
}
@@ -64,6 +86,25 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
}
}
private fun CallableMemberDescriptor.hasExperimentalOverriddenDescriptors(
experimentalFqName: FqName,
visited: MutableSet<CallableMemberDescriptor> = mutableSetOf()
): Boolean {
if (this in visited) return false
visited += this
for (overridden in overriddenDescriptors) {
if (overridden.annotations.any { it.fqName == experimentalFqName }) {
return true
}
if (overridden.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE &&
overridden.hasExperimentalOverriddenDescriptors(experimentalFqName)
) {
return true
}
}
return false
}
private fun checkUseExperimentalUsage(annotationClasses: List<ConstantValue<*>>, trace: BindingTrace, entry: KtAnnotationEntry) {
if (annotationClasses.isEmpty()) {
trace.report(Errors.USE_EXPERIMENTAL_WITHOUT_ARGUMENTS.on(entry))