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) return getAnnotationByClassId(StandardClassIds.Annotations.Target)
} }
fun FirExpression.extractClassesFromArgument(): List<FirRegularClassSymbol> { fun FirExpression.extractClassesFromArgument(session: FirSession): List<FirRegularClassSymbol> {
return unfoldArrayOrVararg().mapNotNull { return unfoldArrayOrVararg().mapNotNull {
it.extractClassFromArgument() it.extractClassFromArgument(session)
} }
} }
fun FirExpression.extractClassFromArgument(): FirRegularClassSymbol? { fun FirExpression.extractClassFromArgument(session: FirSession): FirRegularClassSymbol? {
if (this !is FirGetClassCall) return null if (this !is FirGetClassCall) return null
val qualifier = argument as? FirResolvedQualifier ?: return null return when (val argument = argument) {
return qualifier.symbol as? FirRegularClassSymbol is FirResolvedQualifier ->
argument.symbol as? FirRegularClassSymbol
is FirClassReferenceExpression ->
argument.classTypeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session)?.toRegularClassSymbol(session)
else ->
null
}
} }
fun checkRepeatedAnnotation( fun checkRepeatedAnnotation(
@@ -69,7 +69,7 @@ private fun FirDeclaration.getOwnSinceKotlinVersion(session: FirSession): FirSin
if (apiVersion != null) { if (apiVersion != null) {
// TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version // TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version
if (result == null || apiVersion > result!!.apiVersion) { 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 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 wasExperimental = getAnnotationByClassId(OptInNames.WAS_EXPERIMENTAL_CLASS_ID) ?: return emptyList()
val annotationClasses = wasExperimental.findArgumentByName(OptInNames.WAS_EXPERIMENTAL_ANNOTATION_CLASS) ?: 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.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn 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.extractClassFromArgument
import org.jetbrains.kotlin.fir.analysis.checkers.modality import org.jetbrains.kotlin.fir.analysis.checkers.modality
import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirClass
@@ -46,7 +45,7 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() {
reporter.reportOn(expression.source, FirErrors.OPT_IN_WITHOUT_ARGUMENTS, context) reporter.reportOn(expression.source, FirErrors.OPT_IN_WITHOUT_ARGUMENTS, context)
} else { } else {
val annotationClasses = expression.findArgumentByName(OPT_IN_ANNOTATION_CLASS) 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) checkOptInArgumentIsMarker(classSymbol, expression.source, reporter, context)
} }
} }
@@ -73,7 +72,7 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() {
return 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) checkOptInArgumentIsMarker(classSymbol, expression.source, reporter, context)
} }
} }
@@ -95,7 +95,8 @@ object FirOptInUsageBaseChecker {
if (annotationType.lookupTag.classId == OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) { if (annotationType.lookupTag.classId == OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) {
val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
result.addIfNotNull( result.addIfNotNull(
annotationClass.extractClassFromArgument()?.loadExperimentalityForMarkerAnnotation()?.copy(fromSupertype = true) annotationClass.extractClassFromArgument(session)
?.loadExperimentalityForMarkerAnnotation()?.copy(fromSupertype = true)
) )
} }
} }
@@ -256,7 +257,7 @@ object FirOptInUsageBaseChecker {
reporter: DiagnosticReporter reporter: DiagnosticReporter
) { ) {
for ((annotationClassId, severity, markerMessage, supertypeName) in experimentalities) { 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) !isExperimentalityAcceptableInContext(annotationClassId, context, fromSupertype = false)
) { ) {
val (diagnostic, verb) = when (severity) { val (diagnostic, verb) = when (severity) {
@@ -285,40 +286,47 @@ object FirOptInUsageBaseChecker {
return true return true
} }
for (annotationContainer in context.annotationContainers) { for (annotationContainer in context.annotationContainers) {
if (annotationContainer.isExperimentalityAcceptable(annotationClassId, fromSupertype)) { if (annotationContainer.isExperimentalityAcceptable(context.session, annotationClassId, fromSupertype)) {
return true return true
} }
} }
return false return false
} }
private fun FirAnnotationContainer.isExperimentalityAcceptable(annotationClassId: ClassId, fromSupertype: Boolean): Boolean { private fun FirAnnotationContainer.isExperimentalityAcceptable(
return getAnnotationByClassId(annotationClassId) != null || isAnnotatedWithOptIn(annotationClassId) || session: FirSession,
fromSupertype && isAnnotatedWithSubclassOptInRequired(annotationClassId) 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) { for (annotation in annotations) {
val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType
if (coneType?.lookupTag?.classId != OptInNames.OPT_IN_CLASS_ID) { if (coneType?.lookupTag?.classId != OptInNames.OPT_IN_CLASS_ID) {
continue continue
} }
val annotationClasses = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: 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 true
} }
} }
return false return false
} }
private fun FirAnnotationContainer.isAnnotatedWithSubclassOptInRequired(annotationClassId: ClassId): Boolean { private fun FirAnnotationContainer.isAnnotatedWithSubclassOptInRequired(
session: FirSession,
annotationClassId: ClassId
): Boolean {
for (annotation in annotations) { for (annotation in annotations) {
val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType val coneType = annotation.annotationTypeRef.coneType as? ConeClassLikeType
if (coneType?.lookupTag?.classId != OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) { if (coneType?.lookupTag?.classId != OptInNames.SUBCLASS_OPT_IN_REQUIRED_CLASS_ID) {
continue continue
} }
val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue val annotationClass = annotation.findArgumentByName(OptInNames.OPT_IN_ANNOTATION_CLASS) ?: continue
if (annotationClass.extractClassFromArgument()?.classId == annotationClassId) { if (annotationClass.extractClassFromArgument(session)?.classId == annotationClassId) {
return true return true
} }
} }
@@ -9,6 +9,6 @@ data class Some(val duration: Duration = Duration.INFINITE)
fun foo(duration: Duration = Duration.INFINITE) {} fun foo(duration: Duration = Duration.INFINITE) {}
fun test() { fun test() {
Some() <!OPT_IN_USAGE_ERROR!>Some<!>()
foo() <!OPT_IN_USAGE_ERROR!>foo<!>()
} }