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
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
object FirOptInUsageBaseChecker {
|
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 }
|
enum class Severity { WARNING, ERROR }
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT_SEVERITY = Severity.ERROR
|
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)
|
ensureResolved(FirResolvePhase.BODY_RESOLVE)
|
||||||
@OptIn(SymbolInternals::class)
|
@OptIn(SymbolInternals::class)
|
||||||
return fir.loadExperimentalityForMarkerAnnotation()
|
return fir.loadExperimentalityForMarkerAnnotation(annotatedOwnerClassName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FirBasedSymbol<*>.loadExperimentalitiesFromAnnotationTo(session: FirSession, result: MutableCollection<Experimentality>) {
|
fun FirBasedSymbol<*>.loadExperimentalitiesFromAnnotationTo(session: FirSession, result: MutableCollection<Experimentality>) {
|
||||||
@@ -62,10 +86,15 @@ object FirOptInUsageBaseChecker {
|
|||||||
) {
|
) {
|
||||||
for (annotation in annotations) {
|
for (annotation in annotations) {
|
||||||
val annotationType = annotation.annotationTypeRef.coneTypeSafe<ConeClassLikeType>()
|
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(
|
result.addIfNotNull(
|
||||||
annotationType?.lookupTag?.toFirRegularClassSymbol(
|
annotationType?.lookupTag?.toFirRegularClassSymbol(
|
||||||
session
|
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)
|
val experimental = getAnnotationByClassId(OptInNames.REQUIRES_OPT_IN_CLASS_ID)
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
@@ -191,7 +221,7 @@ object FirOptInUsageBaseChecker {
|
|||||||
val levelName = levelArgument?.calleeReference?.resolved?.name?.asString()
|
val levelName = levelArgument?.calleeReference?.resolved?.name?.asString()
|
||||||
val level = OptInLevel.values().firstOrNull { it.name == levelName } ?: OptInLevel.DEFAULT
|
val level = OptInLevel.values().firstOrNull { it.name == levelName } ?: OptInLevel.DEFAULT
|
||||||
val message = (experimental.findArgumentByName(MESSAGE) as? FirConstExpression<*>)?.value as? String
|
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(
|
fun reportNotAcceptedExperimentalities(
|
||||||
@@ -221,7 +251,7 @@ object FirOptInUsageBaseChecker {
|
|||||||
context: CheckerContext,
|
context: CheckerContext,
|
||||||
reporter: DiagnosticReporter
|
reporter: DiagnosticReporter
|
||||||
) {
|
) {
|
||||||
for ((annotationClassId, severity, _) in experimentalities) {
|
for ((annotationClassId, severity, markerMessage, supertypeName) in experimentalities) {
|
||||||
if (!symbol.fir.isExperimentalityAcceptable(annotationClassId) &&
|
if (!symbol.fir.isExperimentalityAcceptable(annotationClassId) &&
|
||||||
!isExperimentalityAcceptableInContext(annotationClassId, context)
|
!isExperimentalityAcceptableInContext(annotationClassId, context)
|
||||||
) {
|
) {
|
||||||
@@ -229,10 +259,13 @@ object FirOptInUsageBaseChecker {
|
|||||||
Experimentality.Severity.WARNING -> FirErrors.OPT_IN_OVERRIDE to "should"
|
Experimentality.Severity.WARNING -> FirErrors.OPT_IN_OVERRIDE to "should"
|
||||||
Experimentality.Severity.ERROR -> FirErrors.OPT_IN_OVERRIDE_ERROR to "must"
|
Experimentality.Severity.ERROR -> FirErrors.OPT_IN_OVERRIDE_ERROR to "must"
|
||||||
}
|
}
|
||||||
val reportedMessage = "This declaration overrides experimental member of supertype " +
|
val message = OptInNames.buildOverrideMessage(
|
||||||
"'${symbol.callableId.className?.shortName()?.asString()}' and $verb be annotated " +
|
supertypeName ?: "???",
|
||||||
"with '@${annotationClassId.asFqNameString()}'"
|
markerMessage,
|
||||||
reporter.reportOn(symbol.source, diagnostic, annotationClassId.asSingleFqName(), reportedMessage, context)
|
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.ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must"
|
||||||
Experimentality.Severity.FUTURE_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() }
|
val message = OptInNames.buildOverrideMessage(
|
||||||
?: "This declaration overrides experimental member of supertype " +
|
supertypeName = member.containingDeclaration.name.asString(),
|
||||||
"'${member.containingDeclaration.name.asString()}' and $defaultMessageVerb be annotated " +
|
markerMessage = experimentality.message,
|
||||||
"with '@${experimentality.annotationFqName.asString()}'"
|
verb = defaultMessageVerb,
|
||||||
|
markerName = experimentality.annotationFqName.asString()
|
||||||
|
)
|
||||||
context.trace.report(diagnostic.on(reportOn, experimentality.annotationFqName, message))
|
context.trace.report(diagnostic.on(reportOn, experimentality.annotationFqName, message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
warning: ATTENTION!
|
warning: ATTENTION!
|
||||||
This build uses in-dev FIR:
|
This build uses in-dev FIR:
|
||||||
-Xuse-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()
|
foo()
|
||||||
^
|
^
|
||||||
COMPILATION_ERROR
|
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");
|
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")
|
@TestMetadata("pluginSimple.args")
|
||||||
public void testPluginSimple() throws Exception {
|
public void testPluginSimple() throws Exception {
|
||||||
runTest("compiler/testData/cli/jvm/pluginSimple.args");
|
runTest("compiler/testData/cli/jvm/pluginSimple.args");
|
||||||
|
|||||||
@@ -45,4 +45,12 @@ object OptInNames {
|
|||||||
|
|
||||||
fun buildMessagePrefix(verb: String): String =
|
fun buildMessagePrefix(verb: String): String =
|
||||||
"This declaration needs OptIn. Its usage $verb be marked"
|
"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