diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt index 78de5649d6c..607a63e952d 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt @@ -40,10 +40,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.plugin.GeneratedClassIndex import org.jetbrains.kotlin.fir.scopes.FirOverrideService import org.jetbrains.kotlin.fir.scopes.FirPlatformClassMapper import org.jetbrains.kotlin.fir.scopes.PlatformSpecificOverridabilityRules -import org.jetbrains.kotlin.fir.scopes.impl.FirDeclaredMemberScopeProvider -import org.jetbrains.kotlin.fir.scopes.impl.FirDynamicMembersStorage -import org.jetbrains.kotlin.fir.scopes.impl.FirIntersectionOverrideStorage -import org.jetbrains.kotlin.fir.scopes.impl.FirSubstitutionOverrideStorage +import org.jetbrains.kotlin.fir.scopes.impl.* import org.jetbrains.kotlin.fir.symbols.FirLazyDeclarationResolver import org.jetbrains.kotlin.fir.types.FirCorrespondingSupertypesCache import org.jetbrains.kotlin.fir.types.FirFunctionTypeKindService @@ -104,6 +101,7 @@ fun FirSession.registerCommonJavaComponents(javaModuleResolver: JavaModuleResolv register(PlatformSupertypeUpdater::class, JvmSupertypeUpdater(this)) register(PlatformSpecificOverridabilityRules::class, JavaOverridabilityRules(this)) register(DeserializedClassConfigurator::class, JvmDeserializedClassConfigurator(this)) + register(FirEnumEntriesSupport::class, FirJvmEnumEntriesSupport(this)) } // -------------------------- Resolve components -------------------------- diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJsSessionFactory.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJsSessionFactory.kt index ec584d5c262..61623036def 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJsSessionFactory.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJsSessionFactory.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.fir.resolve.calls.ConeCallConflictResolverFactory import org.jetbrains.kotlin.fir.resolve.providers.impl.* import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider import org.jetbrains.kotlin.fir.scopes.FirPlatformClassMapper +import org.jetbrains.kotlin.fir.scopes.impl.FirEnumEntriesSupport import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.library.metadata.resolver.KotlinResolvedLibrary import org.jetbrains.kotlin.name.Name @@ -90,5 +91,6 @@ object FirJsSessionFactory : FirAbstractSessionFactory() { register(ConeCallConflictResolverFactory::class, JsCallConflictResolverFactory) register(FirPlatformClassMapper::class, FirPlatformClassMapper.Default) register(FirOverridesBackwardCompatibilityHelper::class, FirOverridesBackwardCompatibilityHelper.Default()) + register(FirEnumEntriesSupport::class, FirEnumEntriesSupport(this)) } } diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt index 24e6fd057ef..84876d768fe 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.resolve.providers.impl.FirCloneableSymbolProvide import org.jetbrains.kotlin.fir.resolve.providers.impl.FirExtensionSyntheticFunctionInterfaceProvider import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider import org.jetbrains.kotlin.fir.scopes.FirPlatformClassMapper +import org.jetbrains.kotlin.fir.scopes.impl.FirEnumEntriesSupport import org.jetbrains.kotlin.library.metadata.resolver.KotlinResolvedLibrary import org.jetbrains.kotlin.name.Name @@ -94,5 +95,6 @@ object FirNativeSessionFactory : FirAbstractSessionFactory() { register(ConeCallConflictResolverFactory::class, NativeCallConflictResolverFactory) register(FirPlatformClassMapper::class, FirPlatformClassMapper.Default) register(FirOverridesBackwardCompatibilityHelper::class, FirOverridesBackwardCompatibilityHelper.Default()) + register(FirEnumEntriesSupport::class, FirEnumEntriesSupport(this)) } } diff --git a/compiler/fir/fir-deserialization/src/org/jetbrains/kotlin/fir/deserialization/ClassDeserialization.kt b/compiler/fir/fir-deserialization/src/org/jetbrains/kotlin/fir/deserialization/ClassDeserialization.kt index 95d567fe815..88370f36f15 100644 --- a/compiler/fir/fir-deserialization/src/org/jetbrains/kotlin/fir/deserialization/ClassDeserialization.kt +++ b/compiler/fir/fir-deserialization/src/org/jetbrains/kotlin/fir/deserialization/ClassDeserialization.kt @@ -245,6 +245,10 @@ fun deserializeClassToSymbol( session.deserializedClassConfigurator?.run { configure(classId) } + + if (!Flags.HAS_ENUM_ENTRIES.get(flags)) { + hasNoEnumEntriesAttr = true + } } } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConverter.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConverter.kt index 9d236f690d3..e6774e4eaef 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConverter.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConverter.kt @@ -373,8 +373,8 @@ class Fir2IrConverter( ) } is FirProperty -> { - if (declaration.source?.kind == KtFakeSourceElementKind.EnumGeneratedDeclaration && - declaration.name == StandardNames.ENUM_ENTRIES && + if (containingClass != null && + declaration.isEnumEntries(containingClass) && !session.languageVersionSettings.supportsFeature(LanguageFeature.EnumEntries) ) { // Note: we have to do it, because backend without the feature diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirClassDeclaredMemberScope.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirClassDeclaredMemberScope.kt index 34f7e13335e..62f19404469 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirClassDeclaredMemberScope.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirClassDeclaredMemberScope.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.classId import org.jetbrains.kotlin.fir.declarations.utils.isSynthetic +import org.jetbrains.kotlin.fir.isEnumEntries import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope @@ -38,7 +39,10 @@ class FirClassDeclaredMemberScopeImpl( if (declaration is FirCallableDeclaration) { val name = when (declaration) { is FirConstructor -> SpecialNames.INIT - is FirVariable -> if (declaration.isSynthetic) continue@loop else declaration.name + is FirVariable -> when { + declaration.isSynthetic || declaration.isEnumEntries(klass) && !klass.supportsEnumEntries -> continue@loop + else -> declaration.name + } is FirSimpleFunction -> declaration.name else -> continue@loop } @@ -48,6 +52,8 @@ class FirClassDeclaredMemberScopeImpl( result } + private val FirClass.supportsEnumEntries get() = useSiteSession.enumEntriesSupport.canSynthesizeEnumEntriesFor(this) + override fun processFunctionsByName(name: Name, processor: (FirNamedFunctionSymbol) -> Unit) { if (name == SpecialNames.INIT) return processCallables(name, processor) diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirEnumEntriesSupport.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirEnumEntriesSupport.kt new file mode 100644 index 00000000000..4f6421ac5ad --- /dev/null +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirEnumEntriesSupport.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2023 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.scopes.impl + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.FirSessionComponent +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.hasEnumEntries +import org.jetbrains.kotlin.fir.resolve.providers.getRegularClassSymbolByClassId +import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider +import org.jetbrains.kotlin.name.StandardClassIds + +open class FirEnumEntriesSupport(val session: FirSession) : FirSessionComponent { + protected val isEnumEntriesAvailable by lazy { + session.symbolProvider.getRegularClassSymbolByClassId(StandardClassIds.EnumEntries) != null + } + + open fun canSynthesizeEnumEntriesFor(klass: FirClass): Boolean { + return klass.hasEnumEntries && isEnumEntriesAvailable + } +} + +class FirJvmEnumEntriesSupport(session: FirSession) : FirEnumEntriesSupport(session) { + // In JVM modules "entries" can be called even on enum compiled without this property. + override fun canSynthesizeEnumEntriesFor(klass: FirClass): Boolean = isEnumEntriesAvailable +} + +val FirSession.enumEntriesSupport: FirEnumEntriesSupport by FirSession.sessionComponentAccessor() diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/ClassMembers.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/ClassMembers.kt index 55bd4de4b9d..5b33f85ec86 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/ClassMembers.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/ClassMembers.kt @@ -45,6 +45,12 @@ var FirCallableDeclaration.containingClassForStaticMemberAttr: ConeClassLikeLook var FirRegularClass.containingClassForLocalAttr: ConeClassLikeLookupTag? by FirDeclarationDataRegistry.data(ContainingClassKey) var FirDanglingModifierList.containingClassAttr: ConeClassLikeLookupTag? by FirDeclarationDataRegistry.data(ContainingClassKey) +private object HasNoEnumEntriesKey : FirDeclarationDataKey() +var FirClass.hasNoEnumEntriesAttr: Boolean? by FirDeclarationDataRegistry.data(HasNoEnumEntriesKey) + +// Must be true iff the class metadata contains the hasEnumEntries flag +val FirClass.hasEnumEntries get() = hasNoEnumEntriesAttr != true + private object IsNewPlaceForBodyGeneration : FirDeclarationDataKey() var FirRegularClass.isNewPlaceForBodyGeneration: Boolean? by FirDeclarationDataRegistry.data(IsNewPlaceForBodyGeneration) diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Utils.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Utils.kt index 8cdc0be5a84..ee1f45e0191 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Utils.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/Utils.kt @@ -7,14 +7,13 @@ package org.jetbrains.kotlin.fir import com.intellij.psi.PsiElement import org.jetbrains.kotlin.* +import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibility -import org.jetbrains.kotlin.fir.declarations.FirContextReceiver -import org.jetbrains.kotlin.fir.declarations.FirDeclarationStatus -import org.jetbrains.kotlin.fir.declarations.FirFile -import org.jetbrains.kotlin.fir.declarations.FirResolvedDeclarationStatus +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirDeclarationStatusImpl import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl +import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass import org.jetbrains.kotlin.fir.expressions.FirBlock import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.renderer.FirRenderer @@ -254,3 +253,6 @@ fun List.smartPlus(other: List): List = when { result } } + +// Source element may be missing if the class came from a library +fun FirVariable.isEnumEntries(containingClass: FirClass) = name == StandardNames.ENUM_ENTRIES && containingClass.isEnumClass diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumClassLowering.kt index 306d1168d71..cc6e2f5e868 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumClassLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumClassLowering.kt @@ -124,7 +124,13 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL val valuesField = buildValuesField(valuesHelperFunction) val entriesField = when { - irClass.declarations.any { it.isGetEntriesFunction } -> { + !irClass.hasGetEntriesFunction -> { + null + } + !supportsEnumEntries -> { + error("The frontend must have checked if the feature is supported while emitting the IR") + } + else -> { // Constructs the synthetic $entries() function that returns plain $VALUES without copy val entriesHelperFunction = buildEntriesHelperFunction(valuesField) @@ -137,12 +143,6 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL */ buildEntriesField(entriesHelperFunction) } - supportsEnumEntries -> { - error("kotlin.enums.EnumEntries is not available to the frontend. Is non-full kotlin stdlib being used?") - } - else -> { - null - } } // Add synthetic parameters to enum constructors and implement the values and valueOf functions @@ -152,6 +152,9 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL irClass.transformChildrenVoid(EnumClassCallTransformer()) } + private val IrClass.hasGetEntriesFunction: Boolean + get() = declarations.any { it.isGetEntriesFunction } + private val IrDeclaration.isGetEntriesFunction: Boolean get() = this is IrFunction && name == SpecialNames.ENUM_GET_ENTRIES && origin == IrDeclarationOrigin.ENUM_CLASS_SPECIAL_MEMBER diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/EnumEntriesWithOutdatedStdlibTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/EnumEntriesWithOutdatedStdlibTest.kt new file mode 100644 index 00000000000..d76f337b0fe --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/EnumEntriesWithOutdatedStdlibTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2019 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.gradle + +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.util.replaceText +import org.junit.jupiter.api.DisplayName + +@DisplayName("EnumEntries feature test when no kotlin.enums.EnumEntries found on the classpath") +@JvmGradlePluginTests +class EnumEntriesWithOutdatedStdlibTest : KGPBaseTest() { + @DisplayName("EnumEntries should not be accessible without kotlin.enums.EnumEntries") + @GradleTest + fun enumEntriesNotAccessible(gradleVersion: GradleVersion) { + project("enumEntriesNotAccessible", gradleVersion) { + buildGradleKts.replaceText("\"\"", "\"1.9\" // ") + + buildAndFail(":compileKotlin") { + assertOutputContains("Main.kt:13:20 Unresolved reference: entries") + assertOutputContains("Main.kt:14:30 Unresolved reference: entries") + } + + buildGradleKts.replaceText("\"1.9\" // ", "\"2.0\" // ") + + buildAndFail(":compileKotlin") { + assertOutputContains("Main.kt:13:20 Unresolved reference: entries") + assertOutputContains("Main.kt:14:30 Unresolved reference: entries") + } + } + } + + @DisplayName("Code should compile normally if `entries` is never referenced") + @GradleTest + fun codeCompilesWithoutReferencingEntries(gradleVersion: GradleVersion) { + project("enumEntriesNotAccessible", gradleVersion) { + projectPath.resolve("src/main/kotlin/Main.kt") + .replaceText("entries", "values()") + + buildGradleKts.replaceText("\"\"", "\"1.9\" // ") + build(":compileKotlin") + + buildGradleKts.replaceText("\"1.9\" // ", "\"2.0\" // ") + build(":compileKotlin") + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/build.gradle.kts new file mode 100644 index 00000000000..0a832d435d5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/build.gradle.kts @@ -0,0 +1,24 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") +} + +group = "com.example" +version = "1.0" + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.20") +} + +allprojects { + repositories { + mavenLocal() + mavenCentral() + } +} + +tasks.withType { + kotlinOptions.jvmTarget = "1.8" + kotlinOptions.languageVersion = "" +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/settings.gradle new file mode 100644 index 00000000000..699e33c0b33 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/settings.gradle @@ -0,0 +1,2 @@ +include(":k1") +include(":k2") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/src/main/kotlin/Main.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/src/main/kotlin/Main.kt new file mode 100644 index 00000000000..a3c8d93a32b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/enumEntriesNotAccessible/src/main/kotlin/Main.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2019 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 com.example + +enum class MyEnum { + A, B +} + +fun main() { + val a = MyEnum.entries + val b = AnnotationTarget.entries + println("$a :: $b") +}