Skip to content

Commit 2d0815c

Browse files
BlondeHexSpace Team
authored and
Space Team
committed
Make @SubclassOptInRequired accept multiple experimental markers
[1/5] Prepare frontend checkers to support SubclassOptInRequired arguments from different stdlib versions. These changes are required first to ensure that tests continue to work while updating the SubclassOptInRequired annotation in the stdlib. Support for K1 checkers is necessary for the Kotlin IDE plugin, as it uses on the old frontend. ^KT-70562 in progress
1 parent 68c47c7 commit 2d0815c

File tree

5 files changed

+80
-55
lines changed

5 files changed

+80
-55
lines changed

compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInAnnotationCallChecker.kt

+6-5
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
1414
import org.jetbrains.kotlin.diagnostics.reportOn
1515
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
1616
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
17-
import org.jetbrains.kotlin.fir.analysis.checkers.extractClassFromArgument
1817
import org.jetbrains.kotlin.fir.analysis.checkers.extractClassesFromArgument
1918
import org.jetbrains.kotlin.fir.analysis.checkers.modality
2019
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
2120
import org.jetbrains.kotlin.fir.declarations.FirClass
2221
import org.jetbrains.kotlin.fir.declarations.findArgumentByName
2322
import org.jetbrains.kotlin.fir.declarations.utils.isFun
2423
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
25-
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
26-
import org.jetbrains.kotlin.fir.expressions.arguments
24+
import org.jetbrains.kotlin.fir.expressions.*
2725
import org.jetbrains.kotlin.fir.languageVersionSettings
2826
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
2927
import org.jetbrains.kotlin.fir.types.classLikeLookupTagIfAny
@@ -82,8 +80,11 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker(MppCheckerKind.C
8280
return
8381
}
8482
}
85-
val classSymbol = expression.findArgumentByName(OPT_IN_ANNOTATION_CLASS)?.extractClassFromArgument(context.session) ?: return
86-
checkOptInArgumentIsMarker(classSymbol, classId, expression.source, reporter, context)
83+
val classSymbols = expression.findArgumentByName(OPT_IN_ANNOTATION_CLASS)?.extractClassesFromArgument(context.session).orEmpty()
84+
85+
classSymbols.forEach {
86+
checkOptInArgumentIsMarker(it, classId, expression.source, reporter, context)
87+
}
8788
}
8889
}
8990

compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirOptInUsageBaseChecker.kt

