Add applicability checks & tests for SubclassOptInRequired

This commit is contained in:
Mikhail Glukhikh
2022-08-08 11:46:46 +02:00
committed by teamcity
parent 66e710704a
commit 80a9f22052
16 changed files with 431 additions and 18 deletions
@@ -11,7 +11,6 @@ import com.intellij.psi.impl.source.tree.LeafPsiElement;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.config.LanguageVersion;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.rendering.DeclarationWithDiagnosticComponents;
@@ -27,7 +26,6 @@ import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData;
import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData;
import org.jetbrains.kotlin.resolve.calls.inference.model.TypeVariableTypeConstructor;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier;
import org.jetbrains.kotlin.resolve.calls.util.BuilderLambdaLabelingInfo;
@@ -43,7 +41,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.jetbrains.kotlin.config.LanguageFeature.ReportTypeVarianceConflictOnQualifierArguments;
import static org.jetbrains.kotlin.diagnostics.ClassicPositioningStrategies.ACTUAL_DECLARATION_NAME;
import static org.jetbrains.kotlin.diagnostics.ClassicPositioningStrategies.INCOMPATIBLE_DECLARATION;
import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.*;
@@ -339,6 +336,8 @@ public interface Errors {
DiagnosticFactory0<KtAnnotationEntry> OPT_IN_MARKER_ON_OVERRIDE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> OPT_IN_MARKER_ON_OVERRIDE_WARNING = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<KtAnnotationEntry, String> SUBCLASS_OPT_IN_INAPPLICABLE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR);
@@ -180,6 +180,8 @@ public class DefaultErrorMessages {
MAP.put(OPT_IN_MARKER_ON_OVERRIDE, "Opt-in requirement marker annotation on override requires the same marker on base declaration");
MAP.put(OPT_IN_MARKER_ON_OVERRIDE_WARNING, "Opt-in requirement marker annotation on override makes no sense without the same marker on base declaration");
MAP.put(SUBCLASS_OPT_IN_INAPPLICABLE, "@SubclassOptInRequired is inapplicable on {0}", STRING);
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "{0}", STRING);
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "{0}", STRING);
@@ -8,14 +8,15 @@ package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
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.descriptors.annotations.KotlinTarget.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.BindingContext
@@ -47,6 +48,11 @@ class OptInMarkerDeclarationAnnotationChecker(private val module: ModuleDescript
.safeAs<ArrayValue>()?.value.orEmpty()
checkOptInUsage(annotationClasses, trace, entry)
}
OptInNames.SUBCLASS_OPT_IN_REQUIRED_FQ_NAME -> {
val annotationClass =
annotation.allValueArguments[OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS]
checkSubclassOptInUsage(annotated, listOfNotNull(annotationClass), trace, entry)
}
in OptInNames.REQUIRES_OPT_IN_FQ_NAMES -> {
hasOptIn = true
}
@@ -89,7 +95,53 @@ class OptInMarkerDeclarationAnnotationChecker(private val module: ModuleDescript
trace.report(Errors.OPT_IN_WITHOUT_ARGUMENTS.on(entry))
return
}
checkArgumentsAreMarkers(annotationClasses, trace, entry)
}
private fun checkSubclassOptInUsage(
annotated: KtAnnotated?,
annotationClasses: List<ConstantValue<*>>,
trace: BindingTrace,
entry: KtAnnotationEntry
) {
when (annotated) {
is KtAnnotatedExpression -> {
if (annotated.baseExpression is KtObjectLiteralExpression) {
trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "object"))
}
return
}
is KtClassOrObject -> {
val descriptor = trace[BindingContext.CLASS, annotated]
if (descriptor != null) {
val kind = descriptor.kind
if (kind == ClassKind.OBJECT || kind == ClassKind.ENUM_CLASS || kind == ClassKind.ANNOTATION_CLASS) {
trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, kind.toString()))
return
}
if (kind != ClassKind.ENUM_ENTRY) {
// ^ We don't report anything on enum entries because it's anyway inapplicable target
val modality = descriptor.modality
if (modality != Modality.ABSTRACT && modality != Modality.OPEN) {
trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "$modality $kind"))
return
}
if (descriptor.isFun) {
trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "fun interface"))
return
}
if (annotated.isLocal) {
trace.report(Errors.SUBCLASS_OPT_IN_INAPPLICABLE.on(entry, "local $kind"))
return
}
}
}
}
}
checkArgumentsAreMarkers(annotationClasses, trace, entry)
}
private fun checkArgumentsAreMarkers(annotationClasses: List<ConstantValue<*>>, trace: BindingTrace, entry: KtAnnotationEntry) {
for (annotationClass in annotationClasses) {
val classDescriptor =
(annotationClass as? KClassValue)?.getArgumentType(module)?.constructor?.declarationDescriptor as? ClassDescriptor