diff --git a/compiler/fir/analysis-tests/testData/resolve/fromBuilder/enums.kt b/compiler/fir/analysis-tests/testData/resolve/fromBuilder/enums.kt index b651fd02343..739c7a263e3 100644 --- a/compiler/fir/analysis-tests/testData/resolve/fromBuilder/enums.kt +++ b/compiler/fir/analysis-tests/testData/resolve/fromBuilder/enums.kt @@ -23,7 +23,7 @@ enum class Planet(val m: Double, internal val r: Double) { } }; - val g: Double = G * m / (r * r) + val g: Double = G * m / (r * r) abstract fun sayHello() diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 5147d79bd10..ddc621c7582 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -30900,6 +30900,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/testsWithStdLib/elvisOnUnitInLet.kt"); } + @Test + @TestMetadata("enumEntryInitialization.kt") + public void testEnumEntryInitialization() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.kt"); + } + @Test @TestMetadata("exitProcess.kt") public void testExitProcess() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index db93536ccd6..0fed63446ff 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -19,10 +19,7 @@ import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.name.Name @@ -529,6 +526,12 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val UNINITIALIZED_VARIABLE by error { parameter("variable") } + val UNINITIALIZED_ENUM_ENTRY by error { + parameter>("enumEntry") + } + val UNINITIALIZED_ENUM_COMPANION by error(PositioningStrategy.REFERENCE_BY_QUALIFIED) { + parameter("enumClass") + } val VAL_REASSIGNMENT by error { parameter>("variable") } diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index d19827ce419..c33c7fc84db 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -19,12 +19,14 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.SourceElementPositioningStr import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirEnumEntry import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol import org.jetbrains.kotlin.fir.types.ConeKotlinType @@ -322,6 +324,8 @@ object FirErrors { // Control flow diagnostics val UNINITIALIZED_VARIABLE by error1() + val UNINITIALIZED_ENUM_ENTRY by error1>() + val UNINITIALIZED_ENUM_COMPANION by error1(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) val VAL_REASSIGNMENT by error1>() val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning1() val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error1() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 8befed43d24..18be2c6c75e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -21,11 +21,14 @@ import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyExpressionBlock import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.symbolProvider import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike import org.jetbrains.kotlin.fir.scopes.ProcessorAction import org.jetbrains.kotlin.fir.scopes.processOverriddenFunctions import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.types.* @@ -158,6 +161,16 @@ inline fun FirQualifiedAccessExpression.getDeclaration(): T? { fun FirSymbolOwner<*>.getContainingClass(context: CheckerContext): FirClassLikeDeclaration<*>? = this.safeAs>()?.containingClass()?.toSymbol(context.session)?.fir +fun FirClassLikeSymbol<*>.outerClass(context: CheckerContext): FirClassLikeSymbol<*>? { + if (this !is FirClassSymbol<*>) return null + val outerClassId = classId.outerClassId ?: return null + return context.session.symbolProvider.getClassLikeSymbolByFqName(outerClassId) +} + +fun FirClass<*>.outerClass(context: CheckerContext): FirClass<*>? { + return symbol.outerClass(context)?.fir as? FirClass<*> +} + /** * Returns the FirClassLikeDeclaration that the * sequence of FirTypeAlias'es points to starting diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt index 2997af2fd1a..2dc73dfd609 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt @@ -14,8 +14,10 @@ import org.jetbrains.kotlin.fir.analysis.checkers.modality import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn +import org.jetbrains.kotlin.fir.containingClassAttr import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor +import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLookupTagWithFixedSymbol import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.lexer.KtTokens @@ -175,6 +177,12 @@ internal fun FirRegularClass.isInlineOrValueClass(): Boolean { return isInline || hasModifier(KtTokens.VALUE_KEYWORD) } +internal val FirDeclaration.isEnumEntryInitializer: Boolean + get() { + if (this !is FirConstructor || !this.isPrimary) return false + return (containingClassAttr as? ConeClassLookupTagWithFixedSymbol)?.symbol?.fir?.classKind == ClassKind.ENUM_ENTRY + } + internal val FirMemberDeclaration.isLocalMember: Boolean get() = when (this) { is FirProperty -> this.isLocal diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUninitializedEnumChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUninitializedEnumChecker.kt new file mode 100644 index 00000000000..790ed059c73 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUninitializedEnumChecker.kt @@ -0,0 +1,227 @@ +/* + * 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.checkers.expression + +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.FirSymbolOwner +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.getContainingClass +import org.jetbrains.kotlin.fir.analysis.checkers.outerClass +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.expressions.FirLambdaArgumentExpression +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference + +object FirUninitializedEnumChecker : FirQualifiedAccessChecker() { + // Initialization order: member property initializers, enum entries, companion object (including members in it). + // + // When JVM loads a class, the corresponding class initializer, a.k.a. , is executed first. + // Kotlin (and Java as well) converts enum entries as static final field, which is initialized in that : + // enum class E(...) { + // E1, E2, ... + // } + // ~> + // class E { + // final static E1, E2, ... + // static { // + // E1 = new E(...) + // ... + // } + // } + // + // Note that, when initializing enum entries, now we call the enum class's constructor, a.k.a. , 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: + // enum class E(...) { + // E1, E2, ... + // val m1 = E1 + // } + // ~> + // class E { + // E m1 ... + // E(...) { // + // m1 = E1 + // } + // final static E1, E2, ... + // static { // + // E1 = new E(...) + // ... + // } + // } + // + // A companion object is desugared to a static final singleton, and initialized in too. However, enum lowering goes first, + // or in other words, companion object lowering goes last. Thus, any other things initialized in , including enum entries, + // should not have access to companion object and members in it. + // + // See related discussions: + // https://youtrack.jetbrains.com/issue/KT-6054 + // https://youtrack.jetbrains.com/issue/KT-11769 + override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { + val source = expression.source ?: return + if (source.kind is FirFakeSourceElementKind) return + + val reference = expression.calleeReference as? FirResolvedNamedReference ?: return + val calleeDeclaration = reference.resolvedSymbol.fir + val calleeContainingClass = calleeDeclaration.getContainingClass(context) as? FirRegularClass ?: return + // We're looking for members/entries/companion object in an enum class or members in companion object of an enum class. + val calleeIsInsideEnum = calleeContainingClass.isEnumClass + val calleeIsInsideEnumCompanion = + calleeContainingClass.isCompanion && (calleeContainingClass.outerClass(context) as? FirRegularClass)?.isEnumClass == true + if (!calleeIsInsideEnum && !calleeIsInsideEnumCompanion) return + + val enumClass = + if (calleeIsInsideEnum) calleeContainingClass + else calleeContainingClass.outerClass(context) as? FirRegularClass ?: return + + // An accessed context within the enum class of interest. We should look up until either enum members or enum entries are found, + // not just last containing declaration. For example, + // enum class Fruit(...) { + // APPLE(...); + // companion object { + // val common = ... + // } + // val score = ... common + // val score2 = { ... common }() + // } + // 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 accessedContext = context.containingDeclarations.lastOrNull { + // To not raise an error for an access from another enum class, e.g., + // enum class EnumCompanion4(...) { + // INSTANCE(EnumCompanion2.foo()) + // } + // find an accessed context within the same enum class. + (it as? FirSymbolOwner<*>)?.getContainingClass(context) == enumClass + } ?: return + + val enumMemberProperties = enumClass.declarations.filterIsInstance() + val enumEntries = enumClass.declarations.filterIsInstance() + + // When checking enum member properties, accesses to enum entries in lazy delegation is legitimate, e.g., + // enum JvmTarget(...) { + // JVM_1_6, ... + // val bytecodeVersion: ... by lazy { + // when (this) { + // JVM_1_6 -> ... + // } + // } + if (accessedContext in enumMemberProperties) { + val lazyDelegation = (accessedContext as FirProperty).lazyDelegation + if (lazyDelegation != null && lazyDelegation == context.containingDeclarations.lastOrNull()) { + return + } + } + + // When checking enum entries, only entry initializer matters. For example, + // enum class EnumCompanion(...) { + // ANOTHER { + // override fun bar() = foo() // At this point, companion object is initialized + // } + // abstract bar(): ... + // companion object { + // fun foo() = ... + // } + // } + if (accessedContext in enumEntries && context.containingDeclarations.lastOrNull()?.isEnumEntryInitializer != true) { + return + } + + // Members inside the companion object of an enum class + if (calleeContainingClass == enumClass.companionObject) { + // Uninitialized from the point of view of members or enum entries of that enum class + if (accessedContext in enumMemberProperties || accessedContext in enumEntries) { + if (calleeDeclaration is FirProperty) { + // From KT-11769 + // enum class Fruit(...) { + // APPLE(...); + // companion object { + // val common = ... + // } + // val score = ... common + // } + reporter.reportOn(source, FirErrors.UNINITIALIZED_VARIABLE, calleeDeclaration.symbol, context) + } else { + // enum class EnumCompanion2(...) { + // INSTANCE(foo()) + // companion object { + // fun foo() = ... + // } + // } + // Companion.foo() v.s. foo() + if ((expression.explicitReceiver as? FirResolvedQualifier)?.symbol?.fir == enumClass.companionObject) { + reporter.reportOn( + expression.explicitReceiver!!.source, + FirErrors.UNINITIALIZED_ENUM_COMPANION, + enumClass.symbol, + context + ) + } else { + reporter.reportOn( + expression.calleeReference.source, + FirErrors.UNINITIALIZED_ENUM_COMPANION, + enumClass.symbol, + context + ) + } + } + } + } + + // The enum entries of an enum class + if (calleeDeclaration in enumEntries) { + val calleeEnumEntry = calleeDeclaration as FirEnumEntry + // 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.symbol, context) + } + // enum class A(...) { + // A1(A2), + // A2(...), + // A3(A3) + // } + if (accessedContext in enumEntries) { + // 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: FirEnumEntry? = null + enumEntries.forEach { + if (precedingEntry != null) return@forEach + if (it == calleeEnumEntry || it == accessedContext) { + precedingEntry = it + } + } + if (precedingEntry == accessedContext) { + reporter.reportOn(source, FirErrors.UNINITIALIZED_ENUM_ENTRY, calleeEnumEntry.symbol, context) + } + } + } + } + + private val FirProperty.lazyDelegation: FirAnonymousFunction? + get() { + if (delegate == null || delegate !is FirFunctionCall) return null + val delegateCall = delegate as FirFunctionCall + val callee = + (delegateCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirSimpleFunction ?: return null + if (callee.symbol.callableId.asSingleFqName().asString() != "kotlin.lazy") return null + val lazyCallArgument = delegateCall.argumentList.arguments.singleOrNull() as? FirLambdaArgumentExpression ?: return null + return lazyCallArgument.expression as? FirAnonymousFunction + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 34763f77b66..747b0efe7ba 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -220,6 +220,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_IN import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_IS_NOT_AN_EXPRESSION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_ON_LHS_OF_DOT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNEXPECTED_SAFE_CALL +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_ENUM_COMPANION +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_ENUM_ENTRY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_VARIABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNNECESSARY_LATEINIT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNNECESSARY_NOT_NULL_ASSERTION @@ -722,6 +724,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { // Control flow diagnostics map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", VARIABLE_NAME) + map.put(UNINITIALIZED_ENUM_ENTRY, "Enum entry ''{0}'' is uninitialized here", VARIABLE_NAME) + map.put(UNINITIALIZED_ENUM_COMPANION, "Companion object of enum class ''{0}'' is uninitialized here", SYMBOL) map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", VARIABLE_NAME) map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD, "Reassignment of read-only property via backing field is deprecated", VARIABLE_NAME) map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR, "Reassignment of read-only property via backing field", VARIABLE_NAME) diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt index 7f5bba89e8b..290ae0a9743 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt @@ -27,6 +27,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { FirTypeArgumentsNotAllowedExpressionChecker, FirTypeParameterInQualifiedAccessChecker, FirSealedClassConstructorCallChecker, + FirUninitializedEnumChecker, ) override val functionCallCheckers: Set = setOf( diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.fir.kt index 79dc1230d06..58b2a34f3e1 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.fir.kt @@ -9,7 +9,7 @@ enum class B(val x: Int) { } enum class C(val x: Int) { - C1(SUM), + C1(SUM), C2(1); companion object { @@ -26,13 +26,14 @@ enum class Fruit(personal: Int) { val common = 20 } - val score = personal + common + val score = personal + common + val score2 = { personal + common }() } // Another example from KT-11769 enum class EnumCompanion1(val x: Int) { - INSTANCE(Companion.foo()), - ANOTHER(foo()); + INSTANCE(Companion.foo()), + ANOTHER(foo()); companion object { fun foo() = 42 @@ -40,7 +41,7 @@ enum class EnumCompanion1(val x: Int) { } // Also should be reported for implicit receiver enum class EnumCompanion2(val x: Int) { - INSTANCE(foo()); + INSTANCE(foo()); companion object { fun foo() = 42 @@ -53,3 +54,28 @@ enum class EnumCompanion3(val x: Int) { companion object } + +interface ExtractableCodeDescriptor { + fun isInterface(): Boolean +} + +enum class ExtractionTarget(val targetName: String) { + FUNCTION("function") { + override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true + }, + + LAZY_PROPERTY("lazy property") { + override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { + // Should not report UNINITIALIZED_ENUM_COMPANION + return checkNotTrait(descriptor) + } + }; + + abstract fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean + + companion object { + fun checkNotTrait(descriptor: ExtractableCodeDescriptor): Boolean { + return !descriptor.isInterface() + } + } +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.kt index 5e385f6333e..0bae914a844 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.kt @@ -27,6 +27,7 @@ enum class Fruit(personal: Int) { } val score = personal + common + val score2 = { personal + common }() } // Another example from KT-11769 @@ -53,3 +54,28 @@ enum class EnumCompanion3(val x: Int) { companion object } + +interface ExtractableCodeDescriptor { + fun isInterface(): Boolean +} + +enum class ExtractionTarget(val targetName: String) { + FUNCTION("function") { + override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true + }, + + LAZY_PROPERTY("lazy property") { + override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { + // Should not report UNINITIALIZED_ENUM_COMPANION + return checkNotTrait(descriptor) + } + }; + + abstract fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean + + companion object { + fun checkNotTrait(descriptor: ExtractableCodeDescriptor): Boolean { + return !descriptor.isInterface() + } + } +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.txt index d7bd881ae52..0757b7cb420 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.txt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumCompanionInterdependence.txt @@ -149,6 +149,44 @@ public final enum class EnumCompanion3 : kotlin.Enum { public final /*synthesized*/ fun values(): kotlin.Array } +public interface ExtractableCodeDescriptor { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract fun isInterface(): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final enum class ExtractionTarget : kotlin.Enum { + enum entry FUNCTION + + enum entry LAZY_PROPERTY + + private constructor ExtractionTarget(/*0*/ targetName: kotlin.String) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final val targetName: kotlin.String + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: ExtractionTarget): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract fun isAvailable(/*0*/ descriptor: ExtractableCodeDescriptor): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Companion { + private constructor Companion() + public final fun checkNotTrait(/*0*/ descriptor: ExtractableCodeDescriptor): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): ExtractionTarget + public final /*synthesized*/ fun values(): kotlin.Array +} + public final enum class Fruit : kotlin.Enum { enum entry APPLE @@ -156,6 +194,7 @@ public final enum class Fruit : kotlin.Enum { public final override /*1*/ /*fake_override*/ val name: kotlin.String public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int public final val score: kotlin.Int + public final val score2: kotlin.Int protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Fruit): kotlin.Int public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.fir.kt index 8c282117050..7662fab568e 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.fir.kt @@ -1,10 +1,11 @@ enum class A(val v: A) { - A1(A2), - A2(A1) + A1(A2), + A2(A1), + A3(A3) } enum class D(val x: Int) { - D1(D2.x), + D1(D2.x), D2(D1.x) } @@ -28,8 +29,8 @@ object Object1 { enum class MyEnum { A, B; val x = when(this) { - A -> 1 - B -> 2 + A -> 1 + B -> 2 else -> 3 } } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.kt index e7fcec1adb2..b206c116d4f 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.kt @@ -1,6 +1,7 @@ enum class A(val v: A) { A1(A2), - A2(A1) + A2(A1), + A3(A3) } enum class D(val x: Int) { diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.txt index 956ae2370c4..773e860c1f1 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.txt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/enumInterdependence.txt @@ -5,6 +5,8 @@ public final enum class A : kotlin.Enum { enum entry A2 + enum entry A3 + private constructor A(/*0*/ v: A) public final override /*1*/ /*fake_override*/ val name: kotlin.String public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int diff --git a/compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.kt b/compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.kt new file mode 100644 index 00000000000..86f9c97a3c2 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.kt @@ -0,0 +1,28 @@ +// FIR_IDENTICAL +enum class JvmTarget(val description: String) { + JVM_1_6("1.6"), + JVM_1_8("1.8"), + JVM_9("9"), + JVM_10("10"), + JVM_11("11"), + JVM_12("12"), + JVM_13("13"), + JVM_14("14"), + JVM_15("15"), + ; + + // Should not report UNINITIALIZED_ENUM_ENTRY + val bytecodeVersion: String by lazy { + when (this) { + JVM_1_6 -> "Opcodes.V1_6" + JVM_1_8 -> "Opcodes.V1_8" + JVM_9 -> "Opcodes.V9" + JVM_10 -> "Opcodes.V10" + JVM_11 -> "Opcodes.V11" + JVM_12 -> "Opcodes.V12" + JVM_13 -> "Opcodes.V12 + 1" + JVM_14 -> "Opcodes.V12 + 2" + JVM_15 -> "Opcodes.V12 + 3" + } + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.txt b/compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.txt new file mode 100644 index 00000000000..6ef485ec5b5 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.txt @@ -0,0 +1,38 @@ +package + +public final enum class JvmTarget : kotlin.Enum { + enum entry JVM_1_6 + + enum entry JVM_1_8 + + enum entry JVM_9 + + enum entry JVM_10 + + enum entry JVM_11 + + enum entry JVM_12 + + enum entry JVM_13 + + enum entry JVM_14 + + enum entry JVM_15 + + private constructor JvmTarget(/*0*/ description: kotlin.String) + public final val bytecodeVersion: kotlin.String + public final val description: kotlin.String + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: JvmTarget): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): JvmTarget + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index a3b52b148cb..8bd43250c13 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -30996,6 +30996,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/testsWithStdLib/elvisOnUnitInLet.kt"); } + @Test + @TestMetadata("enumEntryInitialization.kt") + public void testEnumEntryInitialization() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/enumEntryInitialization.kt"); + } + @Test @TestMetadata("exitProcess.kt") public void testExitProcess() throws Exception { diff --git a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt index 4cb373b7718..82b786964ff 100644 --- a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt +++ b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt @@ -18,10 +18,7 @@ import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.DiagnosticParamet import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.idea.frontend.api.symbols.* @@ -30,7 +27,6 @@ import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget -import org.jetbrains.kotlin.psi.KtParameter import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.createType @@ -147,6 +143,11 @@ private object FirToKtConversionCreator { KtClassLikeSymbol::class.createType(), importsToAdd = listOf("org.jetbrains.kotlin.fir.declarations.FirClass") ), + FirRegularClassSymbol::class to HLFunctionCallConversion( + "firSymbolBuilder.classifierBuilder.buildClassLikeSymbol({0}.fir)", + KtClassLikeSymbol::class.createType(), + importsToAdd = listOf("org.jetbrains.kotlin.fir.declarations.FirRegularClass") + ), FirMemberDeclaration::class to HLFunctionCallConversion( "firSymbolBuilder.buildSymbol({0} as FirDeclaration)", KtSymbol::class.createType(), @@ -171,7 +172,7 @@ private object FirToKtConversionCreator { KtType::class.createType() ), FirPropertySymbol::class to HLFunctionCallConversion( - "firSymbolBuilder.variableLikeBuilder.buildVariableSymbol({0}.fir as FirProperty)", + "firSymbolBuilder.variableLikeBuilder.buildVariableSymbol({0}.fir)", KtVariableSymbol::class.createType(), importsToAdd = listOf("org.jetbrains.kotlin.fir.declarations.FirProperty") ), diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index e837d2872e3..acf64580914 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirTypeParameter import org.jetbrains.kotlin.fir.declarations.FirVariable import org.jetbrains.kotlin.fir.psi @@ -1471,7 +1472,21 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert } add(FirErrors.UNINITIALIZED_VARIABLE) { firDiagnostic -> UninitializedVariableImpl( - firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty), + firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.UNINITIALIZED_ENUM_ENTRY) { firDiagnostic -> + UninitializedEnumEntryImpl( + firSymbolBuilder.variableLikeBuilder.buildVariableLikeSymbol(firDiagnostic.a.fir), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.UNINITIALIZED_ENUM_COMPANION) { firDiagnostic -> + UninitializedEnumCompanionImpl( + firSymbolBuilder.classifierBuilder.buildClassLikeSymbol(firDiagnostic.a.fir), firDiagnostic as FirPsiDiagnostic<*>, token, ) @@ -1485,14 +1500,14 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert } add(FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD) { firDiagnostic -> ValReassignmentViaBackingFieldImpl( - firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty), + firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir), firDiagnostic as FirPsiDiagnostic<*>, token, ) } add(FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR) { firDiagnostic -> ValReassignmentViaBackingFieldErrorImpl( - firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty), + firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir), firDiagnostic as FirPsiDiagnostic<*>, token, ) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 1f9ce10744b..ca2c02f4d7f 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -1044,6 +1044,16 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val variable: KtVariableSymbol } + abstract class UninitializedEnumEntry : KtFirDiagnostic() { + override val diagnosticClass get() = UninitializedEnumEntry::class + abstract val enumEntry: KtVariableLikeSymbol + } + + abstract class UninitializedEnumCompanion : KtFirDiagnostic() { + override val diagnosticClass get() = UninitializedEnumCompanion::class + abstract val enumClass: KtClassLikeSymbol + } + abstract class ValReassignment : KtFirDiagnostic() { override val diagnosticClass get() = ValReassignment::class abstract val variable: KtVariableLikeSymbol diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 8e52491ee4c..781349ceffb 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1687,6 +1687,22 @@ internal class UninitializedVariableImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class UninitializedEnumEntryImpl( + override val enumEntry: KtVariableLikeSymbol, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.UninitializedEnumEntry(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class UninitializedEnumCompanionImpl( + override val enumClass: KtClassLikeSymbol, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.UninitializedEnumCompanion(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ValReassignmentImpl( override val variable: KtVariableLikeSymbol, firDiagnostic: FirPsiDiagnostic<*>,