+22-20
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object FirOptInUsageBaseChecker {
4848
val severity: Severity,
4949
val message: String?,
5050
val supertypeName: String? = null,
51-
val fromSupertype: Boolean = false
51+
val fromSupertype: Boolean = false,
5252
) {
5353
enum class Severity { WARNING, ERROR }
5454
companion object {
@@ -77,7 +77,7 @@ object FirOptInUsageBaseChecker {
7777
// Note: receiver is an OptIn marker class and parameter is an annotated member owner class / self class name
7878
fun FirRegularClassSymbol.loadExperimentalityForMarkerAnnotation(
7979
session: FirSession,
80-
annotatedOwnerClassName: String? = null
80+
annotatedOwnerClassName: String? = null,
8181
): Experimentality? {
8282
lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
8383
@OptIn(SymbolInternals::class)
@@ -93,7 +93,7 @@ object FirOptInUsageBaseChecker {
9393
private fun FirDeclaration.loadExperimentalitiesFromAnnotationTo(
9494
session: FirSession,
9595
result: MutableCollection<Experimentality>,
96-
fromSupertype: Boolean
96+
fromSupertype: Boolean,
9797
) {
9898
for (annotation in annotations) {
9999
val annotationType = annotation.annotationTypeRef.coneType as? ConeClassLikeType ?: continue
@@ -110,26 +110,28 @@ object FirOptInUsageBaseChecker {
110110
if (fromSupertype) {
111111
if (annotationType.lookupTag.classId == OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) {
112112
val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
113-
result.addIfNotNull(
114-
annotationClass.extractClassFromArgument(session)
115-
?.loadExperimentalityForMarkerAnnotation(session)?.copy(fromSupertype = true)
116-
)
113+
val classes = annotationClass.extractClassesFromArgument(session)
114+
classes.forEach { klass ->
115+
result.addIfNotNull(
116+
klass.loadExperimentalityForMarkerAnnotation(session)?.copy(fromSupertype = true)
117+
)
118+
}
117119
}
118120
}
119121
}
120122
}
121123

122124
fun loadExperimentalitiesFromTypeArguments(
123125
context: CheckerContext,
124-
typeArguments: List<FirTypeProjection>
126+
typeArguments: List<FirTypeProjection>,
125127
): Set<Experimentality> {
126128
if (typeArguments.isEmpty()) return emptySet()
127129
return loadExperimentalitiesFromConeArguments(context, typeArguments.map { it.toConeTypeProjection() })
128130
}
129131

130132
fun loadExperimentalitiesFromConeArguments(
131133
context: CheckerContext,
132-
typeArguments: List<ConeTypeProjection>
134+
typeArguments: List<ConeTypeProjection>,
133135
): Set<Experimentality> {
134136
if (typeArguments.isEmpty()) return emptySet()
135137
val result = SmartSet.create<Experimentality>()
@@ -140,7 +142,7 @@ object FirOptInUsageBaseChecker {
140142
}
141143

142144
fun FirBasedSymbol<*>.loadExperimentalities(
143-
context: CheckerContext, fromSetter: Boolean, dispatchReceiverType: ConeKotlinType?
145+
context: CheckerContext, fromSetter: Boolean, dispatchReceiverType: ConeKotlinType?,
144146
): Set<Experimentality> = loadExperimentalities(
145147
context, knownExperimentalities = null, visited = mutableSetOf(), fromSetter, dispatchReceiverType, fromSupertype = false
146148
)
@@ -199,7 +201,7 @@ object FirOptInUsageBaseChecker {
199201
visited: MutableSet<FirDeclaration>,
200202
fromSetter: Boolean,
201203
dispatchReceiverType: ConeKotlinType?,
202-
result: SmartSet<Experimentality>
204+
result: SmartSet<Experimentality>,
203205
) {
204206
val parentClassSymbol = containingClassLookupTag()?.toRegularClassSymbol(context.session)
205207
if (this is FirConstructor) {
@@ -242,7 +244,7 @@ object FirOptInUsageBaseChecker {
242244
symbol: FirBasedSymbol<*>,
243245
context: CheckerContext,
244246
visited: MutableSet<FirDeclaration>,
245-
result: SmartSet<Experimentality>
247+
result: SmartSet<Experimentality>,
246248
) {
247249
when (this) {
248250
is FirRegularClass -> if (symbol is FirRegularClassSymbol) {
@@ -260,7 +262,7 @@ object FirOptInUsageBaseChecker {
260262
private fun ConeKotlinType?.addExperimentalities(
261263
context: CheckerContext,
262264
result: SmartSet<Experimentality>,
263-
visited: MutableSet<FirDeclaration> = mutableSetOf()
265+
visited: MutableSet<FirDeclaration> = mutableSetOf(),
264266
) {
265267
if (this !is ConeClassLikeType) return
266268
lookupTag.toSymbol(context.session)?.loadExperimentalities(
@@ -274,7 +276,7 @@ object FirOptInUsageBaseChecker {
274276
// Note: receiver is an OptIn marker class and parameter is an annotated member owner class / self class name
275277
private fun FirRegularClass.loadExperimentalityForMarkerAnnotation(
276278
session: FirSession,
277-
annotatedOwnerClassName: String? = null
279+
annotatedOwnerClassName: String? = null,
278280
): Experimentality? {
279281
val experimental = getAnnotationByClassId(OptInNames.REQUIRES_OPT_IN_CLASS_ID, session)
280282
?: return null
@@ -337,7 +339,7 @@ object FirOptInUsageBaseChecker {
337339
experimentalities: Collection<Experimentality>,
338340
symbol: FirCallableSymbol<*>,
339341
context: CheckerContext,
340-
reporter: DiagnosticReporter
342+
reporter: DiagnosticReporter,
341343
) {
342344
for ((annotationClassId, severity, markerMessage, supertypeName) in experimentalities) {
343345
if (!symbol.fir.isExperimentalityAcceptable(context.session, annotationClassId, fromSupertype = false) &&
@@ -361,7 +363,7 @@ object FirOptInUsageBaseChecker {
361363
private fun isExperimentalityAcceptableInContext(
362364
annotationClassId: ClassId,
363365
context: CheckerContext,
364-
fromSupertype: Boolean
366+
fromSupertype: Boolean,
365367
): Boolean {
366368
val languageVersionSettings = context.session.languageVersionSettings
367369
val fqNameAsString = annotationClassId.asFqNameString()
@@ -379,7 +381,7 @@ object FirOptInUsageBaseChecker {
379381
private fun FirAnnotationContainer.isExperimentalityAcceptable(
380382
session: FirSession,
381383
annotationClassId: ClassId,
382-
fromSupertype: Boolean
384+
fromSupertype: Boolean,
383385
): Boolean {
384386
return getAnnotationByClassId(annotationClassId, session) != null ||
385387
isAnnotatedWithOptIn(annotationClassId, session) ||
@@ -396,7 +398,7 @@ object FirOptInUsageBaseChecker {
396398
@OptIn(SymbolInternals::class)
397399
private fun FirAnnotationContainer.primaryConstructorParameterIsExperimentalityAcceptable(
398400
session: FirSession,
399-
annotationClassId: ClassId
401+
annotationClassId: ClassId,
400402
): Boolean {
401403
if (this !is FirProperty) return false
402404
val parameterSymbol = correspondingValueParameterFromPrimaryConstructor ?: return false
@@ -420,15 +422,15 @@ object FirOptInUsageBaseChecker {
420422

421423
private fun FirAnnotationContainer.isAnnotatedWithSubclassOptInRequired(
422424
session: FirSession,
423-
annotationClassId: ClassId
425+
annotationClassId: ClassId,
424426
): Boolean {
425427
for (annotation in annotations) {
426428
val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType
427429
if (coneType?.lookupTag?.classId != OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) {
428430
continue
429431
}
430432
val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
431-
if (annotationClass.extractClassFromArgument(session)?.classId == annotationClassId) {
433+
if (annotationClass.extractClassesFromArgument(session).any { it.classId == annotationClassId }) {
432434
return true
433435
}
434436
}

compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInMarkerDeclarationAnnotationChecker.kt

+3-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
2121
import org.jetbrains.kotlin.resolve.AnnotationChecker
2222
import org.jetbrains.kotlin.resolve.BindingContext
2323
import org.jetbrains.kotlin.resolve.BindingTrace
24-
import org.jetbrains.kotlin.resolve.constants.ArrayValue
2524
import org.jetbrains.kotlin.resolve.constants.ConstantValue
2625
import org.jetbrains.kotlin.resolve.constants.KClassValue
2726
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
@@ -42,14 +41,12 @@ class OptInMarkerDeclarationAnnotationChecker(private val module: ModuleDescript
4241
val annotation = trace.bindingContext.get(BindingContext.ANNOTATION, entry) ?: continue
4342
when (annotation.fqName) {
4443
OptInNames.OPT_IN_FQ_NAME -> {
45-
val annotationClasses = (annotation.allValueArguments[OptInNames.OPT_IN_ANNOTATION_CLASS] as? ArrayValue)
46-
?.value.orEmpty()
44+
val annotationClasses = getOptInAnnotationArgs(annotation)
4745
checkOptInUsage(annotationClasses, trace, entry)
4846
}
4947
OptInNames.SUBCLASS_OPT_IN_REQUIRED_FQ_NAME -> {
50-
val annotationClass =
51-
annotation.allValueArguments[OptInNames.OPT_IN_ANNOTATION_CLASS]
52-
checkSubclassOptInUsage(annotated, listOfNotNull(annotationClass), trace, entry)
48+
val annotationClasses = getOptInAnnotationArgs(annotation)
49+
checkSubclassOptInUsage(annotated, annotationClasses, trace, entry)
5350
}
5451
OptInNames.REQUIRES_OPT_IN_FQ_NAME -> {
5552
hasOptIn = true

compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/OptInUsageChecker.kt

+21-24
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,13 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
3939
import org.jetbrains.kotlin.resolve.checkers.OptInNames.OPT_IN_FQ_NAME
4040
import org.jetbrains.kotlin.resolve.checkers.OptInNames.REQUIRES_OPT_IN_FQ_NAME
4141
import org.jetbrains.kotlin.resolve.checkers.OptInNames.SUBCLASS_OPT_IN_REQUIRED_FQ_NAME
42-
import org.jetbrains.kotlin.resolve.checkers.OptInNames.OPT_IN_ANNOTATION_CLASS
4342
import org.jetbrains.kotlin.resolve.checkers.OptInNames.WAS_EXPERIMENTAL_FQ_NAME
44-
import org.jetbrains.kotlin.resolve.constants.ArrayValue
45-
import org.jetbrains.kotlin.resolve.constants.EnumValue
46-
import org.jetbrains.kotlin.resolve.constants.KClassValue
47-
import org.jetbrains.kotlin.resolve.constants.StringValue
43+
import org.jetbrains.kotlin.resolve.constants.*
4844
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
4945
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
5046
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
5147
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
5248
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
53-
import org.jetbrains.kotlin.resolve.descriptorUtil.module
5449
import org.jetbrains.kotlin.resolve.sam.SamConstructorDescriptor
5550
import org.jetbrains.kotlin.storage.LockBasedStorageManager
5651
import org.jetbrains.kotlin.types.AbbreviatedType
@@ -224,10 +219,12 @@ class OptInUsageChecker : CallChecker {
224219
}
225220

226221
for (annotation in annotations) {
227-
result.addIfNotNull(
228-
annotation.annotationClass?.loadOptInForMarkerAnnotation(useFutureError)
229-
?: if (fromSupertype) annotation.loadSubclassOptInRequired(module) else null
230-
)
222+
val optInMarker = annotation.annotationClass?.loadOptInForMarkerAnnotation(useFutureError)
223+
if (optInMarker != null) {
224+
result.addIfNotNull(optInMarker)
225+
} else if (fromSupertype) {
226+
result.addAll(annotation.loadSubclassOptInRequired(module))
227+
}
231228
}
232229

233230
if (this is CallableDescriptor && this !is ClassConstructorDescriptor) {
@@ -316,14 +313,14 @@ class OptInUsageChecker : CallChecker {
316313
return OptInDescription(fqNameSafe, severity, message, subclassesOnly)
317314
}
318315

319-
private fun AnnotationDescriptor.loadSubclassOptInRequired(module: ModuleDescriptor): OptInDescription? {
320-
if (this.fqName != SUBCLASS_OPT_IN_REQUIRED_FQ_NAME) return null
321-
val markerClass = allValueArguments[OPT_IN_ANNOTATION_CLASS]
322-
if (markerClass !is KClassValue) return null
323-
val value = markerClass.value
324-
if (value !is KClassValue.Value.NormalClass) return null
325-
val markerDescriptor = markerClass.getArgumentType(module).constructor.declarationDescriptor as? ClassDescriptor
326-
return markerDescriptor?.loadOptInForMarkerAnnotation(subclassesOnly = true)
316+
private fun AnnotationDescriptor.loadSubclassOptInRequired(module: ModuleDescriptor): List<OptInDescription> {
317+
if (this.fqName != SUBCLASS_OPT_IN_REQUIRED_FQ_NAME) return emptyList()
318+
val markerClasses = getOptInAnnotationArgs(this)
319+
return markerClasses.mapNotNull { constant ->
320+
val klass = constant as? KClassValue ?: return@mapNotNull null
321+
val markerDescriptor = klass.getArgumentType(module).constructor.declarationDescriptor as? ClassDescriptor
322+
markerDescriptor?.loadOptInForMarkerAnnotation(subclassesOnly = true)
323+
}
327324
}
328325

329326
private fun PsiElement.isOptInAllowed(annotationFqName: FqName, context: CheckerContext, subclassesOnly: Boolean): Boolean =
@@ -368,8 +365,8 @@ class OptInUsageChecker : CallChecker {
368365
return this is KtAnnotated && annotationEntries.any { entry ->
369366
val descriptor = bindingContext.get(BindingContext.ANNOTATION, entry)
370367
if (descriptor != null && descriptor.fqName == OPT_IN_FQ_NAME) {
371-
val annotationClasses = descriptor.allValueArguments[OPT_IN_ANNOTATION_CLASS]
372-
annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass ->
368+
val annotationClasses = getOptInAnnotationArgs(descriptor)
369+
annotationClasses.any { annotationClass ->
373370
annotationClass is KClassValue && annotationClass.value.let { value ->
374371
value is KClassValue.Value.NormalClass &&
375372
value.classId.asSingleFqName() == annotationFqName && value.arrayDimensions == 0
@@ -386,10 +383,10 @@ class OptInUsageChecker : CallChecker {
386383
return this is KtAnnotated && annotationEntries.any { entry ->
387384
val descriptor = bindingContext.get(BindingContext.ANNOTATION, entry)
388385
if (descriptor != null && descriptor.fqName == SUBCLASS_OPT_IN_REQUIRED_FQ_NAME) {
389-
val annotationClass = descriptor.allValueArguments[OPT_IN_ANNOTATION_CLASS]
390-
annotationClass is KClassValue && annotationClass.value.let { value ->
391-
value is KClassValue.Value.NormalClass &&
392-
value.classId.asSingleFqName() == annotationFqName && value.arrayDimensions == 0
386+
val annotationClasses = getOptInAnnotationArgs(descriptor)
387+
annotationClasses.any { constant ->
388+
val klass = constant.value as? KClassValue.Value.NormalClass ?: return false
389+
klass.classId.asSingleFqName() == annotationFqName && klass.arrayDimensions == 0
393390
}
394391
} else false
395392
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2010-2024 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.resolve.checkers
18+
19+
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
20+
import org.jetbrains.kotlin.resolve.constants.*
21+
22+
fun getOptInAnnotationArgs(annotation: AnnotationDescriptor): List<ConstantValue<*>> =
23+
when (val arguments = annotation.allValueArguments[OptInNames.OPT_IN_ANNOTATION_CLASS]) {
24+
// @SubclassOptInRequired from stdlib versions 2.1 and above
25+
is ArrayValue -> arguments.value
26+
// Applies @SubclassOptInRequired for stdlib versions below 2.1
27+
else -> listOfNotNull(arguments)
28+
}

0 commit comments

Comments
 (0)