diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 02740561bf7..7a7d539395a 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -5859,6 +5859,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt"); } + @Test + @TestMetadata("uninitializedQualifiedEnumEntry.kt") + public void testUninitializedQualifiedEnumEntry() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt"); + } + @Test @TestMetadata("unmappedArgs.kt") public void testUnmappedArgs() throws Exception { 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 379290d3726..8f81b3c3c1d 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 @@ -5859,6 +5859,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt"); } + @Test + @TestMetadata("uninitializedQualifiedEnumEntry.kt") + public void testUninitializedQualifiedEnumEntry() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt"); + } + @Test @TestMetadata("unmappedArgs.kt") public void testUnmappedArgs() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index dd5a4836930..00681c2e019 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -5859,6 +5859,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt"); } + @Test + @TestMetadata("uninitializedQualifiedEnumEntry.kt") + public void testUninitializedQualifiedEnumEntry() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt"); + } + @Test @TestMetadata("unmappedArgs.kt") public void testUnmappedArgs() throws Exception { diff --git a/compiler/frontend/cfg/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/cfg/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index e4c0d62210e..590f6490536 100644 --- a/compiler/frontend/cfg/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/cfg/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessTarget import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageFeature.ProhibitQualifiedAccessToUninitializedEnumEntry import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.contracts.description.canBeRevisited @@ -283,7 +284,12 @@ class ControlFlowProcessor( generateCall(resolvedCall.variableCall) } else { if (resolvedCall == null) { - val qualifier = trace.bindingContext[BindingContext.QUALIFIER, expression] + val qualifierExpression = + when (languageVersionSettings.supportsFeature(ProhibitQualifiedAccessToUninitializedEnumEntry)) { + true -> expression.getTopmostParentQualifiedExpressionForSelector() ?: expression + false -> expression + } + val qualifier = trace.bindingContext[BindingContext.QUALIFIER, qualifierExpression] if (qualifier != null && generateQualifier(expression, qualifier)) return } if (!generateCall(expression) && expression.parent !is KtCallExpression) { diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.fir.kt new file mode 100644 index 00000000000..06c90466cef --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.fir.kt @@ -0,0 +1,55 @@ +// 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 + } + }; +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt new file mode 100644 index 00000000000..6002cce1a2b --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt @@ -0,0 +1,55 @@ +// 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 + } + }; +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.txt new file mode 100644 index 00000000000..8bf0a53816d --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.txt @@ -0,0 +1,112 @@ +package + +public final enum class SomeEnum11 : kotlin.Enum { + enum entry A + + enum entry B + + private constructor SomeEnum11(/*0*/ x: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var x: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: SomeEnum11): 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): SomeEnum11 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class SomeEnum12 : kotlin.Enum { + enum entry A + + enum entry B + + private constructor SomeEnum12(/*0*/ x: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var x: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: SomeEnum12): 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): SomeEnum12 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class SomeEnum21 : kotlin.Enum { + enum entry A + + enum entry B + + private constructor SomeEnum21(/*0*/ x: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var x: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: SomeEnum21): 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): SomeEnum21 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class SomeEnum22 : kotlin.Enum { + enum entry A + + enum entry B + + private constructor SomeEnum22(/*0*/ x: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var x: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: SomeEnum22): 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): SomeEnum22 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class SomeEnum3 : kotlin.Enum { + enum entry A + + enum entry B + + private constructor SomeEnum3(/*0*/ x: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var x: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: SomeEnum3): 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): SomeEnum3 + 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 2d43c2e2808..0c4981aaccf 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 @@ -5865,6 +5865,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt"); } + @Test + @TestMetadata("uninitializedQualifiedEnumEntry.kt") + public void testUninitializedQualifiedEnumEntry() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedQualifiedEnumEntry.kt"); + } + @Test @TestMetadata("unmappedArgs.kt") public void testUnmappedArgs() throws Exception { diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 3122b0e1535..69a936b9f61 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -244,6 +244,7 @@ enum class LanguageFeature( QualifiedSupertypeMayBeExtendedByOtherSupertype(KOTLIN_1_7), YieldIsNoMoreReserved(KOTLIN_1_7), NoDeprecationOnDeprecatedEnumEntries(KOTLIN_1_7), // KT-37975 + ProhibitQualifiedAccessToUninitializedEnumEntry(KOTLIN_1_7, kind = BUG_FIX), // KT-41124 // 1.8