K2 opt-in: fix reporting of WasExperimental-bound errors

This commit is contained in:
Mikhail Glukhikh
2022-11-22 11:08:54 +01:00
committed by Space Team
parent f62bdb0e3d
commit 3e22f6c052
5 changed files with 36 additions and 23 deletions
@@ -90,16 +90,22 @@ fun FirClassLikeSymbol<*>.getTargetAnnotation(): FirAnnotation? {
return getAnnotationByClassId(StandardClassIds.Annotations.Target)
}
fun FirExpression.extractClassesFromArgument(): List<FirRegularClassSymbol> {
fun FirExpression.extractClassesFromArgument(session: FirSession): List<FirRegularClassSymbol> {
return unfoldArrayOrVararg().mapNotNull {
it.extractClassFromArgument()
it.extractClassFromArgument(session)
}
}
fun FirExpression.extractClassFromArgument(): FirRegularClassSymbol? {
fun FirExpression.extractClassFromArgument(session: FirSession): FirRegularClassSymbol? {
if (this !is FirGetClassCall) return null
val qualifier = argument as? FirResolvedQualifier ?: return null
return qualifier.symbol as? FirRegularClassSymbol
return when (val argument = argument) {
is FirResolvedQualifier ->
argument.symbol as? FirRegularClassSymbol
is FirClassReferenceExpression ->
argument.classTypeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session)?.toRegularClassSymbol(session)
else ->
null
}
}
fun checkRepeatedAnnotation(
@@ -69,7 +69,7 @@ private fun FirDeclaration.getOwnSinceKotlinVersion(session: FirSession): FirSin
if (apiVersion != null) {
// TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version
if (result == null || apiVersion > result!!.apiVersion) {
result = FirSinceKotlinValue(apiVersion, loadWasExperimentalMarkerClasses())
result = FirSinceKotlinValue(apiVersion, loadWasExperimentalMarkerClasses(session))
}
}
}
@@ -96,9 +96,9 @@ private fun FirDeclaration.getOwnSinceKotlinVersion(session: FirSession): FirSin
return result
}
private fun FirDeclaration.loadWasExperimentalMarkerClasses(): List<FirRegularClassSymbol> {
private fun FirDeclaration.loadWasExperimentalMarkerClasses(session: FirSession): List<FirRegularClassSymbol> {
val wasExperimental = getAnnotationByClassId(OptInNames.WAS_EXPERIMENTAL_CLASS_ID) ?: return emptyList()
val annotationClasses = wasExperimental.findArgumentByName(OptInNames.WAS_EXPERIMENTAL_ANNOTATION_CLASS) ?: return emptyList()
return annotationClasses.extractClassesFromArgument()
return annotationClasses.extractClassesFromArgument(session)
}
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.fir.analysis.checkers.extractClassesFromArgument
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirOptInUsageBaseChecker.loadExperimentalityForMarkerAnnotation
import org.jetbrains.kotlin.fir.analysis.checkers.extractClassFromArgument
import org.jetbrains.kotlin.fir.analysis.checkers.modality
import org.jetbrains.kotlin.fir.declarations.FirClass
@@ -46,7 +45,7 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() {
reporter.reportOn(expression.source, FirErrors.OPT_IN_WITHOUT_ARGUMENTS, context)
} else {
val annotationClasses = expression.findArgumentByName(OPT_IN_ANNOTATION_CLASS)
for (classSymbol in annotationClasses?.extractClassesFromArgument().orEmpty()) {
for (classSymbol in annotationClasses?.extractClassesFromArgument(context.session).orEmpty()) {
checkOptInArgumentIsMarker(classSymbol, expression.source, reporter, context)
}
}
@@ -73,7 +72,7 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() {
return
}
}
val classSymbol = expression.findArgumentByName(OPT_IN_ANNOTATION_CLASS)?.extractClassFromArgument() ?: return
val classSymbol = expression.findArgumentByName(OPT_IN_ANNOTATION_CLASS)?.extractClassFromArgument(context.session) ?: return
checkOptInArgumentIsMarker(classSymbol, expression.source, reporter, context)
}
}
@@ -95,7 +95,8 @@ object FirOptInUsageBaseChecker {
if (annotationType.lookupTag.classId == OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) {
val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
result.addIfNotNull(
annotationClass.extractClassFromArgument()?.loadExperimentalityForMarkerAnnotation()?.copy(fromSupertype = true)
annotationClass.extractClassFromArgument(session)
?.loadExperimentalityForMarkerAnnotation()?.copy(fromSupertype = true)
)
}
}
@@ -256,7 +257,7 @@ object FirOptInUsageBaseChecker {
reporter: DiagnosticReporter
) {
for ((annotationClassId, severity, markerMessage, supertypeName) in experimentalities) {
if (!symbol.fir.isExperimentalityAcceptable(annotationClassId, fromSupertype = false) &&
if (!symbol.fir.isExperimentalityAcceptable(context.session, annotationClassId, fromSupertype = false) &&
!isExperimentalityAcceptableInContext(annotationClassId, context, fromSupertype = false)
) {
val (diagnostic, verb) = when (severity) {
@@ -285,40 +286,47 @@ object FirOptInUsageBaseChecker {
return true
}
for (annotationContainer in context.annotationContainers) {
if (annotationContainer.isExperimentalityAcceptable(annotationClassId, fromSupertype)) {
if (annotationContainer.isExperimentalityAcceptable(context.session, annotationClassId, fromSupertype)) {
return true
}
}
return false
}
private fun FirAnnotationContainer.isExperimentalityAcceptable(annotationClassId: ClassId, fromSupertype: Boolean): Boolean {
return getAnnotationByClassId(annotationClassId) != null || isAnnotatedWithOptIn(annotationClassId) ||
fromSupertype && isAnnotatedWithSubclassOptInRequired(annotationClassId)
private fun FirAnnotationContainer.isExperimentalityAcceptable(
session: FirSession,
annotationClassId: ClassId,
fromSupertype: Boolean
): Boolean {
return getAnnotationByClassId(annotationClassId) != null || isAnnotatedWithOptIn(session, annotationClassId) ||
fromSupertype && isAnnotatedWithSubclassOptInRequired(session, annotationClassId)
}
private fun FirAnnotationContainer.isAnnotatedWithOptIn(annotationClassId: ClassId): Boolean {
private fun FirAnnotationContainer.isAnnotatedWithOptIn(session: FirSession, annotationClassId: ClassId): Boolean {
for (annotation in annotations) {
val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType
if (coneType?.lookupTag?.classId != OptInNames.OPT_IN_CLASS_ID) {
continue
}
val annotationClasses = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
if (annotationClasses.extractClassesFromArgument().any { it.classId == annotationClassId }) {
if (annotationClasses.extractClassesFromArgument(session).any { it.classId == annotationClassId }) {
return true
}
}
return false
}
private fun FirAnnotationContainer.isAnnotatedWithSubclassOptInRequired(annotationClassId: ClassId): Boolean {
private fun FirAnnotationContainer.isAnnotatedWithSubclassOptInRequired(
session: FirSession,
annotationClassId: ClassId
): Boolean {
for (annotation in annotations) {
val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType
if (coneType?.lookupTag?.classId != OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) {
continue
}
val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
if (annotationClass.extractClassFromArgument()?.classId == annotationClassId) {
if (annotationClass.extractClassFromArgument(session)?.classId == annotationClassId) {
return true
}
}
@@ -9,6 +9,6 @@ data class Some(val duration: Duration = Duration.INFINITE)
fun foo(duration: Duration = Duration.INFINITE) {}
fun test() {
Some()
foo()
<!OPT_IN_USAGE_ERROR!>Some<!>()
<!OPT_IN_USAGE_ERROR!>foo<!>()
}