Make FE1.0/FIR OptIn override message more clear
This commit is contained in:
+44
-11
@@ -37,17 +37,41 @@ import org.jetbrains.kotlin.utils.SmartSet
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
object FirOptInUsageBaseChecker {
|
||||
data class Experimentality(val annotationClassId: ClassId, val severity: Severity, val message: String?) {
|
||||
data class Experimentality(
|
||||
val annotationClassId: ClassId,
|
||||
val severity: Severity,
|
||||
val message: String?,
|
||||
val supertypeName: String? = null
|
||||
) {
|
||||
enum class Severity { WARNING, ERROR }
|
||||
companion object {
|
||||
val DEFAULT_SEVERITY = Severity.ERROR
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Experimentality) return false
|
||||
|
||||
if (annotationClassId != other.annotationClassId) return false
|
||||
if (severity != other.severity) return false
|
||||
if (message != other.message) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = annotationClassId.hashCode()
|
||||
result = 31 * result + severity.hashCode()
|
||||
result = 31 * result + (message?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun FirRegularClassSymbol.loadExperimentalityForMarkerAnnotation(): Experimentality? {
|
||||
// Note: receiver is an OptIn marker class and parameter is an annotated member owner class / self class name
|
||||
fun FirRegularClassSymbol.loadExperimentalityForMarkerAnnotation(annotatedOwnerClassName: String? = null): Experimentality? {
|
||||
ensureResolved(FirResolvePhase.BODY_RESOLVE)
|
||||
@OptIn(SymbolInternals::class)
|
||||
return fir.loadExperimentalityForMarkerAnnotation()
|
||||
return fir.loadExperimentalityForMarkerAnnotation(annotatedOwnerClassName)
|
||||
}
|
||||
|
||||
fun FirBasedSymbol<*>.loadExperimentalitiesFromAnnotationTo(session: FirSession, result: MutableCollection<Experimentality>) {
|
||||
@@ -62,10 +86,15 @@ object FirOptInUsageBaseChecker {
|
||||
) {
|
||||
for (annotation in annotations) {
|
||||
val annotationType = annotation.annotationTypeRef.coneTypeSafe<ConeClassLikeType>()
|
||||
val className = when (this) {
|
||||
is FirRegularClass -> name.asString()
|
||||
is FirCallableDeclaration -> symbol.callableId.className?.shortName()?.asString()
|
||||
else -> null
|
||||
}
|
||||
result.addIfNotNull(
|
||||
annotationType?.lookupTag?.toFirRegularClassSymbol(
|
||||
session
|
||||
)?.loadExperimentalityForMarkerAnnotation()
|
||||
)?.loadExperimentalityForMarkerAnnotation(className)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -183,7 +212,8 @@ object FirOptInUsageBaseChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirRegularClass.loadExperimentalityForMarkerAnnotation(): Experimentality? {
|
||||
// Note: receiver is an OptIn marker class and parameter is an annotated member owner class / self class name
|
||||
private fun FirRegularClass.loadExperimentalityForMarkerAnnotation(annotatedOwnerClassName: String? = null): Experimentality? {
|
||||
val experimental = getAnnotationByClassId(OptInNames.REQUIRES_OPT_IN_CLASS_ID)
|
||||
?: return null
|
||||
|
||||
@@ -191,7 +221,7 @@ object FirOptInUsageBaseChecker {
|
||||
val levelName = levelArgument?.calleeReference?.resolved?.name?.asString()
|
||||
val level = OptInLevel.values().firstOrNull { it.name == levelName } ?: OptInLevel.DEFAULT
|
||||
val message = (experimental.findArgumentByName(MESSAGE) as? FirConstExpression<*>)?.value as? String
|
||||
return Experimentality(symbol.classId, level.severity, message)
|
||||
return Experimentality(symbol.classId, level.severity, message, annotatedOwnerClassName)
|
||||
}
|
||||
|
||||
fun reportNotAcceptedExperimentalities(
|
||||
@@ -221,7 +251,7 @@ object FirOptInUsageBaseChecker {
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
for ((annotationClassId, severity, _) in experimentalities) {
|
||||
for ((annotationClassId, severity, markerMessage, supertypeName) in experimentalities) {
|
||||
if (!symbol.fir.isExperimentalityAcceptable(annotationClassId) &&
|
||||
!isExperimentalityAcceptableInContext(annotationClassId, context)
|
||||
) {
|
||||
@@ -229,10 +259,13 @@ object FirOptInUsageBaseChecker {
|
||||
Experimentality.Severity.WARNING -> FirErrors.OPT_IN_OVERRIDE to "should"
|
||||
Experimentality.Severity.ERROR -> FirErrors.OPT_IN_OVERRIDE_ERROR to "must"
|
||||
}
|
||||
val reportedMessage = "This declaration overrides experimental member of supertype " +
|
||||
"'${symbol.callableId.className?.shortName()?.asString()}' and $verb be annotated " +
|
||||
"with '@${annotationClassId.asFqNameString()}'"
|
||||
reporter.reportOn(symbol.source, diagnostic, annotationClassId.asSingleFqName(), reportedMessage, context)
|
||||
val message = OptInNames.buildOverrideMessage(
|
||||
supertypeName ?: "???",
|
||||
markerMessage,
|
||||
verb,
|
||||
markerName = annotationClassId.asFqNameString()
|
||||
)
|
||||
reporter.reportOn(symbol.source, diagnostic, annotationClassId.asSingleFqName(), message, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -480,10 +480,12 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
Experimentality.Severity.ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must"
|
||||
Experimentality.Severity.FUTURE_ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must"
|
||||
}
|
||||
val message = experimentality.message?.takeIf { it.isNotBlank() }
|
||||
?: "This declaration overrides experimental member of supertype " +
|
||||
"'${member.containingDeclaration.name.asString()}' and $defaultMessageVerb be annotated " +
|
||||
"with '@${experimentality.annotationFqName.asString()}'"
|
||||
val message = OptInNames.buildOverrideMessage(
|
||||
supertypeName = member.containingDeclaration.name.asString(),
|
||||
markerMessage = experimentality.message,
|
||||
verb = defaultMessageVerb,
|
||||
markerName = experimentality.annotationFqName.asString()
|
||||
)
|
||||
context.trace.report(diagnostic.on(reportOn, experimentality.annotationFqName, message))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
warning: ATTENTION!
|
||||
This build uses in-dev FIR:
|
||||
-Xuse-fir
|
||||
compiler/testData/cli/jvm/optInEmptyMessage.kt:8:5: error: this declaration needs OptIn. Its usage must be marked with '@EmptyMarker' or '@OptIn(EmptyMarker::class)'
|
||||
compiler/testData/cli/jvm/optInEmptyMessageFir.kt:8:5: error: this declaration needs OptIn. Its usage must be marked with '@EmptyMarker' or '@OptIn(EmptyMarker::class)'
|
||||
foo()
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
$TESTDATA_DIR$/optInOverrideMessage.kt
|
||||
-d
|
||||
$TEMP_DIR$
|
||||
-opt-in=kotlin.RequiresOptIn
|
||||
@@ -0,0 +1,19 @@
|
||||
@RequiresOptIn(message = "This API is experimental and can change at any time, please use with care")
|
||||
annotation class Marker
|
||||
|
||||
@RequiresOptIn
|
||||
annotation class EmptyMarker
|
||||
|
||||
interface Base {
|
||||
@Marker
|
||||
fun foo()
|
||||
|
||||
@EmptyMarker
|
||||
fun bar()
|
||||
}
|
||||
|
||||
class Derived : Base {
|
||||
override fun foo() {}
|
||||
|
||||
override fun bar() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
compiler/testData/cli/jvm/optInOverrideMessage.kt:16:18: error: base declaration of supertype 'Base' needs OptIn. This API is experimental and can change at any time, please use with care. The declaration override must be annotated with '@Marker'
|
||||
override fun foo() {}
|
||||
^
|
||||
compiler/testData/cli/jvm/optInOverrideMessage.kt:18:18: error: base declaration of supertype 'Base' needs OptIn. The declaration override must be annotated with '@EmptyMarker'
|
||||
override fun bar() {}
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
@@ -0,0 +1,5 @@
|
||||
$TESTDATA_DIR$/optInOverrideMessageFir.kt
|
||||
-d
|
||||
$TEMP_DIR$
|
||||
-opt-in=kotlin.RequiresOptIn
|
||||
-Xuse-fir
|
||||
@@ -0,0 +1,19 @@
|
||||
@RequiresOptIn(message = "This API is experimental and can change at any time, please use with care")
|
||||
annotation class Marker
|
||||
|
||||
@RequiresOptIn
|
||||
annotation class EmptyMarker
|
||||
|
||||
interface Base {
|
||||
@Marker
|
||||
fun foo()
|
||||
|
||||
@EmptyMarker
|
||||
fun bar()
|
||||
}
|
||||
|
||||
class Derived : Base {
|
||||
override fun foo() {}
|
||||
|
||||
override fun bar() {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
warning: ATTENTION!
|
||||
This build uses in-dev FIR:
|
||||
-Xuse-fir
|
||||
compiler/testData/cli/jvm/optInOverrideMessageFir.kt:16:18: error: base declaration of supertype 'Base' needs OptIn. This API is experimental and can change at any time, please use with care. The declaration override must be annotated with '@Marker'
|
||||
override fun foo() {}
|
||||
^
|
||||
compiler/testData/cli/jvm/optInOverrideMessageFir.kt:18:18: error: base declaration of supertype 'Base' needs OptIn. The declaration override must be annotated with '@EmptyMarker'
|
||||
override fun bar() {}
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
@@ -809,6 +809,16 @@ public class CliTestGenerated extends AbstractCliTest {
|
||||
runTest("compiler/testData/cli/jvm/optInEmptyMessageFir.args");
|
||||
}
|
||||
|
||||
@TestMetadata("optInOverrideMessage.args")
|
||||
public void testOptInOverrideMessage() throws Exception {
|
||||
runTest("compiler/testData/cli/jvm/optInOverrideMessage.args");
|
||||
}
|
||||
|
||||
@TestMetadata("optInOverrideMessageFir.args")
|
||||
public void testOptInOverrideMessageFir() throws Exception {
|
||||
runTest("compiler/testData/cli/jvm/optInOverrideMessageFir.args");
|
||||
}
|
||||
|
||||
@TestMetadata("pluginSimple.args")
|
||||
public void testPluginSimple() throws Exception {
|
||||
runTest("compiler/testData/cli/jvm/pluginSimple.args");
|
||||
|
||||
@@ -45,4 +45,12 @@ object OptInNames {
|
||||
|
||||
fun buildMessagePrefix(verb: String): String =
|
||||
"This declaration needs OptIn. Its usage $verb be marked"
|
||||
|
||||
fun buildOverrideMessage(supertypeName: String, markerMessage: String?, verb: String, markerName: String): String {
|
||||
val basePrefix = "Base declaration of supertype '$supertypeName' needs OptIn. "
|
||||
val markerMessageOrStub = markerMessage
|
||||
?.takeIf { it.isNotBlank() }?.let { if (it.endsWith(".")) "$it " else "$it. " } ?: ""
|
||||
val baseSuffix = "The declaration override $verb be annotated with '@$markerName'"
|
||||
return basePrefix + markerMessageOrStub + baseSuffix
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user