[K2] Support reporting of SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS

...on regular classes and enum entries.

^KT-59979 Fixed
This commit is contained in:
Roman Efremov
2023-10-11 13:06:05 +02:00
committed by Space Team
parent 32a87836c2
commit a05b37c652
13 changed files with 96 additions and 9 deletions
@@ -2512,7 +2512,7 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = ExpectedLateinitProperty::class
}
interface SupertypeInitializedInExpectedClass : KtFirDiagnostic<PsiElement> {
interface SupertypeInitializedInExpectedClass : KtFirDiagnostic<KtElement> {
override val diagnosticClass get() = SupertypeInitializedInExpectedClass::class
}
@@ -3027,7 +3027,7 @@ internal class ExpectedLateinitPropertyImpl(
internal class SupertypeInitializedInExpectedClassImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.SupertypeInitializedInExpectedClass
) : KtAbstractFirDiagnostic<KtElement>(firDiagnostic, token), KtFirDiagnostic.SupertypeInitializedInExpectedClass
internal class ExpectedPrivateDeclarationImpl(
firDiagnostic: KtPsiDiagnostic,
@@ -1229,7 +1229,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
// TODO: need to cover `by` as well as delegate expression
val EXPECTED_DELEGATED_PROPERTY by error<KtExpression>()
val EXPECTED_LATEINIT_PROPERTY by error<KtModifierListOwner>(PositioningStrategy.LATEINIT_MODIFIER)
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS by error<PsiElement>()
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS by error<KtElement>(PositioningStrategy.SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC)
val EXPECTED_PRIVATE_DECLARATION by error<KtModifierListOwner>(PositioningStrategy.VISIBILITY_MODIFIER)
val EXPECTED_EXTERNAL_DECLARATION by error<KtModifierListOwner>(PositioningStrategy.EXTERNAL_MODIFIER)
val EXPECTED_TAILREC_FUNCTION by error<KtModifierListOwner>(PositioningStrategy.TAILREC_MODIFIER)
@@ -117,6 +117,7 @@ enum class PositioningStrategy(private val strategy: String? = null) {
CALL_ELEMENT_WITH_DOT,
EXPECT_ACTUAL_MODIFIER,
TYPEALIAS_TYPE_REFERENCE,
SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC,
;
val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}"
@@ -642,7 +642,7 @@ object FirErrors {
val EXPECTED_PROPERTY_INITIALIZER by error0<KtExpression>()
val EXPECTED_DELEGATED_PROPERTY by error0<KtExpression>()
val EXPECTED_LATEINIT_PROPERTY by error0<KtModifierListOwner>(SourceElementPositioningStrategies.LATEINIT_MODIFIER)
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS by error0<PsiElement>()
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS by error0<KtElement>(SourceElementPositioningStrategies.SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC)
val EXPECTED_PRIVATE_DECLARATION by error0<KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
val EXPECTED_EXTERNAL_DECLARATION by error0<KtModifierListOwner>(SourceElementPositioningStrategies.EXTERNAL_MODIFIER)
val EXPECTED_TAILREC_FUNCTION by error0<KtModifierListOwner>(SourceElementPositioningStrategies.TAILREC_MODIFIER)
@@ -56,6 +56,13 @@ interface SourceNavigator {
*/
fun FirEnumEntry.hasBody(): Boolean?
/**
* Returns whether this [FirEnumEntry] has an initializer in source, or `null` if the entry does not have a source.
*
* Reason of implementing this in [SourceNavigator] and not in FIR is same as in [hasBody] method.
*/
fun FirEnumEntry.hasInitializer(): Boolean?
companion object {
private val lightTreeInstance = LightTreeSourceNavigator()
@@ -129,6 +136,12 @@ private open class LightTreeSourceNavigator : SourceNavigator {
val childNodes = source.lighterASTNode.getChildren(source.treeStructure)
return childNodes.any { it.tokenType == KtNodeTypes.CLASS_BODY }
}
override fun FirEnumEntry.hasInitializer(): Boolean? {
val source = source ?: return null
val childNodes = source.lighterASTNode.getChildren(source.treeStructure)
return childNodes.any { it.tokenType == KtNodeTypes.INITIALIZER_LIST }
}
}
//by default psi tree can reuse light tree manipulations
@@ -176,4 +189,9 @@ private object PsiSourceNavigator : LightTreeSourceNavigator() {
val enumEntryPsi = source?.psi as? KtEnumEntry ?: return null
return enumEntryPsi.body != null
}
override fun FirEnumEntry.hasInitializer(): Boolean? {
val enumEntryPsi = source?.psi as? KtEnumEntry ?: return null
return enumEntryPsi.initializerList != null
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.expressions.FirDelegatedConstructorCall
import org.jetbrains.kotlin.fir.types.FirTypeRef
// See old FE's [DeclarationsChecker]
object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
@@ -43,6 +44,9 @@ object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
getConstructorDelegationCall(declaration)?.let { delegatedConstructor ->
reporter.reportOn(delegatedConstructor.source, FirErrors.EXPECTED_CLASS_CONSTRUCTOR_DELEGATION_CALL, context)
}
for (superTypeRef in getClassSuperTypeReferencesWithInitializers(declaration)) {
reporter.reportOn(superTypeRef.source, FirErrors.SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS, context)
}
for (propertyParameter in getConstructorProhibitedPropertyParameters(declaration, lastClass)) {
reporter.reportOn(propertyParameter.source, FirErrors.EXPECTED_CLASS_CONSTRUCTOR_PROPERTY_PARAMETER, context)
}
@@ -52,6 +56,9 @@ object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
if (isProhibitedEnumEntryWithBody(declaration)) {
reporter.reportOn(source, FirErrors.EXPECTED_ENUM_ENTRY_WITH_BODY, context)
}
if (isProhibitedEnumEntryWithInitializer(declaration)) {
reporter.reportOn(source, FirErrors.SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS, context)
}
if (isProhibitedPrivateDeclaration(declaration)) {
reporter.reportOn(source, FirErrors.EXPECTED_PRIVATE_DECLARATION, context)
@@ -87,6 +94,13 @@ object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
return null
}
private fun getClassSuperTypeReferencesWithInitializers(declaration: FirMemberDeclaration): List<FirTypeRef> {
if (declaration !is FirRegularClass) return emptyList()
return declaration.withNavigator {
declaration.superTypeRefs.filter { it.isInConstructorCallee() }
}
}
private fun isProhibitedPrivateDeclaration(declaration: FirMemberDeclaration): Boolean {
return declaration !is FirConstructor && declaration !is FirPropertyAccessor && Visibilities.isPrivate(declaration.visibility)
}
@@ -102,4 +116,8 @@ object FirExpectConsistencyChecker : FirBasicDeclarationChecker() {
private fun isProhibitedEnumEntryWithBody(declaration: FirMemberDeclaration): Boolean {
return declaration is FirEnumEntry && declaration.withNavigator { declaration.hasBody() == true }
}
private fun isProhibitedEnumEntryWithInitializer(declaration: FirMemberDeclaration): Boolean {
return declaration is FirEnumEntry && declaration.withNavigator { declaration.hasInitializer() == true }
}
}
@@ -1233,6 +1233,29 @@ object LightTreePositioningStrategies {
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
}
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
override fun mark(
node: LighterASTNode,
startOffset: Int,
endOffset: Int,
tree: FlyweightCapableTreeStructure<LighterASTNode>
): List<TextRange> {
val nodeToMark = when (node.tokenType) {
KtNodeTypes.ENUM_ENTRY -> {
tree.findChildByType(node, KtNodeTypes.INITIALIZER_LIST) ?: node
}
KtNodeTypes.TYPE_REFERENCE -> {
val valueArgList = node.getParentIfTypeIs(KtNodeTypes.CONSTRUCTOR_CALLEE, tree)
?.getParentIfTypeIs(KtNodeTypes.SUPER_TYPE_CALL_ENTRY, tree)
?.let { tree.findChildByType(it, KtNodeTypes.VALUE_ARGUMENT_LIST) }
valueArgList ?: node
}
else -> node
}
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
}
}
fun KtSourceElement.hasValOrVar(): Boolean =
@@ -1657,3 +1680,10 @@ private fun FlyweightCapableTreeStructure<LighterASTNode>.lastChild(node: Lighte
getChildren(node, childrenRef)
return childrenRef.get().lastOrNull { it != null }
}
private fun LighterASTNode.getParentIfTypeIs(
tokenType: IElementType,
tree: FlyweightCapableTreeStructure<LighterASTNode>,
): LighterASTNode? {
return tree.getParent(this)?.takeIf { it.tokenType == tokenType }
}
@@ -1046,6 +1046,21 @@ object PositioningStrategies {
}
}
@JvmField
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC = object : PositioningStrategy<KtElement>() {
override fun mark(element: KtElement): List<TextRange> {
val elementToMark = when (element) {
is KtEnumEntry -> element.initializerList ?: element
is KtTypeReference -> {
val superTypeCallEntry = (element.parent as? KtConstructorCalleeExpression)?.parent as? KtSuperTypeCallEntry
superTypeCallEntry?.valueArgumentList ?: element
}
else -> element
}
return markElement(elementToMark)
}
}
/**
* @param locateReferencedName whether to remove any nested parentheses while locating the reference element. This is useful for
* diagnostics on super and unresolved references. For example, with the following, only the part inside the parentheses should be
@@ -394,4 +394,9 @@ object SourceElementPositioningStrategies {
LightTreePositioningStrategies.TYPEALIAS_TYPE_REFERENCE,
PositioningStrategies.TYPEALIAS_TYPE_REFERENCE,
)
val SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC = SourceElementPositioningStrategy(
LightTreePositioningStrategies.SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC,
PositioningStrategies.SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS_DIAGNOSTIC,
)
}
@@ -7,7 +7,7 @@ open class Base {
open operator fun plus(b: Base) = Base()
}
expect open class Derived constructor() : Base() {
expect open class Derived constructor() : Base {
}
// MODULE: main()()(common)
@@ -3,12 +3,12 @@
<!NO_ACTUAL_FOR_EXPECT!>expect enum class En<!EXPECTED_ENUM_CONSTRUCTOR!>(x: Int)<!> {
E1,
E2(42),
E2<!SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS!>(42)<!>,
;
<!EXPECTED_ENUM_CONSTRUCTOR!>constructor(s: String)<!>
}<!>
<!NO_ACTUAL_FOR_EXPECT!>expect enum class En2 {
E1()
E1<!SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS!>()<!>
}<!>
@@ -7,9 +7,9 @@ interface J
expect class Foo : I, C, J
<!INCOMPATIBLE_MATCHING{JVM}, SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR, SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR!>expect class Bar : C()<!>
<!INCOMPATIBLE_MATCHING{JVM}, SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR, SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR!>expect class Bar : C<!SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS, SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS!>()<!><!>
expect class WithExplicitPrimaryConstructor() : C()
expect class WithExplicitPrimaryConstructor() : C<!SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS, SUPERTYPE_INITIALIZED_IN_EXPECTED_CLASS!>()<!>
// MODULE: m2-jvm()()(m1-common)
// FILE: jvm.kt