[FIR] Fix reporting of UNINITIALIZED_ENUM_ENTRY in init blocks

^KT-41126 Fixed
This commit is contained in:
Dmitriy Novozhilov
2023-02-07 14:17:10 +02:00
committed by Space Team
parent c596c1ad73
commit a9248569a6
17 changed files with 135 additions and 110 deletions
@@ -2313,7 +2313,7 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val parameter: KtSymbol
}
abstract class UninitializedEnumEntry : KtFirDiagnostic<KtSimpleNameExpression>() {
abstract class UninitializedEnumEntry : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = UninitializedEnumEntry::class
abstract val enumEntry: KtSymbol
}
@@ -2793,7 +2793,7 @@ internal class UninitializedEnumEntryImpl(
override val enumEntry: KtSymbol,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.UninitializedEnumEntry(), KtAbstractFirDiagnostic<KtSimpleNameExpression>
) : KtFirDiagnostic.UninitializedEnumEntry(), KtAbstractFirDiagnostic<KtExpression>
internal class UninitializedEnumCompanionImpl(
override val enumClass: KtClassLikeSymbol,
@@ -1133,7 +1133,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val UNINITIALIZED_PARAMETER by error<KtSimpleNameExpression> {
parameter<FirValueParameterSymbol>("parameter")
}
val UNINITIALIZED_ENUM_ENTRY by error<KtSimpleNameExpression> {
val UNINITIALIZED_ENUM_ENTRY by error<KtExpression>(PositioningStrategy.REFERENCE_BY_QUALIFIED) {
parameter<FirEnumEntrySymbol>("enumEntry")
}
val UNINITIALIZED_ENUM_COMPANION by error<KtExpression>(PositioningStrategy.REFERENCE_BY_QUALIFIED) {
@@ -607,7 +607,7 @@ object FirErrors {
// Control flow diagnostics
val UNINITIALIZED_VARIABLE by error1<KtExpression, FirPropertySymbol>()
val UNINITIALIZED_PARAMETER by error1<KtSimpleNameExpression, FirValueParameterSymbol>()
val UNINITIALIZED_ENUM_ENTRY by error1<KtSimpleNameExpression, FirEnumEntrySymbol>()
val UNINITIALIZED_ENUM_ENTRY by error1<KtExpression, FirEnumEntrySymbol>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val UNINITIALIZED_ENUM_COMPANION by error1<KtExpression, FirRegularClassSymbol>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val VAL_REASSIGNMENT by error1<KtExpression, FirVariableSymbol<*>>(SourceElementPositioningStrategies.SELECTOR_BY_QUALIFIED)
val VAL_REASSIGNMENT_VIA_BACKING_FIELD by deprecationError1<KtExpression, FirBackingFieldSymbol>(RestrictionOfValReassignmentViaBackingField, SourceElementPositioningStrategies.SELECTOR_BY_QUALIFIED)
@@ -131,6 +131,7 @@ fun FirTypeRef.toRegularClassSymbol(session: FirSession): FirRegularClassSymbol?
fun FirBasedSymbol<*>.getContainingClassSymbol(session: FirSession): FirClassLikeSymbol<*>? = when (this) {
is FirCallableSymbol<*> -> containingClassLookupTag()?.toSymbol(session)
is FirClassLikeSymbol<*> -> getContainingClassLookupTag()?.toSymbol(session)
is FirAnonymousInitializerSymbol -> dispatchReceiverType?.toSymbol(session) as? FirClassLikeSymbol<*>
else -> null
}
@@ -83,12 +83,6 @@ internal fun FirMemberDeclaration.isEffectivelyExternal(
internal val FirClass.canHaveOpenMembers: Boolean get() = modality() != Modality.FINAL || classKind == ClassKind.ENUM_CLASS
internal val FirDeclaration.isEnumEntryInitializer: Boolean
get() {
if (this !is FirConstructor || !this.isPrimary) return false
return (containingClassForStaticMemberAttr as? ConeClassLookupTagWithFixedSymbol)?.symbol?.classKind == ClassKind.ENUM_ENTRY
}
// contract: returns(true) implies (this is FirMemberDeclaration<*>)
val FirDeclaration.isLocalMember: Boolean
get() = symbol.isLocalMember
@@ -6,25 +6,27 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.classKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isEnumEntryInitializer
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.outerClassSymbol
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.toResolvedBaseSymbol
import org.jetbrains.kotlin.fir.references.toResolvedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirEnumEntrySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.toSymbol
import org.jetbrains.kotlin.utils.addToStdlib.flatAssociateBy
object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
// Initialization order: member property initializers, enum entries, companion object (including members in it).
@@ -44,8 +46,8 @@ object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
// }
//
// Note that, when initializing enum entries, now we call the enum class's constructor, a.k.a. <init>, to initialize non-final
// instance members. Therefore, if there is a member property in the enum class, and if that member has an access to enum entries,
// that is an illegal access since enum entries are not yet initialized:
// instance members. Therefore, if there is a member property in the enum class, and if that member has access to enum entries,
// that is illegal access since enum entries are not yet initialized:
// enum class E(...) {
// E1, E2, ...
// val m1 = E1
@@ -99,19 +101,28 @@ object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
// companion object is not initialized for both member properties. The former has the property itself as the last containing
// declaration, whereas the latter has an anonymous function instead. For both cases, we're looking for member properties as an
// accessed context.
val isInsideCorrespondingEnum = context.containingDeclarations.any {
it.getContainingClassSymbol(context.session) == enumClassSymbol
}
if (!isInsideCorrespondingEnum) return
val declarationSymbols = enumClassSymbol.declarationSymbols
val enumMemberProperties = declarationSymbols.filterIsInstance<FirPropertySymbol>()
val enumInitBlocks = declarationSymbols.filterIsInstance<FirAnonymousInitializerSymbol>()
val enumEntries = declarationSymbols.filterIsInstance<FirEnumEntrySymbol>()
val enumEntriesInitBlocks = enumEntries.flatAssociateBy {
it.initializerObjectSymbol?.declarationSymbols?.filterIsInstance<FirAnonymousInitializerSymbol>().orEmpty()
}
val accessedContext = context.containingDeclarations.lastOrNull {
// To not raise an error for an access from another enum class, e.g.,
// To not raise an error for access from another enum class, e.g.,
// enum class EnumCompanion4(...) {
// INSTANCE(EnumCompanion2.foo())
// }
// find an accessed context within the same enum class.
it.getContainingClassSymbol(context.session) == enumClassSymbol
it.getContainingClassSymbol(context.session) == enumClassSymbol || it.symbol in enumEntriesInitBlocks
}?.symbol ?: return
val declarationSymbols = enumClassSymbol.declarationSymbols
val enumMemberProperties = declarationSymbols.filterIsInstance<FirPropertySymbol>()
val enumEntries = declarationSymbols.filterIsInstance<FirEnumEntrySymbol>()
// When checking enum member properties, accesses to enum entries in lazy delegation is legitimate, e.g.,
// enum JvmTarget(...) {
// JVM_1_6, ...
@@ -120,9 +131,10 @@ object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
// JVM_1_6 -> ...
// }
// }
val containingDeclarationForAccess = context.containingDeclarations.lastOrNull()
if (accessedContext in enumMemberProperties) {
val lazyDelegation = (accessedContext as FirPropertySymbol).lazyDelegation
if (lazyDelegation != null && lazyDelegation == context.containingDeclarations.lastOrNull()) {
if (lazyDelegation != null && lazyDelegation == containingDeclarationForAccess) {
return
}
}
@@ -137,7 +149,7 @@ object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
// fun foo() = ...
// }
// }
if (accessedContext in enumEntries && context.containingDeclarations.lastOrNull()?.isEnumEntryInitializer != true) {
if (accessedContext in enumEntries && containingDeclarationForAccess?.isEnumEntryInitializer(context.session) != true) {
return
}
@@ -163,36 +175,74 @@ object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
if (calleeSymbol in enumEntries) {
val calleeEnumEntry = calleeSymbol as FirEnumEntrySymbol
// Uninitialized from the point of view of members of that enum class
if (accessedContext in enumMemberProperties) {
// From KT-6054
// enum class MyEnum {
// A, B;
// val x = when(this) {
// <!>A<!> -> ...
// <!>B<!> -> ...
// }
// }
reporter.reportOn(source, FirErrors.UNINITIALIZED_ENUM_ENTRY, calleeEnumEntry, context)
}
// enum class A(...) {
// A1(<!>A2<!>),
// A2(...),
// A3(<!>A3<!>)
// }
if (accessedContext in enumEntries) {
fun reportIllegalAccessInEnumEntry(correspondingEnumEntry: FirEnumEntrySymbol) {
// Technically, this is equal to `enumEntries.indexOf(accessedContext) <= enumEntries.indexOf(calleeDeclaration)`.
// Instead of double `indexOf`, we can iterate entries just once until either one appears.
var precedingEntry: FirEnumEntrySymbol? = null
enumEntries.forEach {
if (precedingEntry != null) return@forEach
if (it == calleeEnumEntry || it == accessedContext) {
for (it in enumEntries) {
if (it == calleeEnumEntry || it == correspondingEnumEntry) {
precedingEntry = it
break
}
}
if (precedingEntry == accessedContext) {
if (precedingEntry == correspondingEnumEntry) {
reporter.reportOn(source, FirErrors.UNINITIALIZED_ENUM_ENTRY, calleeEnumEntry, context)
}
}
when (accessedContext) {
in enumMemberProperties,
in enumInitBlocks -> {
/* From KT-6054
* enum class MyEnum {
* A, B;
* val x = when(this) {
* <!>A<!> -> ...
* <!>B<!> -> ...
* }
* }
*/
reporter.reportOn(source, FirErrors.UNINITIALIZED_ENUM_ENTRY, calleeEnumEntry, context)
}
in enumEntries -> {
/*
* enum class A(...) {
* A1(<!>A2<!>),
* A2(...),
* A3(<!>A3<!>)
* }
*/
reportIllegalAccessInEnumEntry(accessedContext as FirEnumEntrySymbol)
}
in enumEntriesInitBlocks -> {
/*
* enum class MyEnum {
* A {
* init { A }
* },
* B;
* }
*/
val entrySymbol = enumEntriesInitBlocks.getValue(accessedContext as FirAnonymousInitializerSymbol)
/*
* In init block of entry this entry already initialized, so it's safe to access it
* enum class MyEnum {
* A {
* init {
* A // safe
* B // unsafe
* }
* },
* B
* }
*/
if (entrySymbol == calleeSymbol) return
reportIllegalAccessInEnumEntry(entrySymbol)
}
}
}
}
@@ -208,4 +258,18 @@ object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
val lazyCallArgument = delegateCall.argumentList.arguments.singleOrNull() as? FirLambdaArgumentExpression ?: return null
return (lazyCallArgument.expression as? FirAnonymousFunctionExpression)?.anonymousFunction
}
private fun FirDeclaration.isEnumEntryInitializer(session: FirSession): Boolean {
val containingClassSymbol = when (this) {
is FirConstructor -> {
if (!isPrimary) return false
(containingClassForStaticMemberAttr as? ConeClassLookupTagWithFixedSymbol)?.symbol
}
is FirAnonymousInitializer -> {
dispatchReceiverType?.toSymbol(session)
}
else -> null
} ?: return false
return containingClassSymbol.classKind == ClassKind.ENUM_ENTRY
}
}
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousInitializerSymbol
import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.visitors.*
/*
@@ -30,7 +30,7 @@ abstract class FirAnonymousInitializer : FirDeclaration(), FirControlFlowGraphOw
abstract override val controlFlowGraphReference: FirControlFlowGraphReference?
abstract val body: FirBlock?
abstract override val symbol: FirAnonymousInitializerSymbol
abstract val dispatchReceiverType: ConeSimpleKotlinType?
abstract val dispatchReceiverType: ConeClassLikeType?
override fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visitAnonymousInitializer(this, data)
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousInitializerSymbol
import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.visitors.*
/*
@@ -40,7 +40,7 @@ class FirAnonymousInitializerBuilder : FirDeclarationBuilder, FirAnnotationConta
override var attributes: FirDeclarationAttributes = FirDeclarationAttributes()
var body: FirBlock? = null
var symbol: FirAnonymousInitializerSymbol = FirAnonymousInitializerSymbol()
var dispatchReceiverType: ConeSimpleKotlinType? = null
var dispatchReceiverType: ConeClassLikeType? = null
override fun build(): FirAnonymousInitializer {
return FirAnonymousInitializerImpl(
@@ -17,7 +17,7 @@ import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousInitializerSymbol
import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.visitors.*
import org.jetbrains.kotlin.fir.MutableOrEmptyList
import org.jetbrains.kotlin.fir.builder.toMutableOrEmpty
@@ -36,7 +36,7 @@ internal class FirAnonymousInitializerImpl(
override val attributes: FirDeclarationAttributes,
override var body: FirBlock?,
override val symbol: FirAnonymousInitializerSymbol,
override val dispatchReceiverType: ConeSimpleKotlinType?,
override val dispatchReceiverType: ConeClassLikeType?,
) : FirAnonymousInitializer() {
override val annotations: List<FirAnnotation> get() = emptyList()
override var controlFlowGraphReference: FirControlFlowGraphReference? = null
@@ -7,7 +7,11 @@ package org.jetbrains.kotlin.fir.symbols.impl
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType
class FirAnonymousInitializerSymbol : FirBasedSymbol<FirAnonymousInitializer>() {
override fun toString(): String = "${this::class.simpleName} <init>"
val dispatchReceiverType: ConeSimpleKotlinType?
get() = fir.dispatchReceiverType
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.symbols.impl
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirAnonymousObjectExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
@@ -94,7 +95,10 @@ class FirFieldSymbol(callableId: CallableId) : FirVariableSymbol<FirField>(calla
get() = fir.isVar
}
class FirEnumEntrySymbol(callableId: CallableId) : FirVariableSymbol<FirEnumEntry>(callableId)
class FirEnumEntrySymbol(callableId: CallableId) : FirVariableSymbol<FirEnumEntry>(callableId) {
val initializerObjectSymbol: FirAnonymousObjectSymbol?
get() = (fir.initializer as? FirAnonymousObjectExpression)?.anonymousObject?.symbol
}
class FirValueParameterSymbol(name: Name) : FirVariableSymbol<FirValueParameter>(CallableId(name)) {
val hasDefaultValue: Boolean
@@ -453,7 +453,7 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
anonymousInitializer.configure {
+body(nullable = true, withReplace = true)
+symbol("FirAnonymousInitializerSymbol")
+field("dispatchReceiverType", coneSimpleKotlinTypeType, nullable = true)
+field("dispatchReceiverType", coneClassLikeTypeType, nullable = true)
}
danglingModifierList.configure {
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.fir.tree.generator.context.generatedType
import org.jetbrains.kotlin.fir.tree.generator.context.type
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeSimpleKotlinType
import org.jetbrains.kotlin.name.ClassId
@@ -44,6 +45,7 @@ val annotationUseSiteTargetType = type(AnnotationUseSiteTarget::class)
val operationKindType = type("fir.expressions", "LogicOperationKind")
val coneKotlinTypeType = type(ConeKotlinType::class)
val coneSimpleKotlinTypeType = type(ConeSimpleKotlinType::class)
val coneClassLikeTypeType = type(ConeClassLikeType::class)
val whenRefType = generatedType("", "FirExpressionRef<FirWhenExpression>")
val referenceToSimpleExpressionType = generatedType("", "FirExpressionRef<FirExpression>")
@@ -1,55 +0,0 @@
// LANGUAGE: +ProhibitQualifiedAccessToUninitializedEnumEntry
// ISSUE: KT-41124
enum class SomeEnum11(var x: Int) {
A(1),
B(2);
init {
A.x = 10 // Error
}
}
enum class SomeEnum12(var x: Int) {
A(1),
B(2);
init {
SomeEnum12.A.x = 10 // Error
}
}
enum class SomeEnum21(var x: Int) {
A(1) {
init {
A.x = 10 // OK
SomeEnum21.A.x = 10 // OK
B.x = 10 // Error
}
},
B(2)
}
enum class SomeEnum22(var x: Int) {
A(1) {
init {
A.x = 10 // OK
SomeEnum22.A.x = 10 // OK
SomeEnum22.B.x = 10 // Migration error
}
},
B(2)
}
enum class SomeEnum3(var x: Int) {
A(1),
B(2) {
init {
A.x = 10 // OK
SomeEnum3.A.x = 10 // OK
B.x = 10 // OK
SomeEnum3.B.x = 10 // OK
}
};
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// LANGUAGE: +ProhibitQualifiedAccessToUninitializedEnumEntry
// ISSUE: KT-41124
@@ -292,6 +292,16 @@ inline fun <T, U, K, V> List<T>.flatGroupBy(
return result
}
inline fun <T, K> List<T>.flatAssociateBy(selector: (T) -> Collection<K>): Map<K, T> {
return buildMap {
for (value in this@flatAssociateBy) {
for (key in selector(value)) {
put(key, value)
}
}
}
}
fun <E> MutableList<E>.popLast(): E = removeAt(lastIndex)
fun <K : Enum<K>, V> enumMapOf(vararg pairs: Pair<K, V>): EnumMap<K, V> = EnumMap(mapOf(*pairs))