KT-56687: Unify handling of EnumEntries.

`supportsEnumEntries` in `EnumCLassLowering` is not really
needed, but it was left just in case.
Both K1 and K2 ensure the feature is
enabled before they generate IR.

`supportsEnumEntries` is not enough, because the frontend will not
generate `entries` if `kotlin.enums.EnumEntries` is not available
(linking against an outdated stdlib is considered a valid use-case).

From the resolution point of view,
it's OK to resolve into `entries` while
the feature is off, because then K1 reports an error on the call site.

^KT-56687 Fixed
^KT-55614 Fixed
This commit is contained in:
Nikolay Lunyak
2023-02-16 13:06:22 +02:00
committed by Space Team
parent bcfafc601e
commit bb368bd191
14 changed files with 164 additions and 18 deletions
@@ -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 --------------------------
@@ -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))
}
}
@@ -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))
}
}
@@ -245,6 +245,10 @@ fun deserializeClassToSymbol(
session.deserializedClassConfigurator?.run {
configure(classId)
}
if (!Flags.HAS_ENUM_ENTRIES.get(flags)) {
hasNoEnumEntriesAttr = true
}
}
}
@@ -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
@@ -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)
@@ -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()
@@ -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)
@@ -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 <T> List<T>.smartPlus(other: List<T>): List<T> = 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
@@ -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
@@ -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("\"<language-version>\"", "\"1.9\" // <language-version>")
buildAndFail(":compileKotlin") {
assertOutputContains("Main.kt:13:20 Unresolved reference: entries")
assertOutputContains("Main.kt:14:30 Unresolved reference: entries")
}
buildGradleKts.replaceText("\"1.9\" // <language-version>", "\"2.0\" // <language-version>")
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("\"<language-version>\"", "\"1.9\" // <language-version>")
build(":compileKotlin")
buildGradleKts.replaceText("\"1.9\" // <language-version>", "\"2.0\" // <language-version>")
build(":compileKotlin")
}
}
}
@@ -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<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.languageVersion = "<language-version>"
}
@@ -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")
}