[K2] Support reporting of EXPECTED_ENUM_ENTRY_WITH_BODY

Test for the case when enum entry has constructor call but doesn't have
body already exist in
`compiler/testData/diagnostics/tests/multiplatform/enum/constructorInHeaderEnum.kt`.

^KT-59978 Fixed
This commit is contained in:
Roman Efremov
2023-10-10 15:04:11 +02:00
committed by Space Team
parent db886a0435
commit e4ea733482
3 changed files with 37 additions and 3 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.diagnostics.getAncestors
import org.jetbrains.kotlin.diagnostics.nameIdentifier
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirEnumEntry
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.lexer.KtTokens
@@ -41,6 +42,20 @@ interface SourceNavigator {
fun FirTypeRef.isRedundantNullable(): Boolean
/**
* Returns whether this [FirEnumEntry] has a body in source, or `null` if the entry does not have a source.
*
* Returns `false` if entry has a constructor call, but doesn't have a body:
* ```kotlin
* enum class E(i: Int) { FOO(42) }
* ```
*
* We have to go down to source level, since this cannot be checked only by FIR element. This is because in FIR all enum entries
* with constructor calls have a fake [FirEnumEntry.initializer] with an anonymous object, regardless of whether the entry had
* body originally.
*/
fun FirEnumEntry.hasBody(): Boolean?
companion object {
private val lightTreeInstance = LightTreeSourceNavigator()
@@ -108,6 +123,12 @@ private open class LightTreeSourceNavigator : SourceNavigator {
parent?.let { parent = source.treeStructure.getParent(it) }
return parent
}
override fun FirEnumEntry.hasBody(): Boolean? {
val source = source ?: return null
val childNodes = source.lighterASTNode.getChildren(source.treeStructure)
return childNodes.any { it.tokenType == KtNodeTypes.CLASS_BODY }
}
}
//by default psi tree can reuse light tree manipulations
@@ -150,4 +171,9 @@ private object PsiSourceNavigator : LightTreeSourceNavigator() {
val typeElement = typeReference.typeElement as? KtNullableType ?: return false
return typeElement.innerType is KtNullableType
}
override fun FirEnumEntry.hasBody(): Boolean? {
val enumEntryPsi = source?.psi as? KtEnumEntry ?: return null
return enumEntryPsi.body != null
}
}
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator.Companion.withNavigator
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.valOrVarKeyword
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -48,6 +49,9 @@ object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
if (isProhibitedEnumConstructor(declaration, lastClass)) {
reporter.reportOn(source, FirErrors.EXPECTED_ENUM_CONSTRUCTOR, context)
}
if (isProhibitedEnumEntryWithBody(declaration)) {
reporter.reportOn(source, FirErrors.EXPECTED_ENUM_ENTRY_WITH_BODY, context)
}
if (isProhibitedPrivateDeclaration(declaration)) {
reporter.reportOn(source, FirErrors.EXPECTED_PRIVATE_DECLARATION, context)
@@ -94,4 +98,8 @@ object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
private fun isProhibitedDeclarationWithBody(declaration: FirMemberDeclaration): Boolean {
return declaration is FirFunction && declaration.hasBody
}
private fun isProhibitedEnumEntryWithBody(declaration: FirMemberDeclaration): Boolean {
return declaration is FirEnumEntry && declaration.withNavigator { declaration.hasBody() == true }
}
}
@@ -3,8 +3,8 @@
<!NO_ACTUAL_FOR_EXPECT!>expect enum class En {
E1,
E2 {
<!EXPECTED_ENUM_ENTRY_WITH_BODY!>E2 {
<!EXPECTED_DECLARATION_WITH_BODY!>fun foo()<!> = ""
},
E3 { };
},<!>
<!EXPECTED_ENUM_ENTRY_WITH_BODY!>E3 { };<!>
}<!>