[FIR] Add INAPPLICABLE_JVM_FIELD, INAPPLICABLE_JVM_FIELD_WARNING

This commit is contained in:
Ivan Kochurkin
2021-09-08 16:05:27 +03:00
committed by TeamCityServer
parent e3805c9b98
commit 3725c58794
15 changed files with 247 additions and 120 deletions
@@ -110,4 +110,13 @@ object JVM_DIAGNOSTICS_LIST : DiagnosticList("FirJvmErrors") {
val EXTERNAL_DECLARATION_IN_INTERFACE by error<KtDeclaration>(PositioningStrategy.DECLARATION_SIGNATURE)
val EXTERNAL_DECLARATION_CANNOT_BE_INLINED by error<KtDeclaration>(PositioningStrategy.DECLARATION_SIGNATURE)
}
val INAPPLICABLE by object : DiagnosticGroup("Inapplicable") {
val INAPPLICABLE_JVM_FIELD by error<KtAnnotationEntry> {
parameter<String>("message")
}
val INAPPLICABLE_JVM_FIELD_WARNING by warning<KtAnnotationEntry> {
parameter<String>("message")
}
}
}
@@ -89,4 +89,8 @@ object FirJvmErrors {
val EXTERNAL_DECLARATION_IN_INTERFACE by error0<KtDeclaration>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
val EXTERNAL_DECLARATION_CANNOT_BE_INLINED by error0<KtDeclaration>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
// Inapplicable
val INAPPLICABLE_JVM_FIELD by error1<KtAnnotationEntry, String>()
val INAPPLICABLE_JVM_FIELD_WARNING by warning1<KtAnnotationEntry, String>()
}
@@ -34,6 +34,7 @@ object JvmDeclarationCheckers : DeclarationCheckers() {
override val propertyCheckers: Set<FirPropertyChecker>
get() = setOf(
FirVolatileAnnotationChecker,
FirJvmFieldApplicabilityChecker
)
override val functionCheckers: Set<FirFunctionChecker>
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.jvm.checkers.declaration
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirPropertyChecker
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.containingClass
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.getAnnotationByFqName
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.JvmFieldApplicabilityProblem.*
import org.jetbrains.kotlin.load.java.JvmAbi.JVM_FIELD_ANNOTATION_CLASS_ID
import org.jetbrains.kotlin.JvmNames.JVM_MULTIFILE_CLASS_SHORT
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.psi.stubs.elements.KtParameterElementType
object FirJvmFieldApplicabilityChecker : FirPropertyChecker() {
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
val annotation = declaration.getAnnotationByFqName(JVM_FIELD_ANNOTATION_FQ_NAME) ?: return
val session = context.session
val containingClass = declaration.containingClass()?.toFirRegularClass(session)
val problem = when {
declaration.delegate != null -> DELEGATE
!declaration.hasBackingField -> return
declaration.isOverridable(containingClass) -> NOT_FINAL
Visibilities.isPrivate(declaration.visibility) -> PRIVATE
declaration.hasCustomAccessor() -> CUSTOM_ACCESSOR
declaration.isOverride -> OVERRIDES
declaration.isLateInit -> LATEINIT
declaration.isConst -> CONST
containingClassSymbol != null && containingClassSymbol.isInsideCompanionObjectOfInterface(session) -> {
if (!session.languageVersionSettings.supportsFeature(LanguageFeature.JvmFieldInInterface)) {
INSIDE_COMPANION_OF_INTERFACE
} else {
if (!isInterfaceCompanionWithPublicJvmFieldProperties(containingClassSymbol)) {
NOT_PUBLIC_VAL_WITH_JVMFIELD
} else {
return
}
}
}
containingClass == null && isInsideJvmMultifileClassFile(context) ->
TOP_LEVEL_PROPERTY_OF_MULTIFILE_FACADE
declaration.returnTypeRef.isInlineClassThatRequiresMangling(session) -> RETURN_TYPE_IS_INLINE_CLASS
else -> return
}
val factory = if (declaration.fromPrimaryConstructor == true &&
!context.session.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitJvmFieldOnOverrideFromInterfaceInPrimaryConstructor)
) {
FirJvmErrors.INAPPLICABLE_JVM_FIELD_WARNING
} else {
FirJvmErrors.INAPPLICABLE_JVM_FIELD
}
reporter.reportOn(annotation.source, factory, problem.errorMessage, context)
}
private fun FirTypeRef.isInlineClassThatRequiresMangling(session: FirSession): Boolean {
val symbol = this.coneType.toRegularClassSymbol(session) ?: return false
return symbol.isInline && !symbol.isDontMangleClass()
}
private fun FirRegularClassSymbol.isDontMangleClass(): Boolean {
return this.classId.relativeClassName.asString() == StandardNames.RESULT_FQ_NAME.asString()
}
private fun FirProperty.isOverridable(containingClass: FirRegularClass?): Boolean {
return visibility != Visibilities.Private && modality != Modality.FINAL &&
containingClass?.isFinal != true
}
private fun FirProperty.hasCustomAccessor(): Boolean {
return (getter != null && getter?.source?.kind !is FirFakeSourceElementKind) ||
(setter != null && setter?.source?.kind !is FirFakeSourceElementKind)
}
private fun FirRegularClassSymbol.isInsideCompanionObjectOfInterface(session: FirSession): Boolean {
if (!isCompanion) {
return false
}
val outerClassKind = (getContainingDeclarationSymbol(session) as? FirRegularClass)?.classKind
return outerClassKind == ClassKind.INTERFACE || outerClassKind == ClassKind.ANNOTATION_CLASS
}
private fun isInterfaceCompanionWithPublicJvmFieldProperties(containingClass: FirRegularClassSymbol): Boolean {
for (symbol in containingClass.declarationSymbols) {
if (symbol !is FirPropertySymbol) continue
if (symbol.visibility != Visibilities.Public || symbol.isVar || symbol.modality != Modality.FINAL) {
return false
}
if (!symbol.hasJvmFieldAnnotation()) {
return false
}
}
return true
}
private fun FirPropertySymbol.hasJvmFieldAnnotation(): Boolean {
return getAnnotationByFqName(JVM_FIELD_ANNOTATION_FQ_NAME) != null
}
private fun isInsideJvmMultifileClassFile(context: CheckerContext): Boolean {
return context.findClosest<FirFile>()?.annotations?.any {
(it.calleeReference as? FirResolvedNamedReference)?.name?.asString() == JVM_MULTIFILE_CLASS_SHORT
} == true
}
}
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.FIELD_IN_J
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.ILLEGAL_JVM_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_FIELD
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_FIELD_WARNING
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INNER_JVM_RECORD
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JAVA_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_DEFAULT_IN_DECLARATION
@@ -164,6 +166,9 @@ object FirJvmDefaultErrorMessages {
map.put(EXTERNAL_DECLARATION_CANNOT_BE_ABSTRACT, "External declaration can not be abstract")
map.put(EXTERNAL_DECLARATION_CANNOT_HAVE_BODY, "External declaration can not have a body")
map.put(EXTERNAL_DECLARATION_CANNOT_BE_INLINED, "Inline functions can not be external")
map.put(INAPPLICABLE_JVM_FIELD, "{0}", STRING)
map.put(INAPPLICABLE_JVM_FIELD_WARNING, "{0}. This warning will become an error in further releases", STRING)
}
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.symbols.impl
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.types.*
@@ -1,13 +0,0 @@
@file:kotlin.jvm.JvmMultifileClass
@file:JvmName("SomeName")
@JvmField
val c = 4
@JvmField
var g = 5
class C {
@JvmField
var g = 5
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
@file:kotlin.jvm.JvmMultifileClass
@file:JvmName("SomeName")
@@ -1,59 +0,0 @@
// !LANGUAGE: +JvmFieldInInterface
interface A {
companion object {
@JvmField
val c = 3
}
}
interface B {
companion object {
@JvmField
val c = 3
@JvmField
val a = 3
}
}
interface C {
companion object {
@JvmField
val c = 3
val a = 3
}
}
interface D {
companion object {
@JvmField
var c = 3
}
}
interface E {
companion object {
@JvmField
private val a = 3
@JvmField
internal val b = 3
@JvmField
protected val c = 3
}
}
interface F {
companion object {
@JvmField
<!NON_FINAL_MEMBER_IN_OBJECT!>open<!> val a = 3
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +JvmFieldInInterface
interface A {
@@ -17,33 +17,33 @@ abstract class C : I{
<!WRONG_ANNOTATION_TARGET!>@kotlin.jvm.JvmField<!> private fun foo(s: String = "OK") {
}
<!WRONG_ANNOTATION_TARGET!>@JvmField<!> val a: String by lazy { "A" }
<!INAPPLICABLE_JVM_FIELD, WRONG_ANNOTATION_TARGET!>@JvmField<!> val a: String by lazy { "A" }
@JvmField open val b: Int = 3
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!> open val b: Int = 3
<!WRONG_ANNOTATION_TARGET!>@JvmField<!> abstract val c: Int
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val customGetter: String = ""
get() = field
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val explicitDefaultGetter: String = ""
get
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var explicitDefaultSetter: String = ""
set
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val explicitDefaultAnnotatedGetter: String = ""
@DemoAnnotation get
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var explicitDefaultAnnotatedSetter: String = ""
@DemoAnnotation set
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var customSetter: String = ""
set(s) {
field = s
@@ -53,10 +53,10 @@ abstract class C : I{
val noBackingField: String
get() = "a"
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
final override val ai = 3
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
private val private = 3
}
@@ -67,14 +67,14 @@ interface I {
}
class G {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
lateinit var lateInit: String
@delegate:JvmField
<!INAPPLICABLE_JVM_FIELD!>@delegate:JvmField<!>
val s: String by lazy { "s" }
}
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
const val Const = 4
@JvmField
@@ -93,7 +93,7 @@ interface K {
val j: Int
companion object {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var c = 3
var x = 3
@@ -101,27 +101,27 @@ interface K {
}
class KK : K {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override val i: Int = 0
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override final val j: Int = 0
}
open class KKK : K {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override val i: Int = 0
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override final val j: Int = 0
}
class JK(
override val i: Int,
@JvmField override val j: Int,
<!INAPPLICABLE_JVM_FIELD_WARNING!>@JvmField<!> override val j: Int,
) : K
annotation class L {
companion object {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var c = 3
}
}
@@ -131,18 +131,18 @@ object O {
val c = 3
}
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
private val private = 3
inline class Foo(val x: Int)
object IObject {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val c: Foo = Foo(42)
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val u = 42u
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
private val r: Result<Int> = TODO()
}
@@ -17,33 +17,33 @@ abstract class C : I{
<!WRONG_ANNOTATION_TARGET!>@kotlin.jvm.JvmField<!> private fun foo(s: String = "OK") {
}
<!WRONG_ANNOTATION_TARGET!>@JvmField<!> val a: String by lazy { "A" }
<!INAPPLICABLE_JVM_FIELD, WRONG_ANNOTATION_TARGET!>@JvmField<!> val a: String by lazy { "A" }
@JvmField open val b: Int = 3
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!> open val b: Int = 3
<!WRONG_ANNOTATION_TARGET!>@JvmField<!> abstract val c: Int
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val customGetter: String = ""
get() = field
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val explicitDefaultGetter: String = ""
get
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var explicitDefaultSetter: String = ""
set
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val explicitDefaultAnnotatedGetter: String = ""
@DemoAnnotation get
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var explicitDefaultAnnotatedSetter: String = ""
@DemoAnnotation set
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var customSetter: String = ""
set(s) {
field = s
@@ -53,10 +53,10 @@ abstract class C : I{
val noBackingField: String
get() = "a"
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
final override val ai = 3
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
private val private = 3
}
@@ -67,14 +67,14 @@ interface I {
}
class G {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
lateinit var lateInit: String
@delegate:JvmField
<!INAPPLICABLE_JVM_FIELD!>@delegate:JvmField<!>
val s: String by lazy { "s" }
}
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
const val Const = 4
@JvmField
@@ -93,7 +93,7 @@ interface K {
val j: Int
companion object {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var c = 3
var x = 3
@@ -101,27 +101,27 @@ interface K {
}
class KK : K {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override val i: Int = 0
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override final val j: Int = 0
}
open class KKK : K {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override val i: Int = 0
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
override final val j: Int = 0
}
class JK(
override val i: Int,
@JvmField override val j: Int,
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!> override val j: Int,
) : K
annotation class L {
companion object {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
var c = 3
}
}
@@ -131,18 +131,18 @@ object O {
val c = 3
}
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
private val private = 3
inline class Foo(val x: Int)
object IObject {
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val c: Foo = Foo(42)
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
val u = 42u
@JvmField
<!INAPPLICABLE_JVM_FIELD!>@JvmField<!>
private val r: Result<Int> = TODO()
}
@@ -3780,4 +3780,18 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJvmErrors.INAPPLICABLE_JVM_FIELD) { firDiagnostic ->
InapplicableJvmFieldImpl(
firDiagnostic.a,
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirJvmErrors.INAPPLICABLE_JVM_FIELD_WARNING) { firDiagnostic ->
InapplicableJvmFieldWarningImpl(
firDiagnostic.a,
firDiagnostic as FirPsiDiagnostic,
token,
)
}
}
@@ -2628,4 +2628,14 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = ExternalDeclarationCannotBeInlined::class
}
abstract class InapplicableJvmField : KtFirDiagnostic<KtAnnotationEntry>() {
override val diagnosticClass get() = InapplicableJvmField::class
abstract val message: String
}
abstract class InapplicableJvmFieldWarning : KtFirDiagnostic<KtAnnotationEntry>() {
override val diagnosticClass get() = InapplicableJvmFieldWarning::class
abstract val message: String
}
}
@@ -4273,3 +4273,19 @@ internal class ExternalDeclarationCannotBeInlinedImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class InapplicableJvmFieldImpl(
override val message: String,
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.InapplicableJvmField(), KtAbstractFirDiagnostic<KtAnnotationEntry> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class InapplicableJvmFieldWarningImpl(
override val message: String,
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.InapplicableJvmFieldWarning(), KtAbstractFirDiagnostic<KtAnnotationEntry> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}