[K2] Properly serialize constant values

#KT-56190 Fixed
This commit is contained in:
Ivan Kylchik
2023-03-31 23:36:16 +02:00
committed by Space Team
parent ac480e2285
commit 8499891756
14 changed files with 135 additions and 44 deletions
@@ -14,6 +14,7 @@ dependencies {
api(project(":compiler:fir:tree"))
api(project(":compiler:fir:providers"))
api(project(":compiler:fir:semantics"))
api(project(":compiler:fir:resolve"))
compileOnly(intellijCore())
}
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.fir.extensions.typeAttributeExtensions
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.serialization.constant.*
import org.jetbrains.kotlin.fir.serialization.constant.EnumValue
import org.jetbrains.kotlin.fir.serialization.constant.IntValue
import org.jetbrains.kotlin.fir.serialization.constant.StringValue
@@ -375,12 +376,13 @@ class FirElementSerializer private constructor(
}
}
val hasConstant = (!property.isVar && property.initializer.hasConstantValue(session)) || property.isConst
val flags = Flags.getPropertyFlags(
hasAnnotations,
ProtoEnumFlags.visibility(normalizeVisibility(property)),
ProtoEnumFlags.modality(modality),
ProtoBuf.MemberKind.DECLARATION,
property.isVar, hasGetter, hasSetter, property.isConst, property.isConst, property.isLateInit,
property.isVar, hasGetter, hasSetter, hasConstant, property.isConst, property.isLateInit,
property.isExternal, property.delegateFieldSymbol != null, property.isExpect
)
if (flags != builder.flags) {
@@ -9,10 +9,12 @@ import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.serialization.constant.ConstValueProvider
import org.jetbrains.kotlin.fir.serialization.constant.buildValueProtoBufIfPropertyHasConst
import org.jetbrains.kotlin.fir.types.ConeErrorType
import org.jetbrains.kotlin.fir.types.ConeFlexibleType
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable
import org.jetbrains.kotlin.name.FqName
@@ -9,10 +9,9 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.serialization.constant.ConstValueProvider
import org.jetbrains.kotlin.fir.serialization.constant.buildValueProtoBufIfPropertyIsConst
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.fir.serialization.constant.buildValueProtoBufIfPropertyHasConst
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.GeneratedMessageLite
@@ -81,7 +80,8 @@ abstract class FirSerializerExtensionBase(
property.setter?.serializeAnnotations(proto, protocol.propertySetterAnnotation)
property.receiverParameter?.serializeAnnotations(proto, protocol.propertyExtensionReceiverAnnotation, property)
constValueProvider?.buildValueProtoBufIfPropertyIsConst(property, annotationSerializer)?.let { constProtoBuf ->
if (!Flags.HAS_CONSTANT.get(proto.flags)) return
constValueProvider?.buildValueProtoBufIfPropertyHasConst(property, annotationSerializer)?.let { constProtoBuf ->
proto.setExtension(protocol.compileTimeValue, constProtoBuf)
}
}
@@ -12,14 +12,14 @@ import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.FirReceiverParameter
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.serialization.FirAnnotationSerializer
import org.jetbrains.kotlin.metadata.ProtoBuf
abstract class ConstValueProvider {
abstract val session: FirSession
abstract fun getConstantValueForProperty(firProperty: FirProperty): FirConstExpression<*>?
abstract fun getConstantValueForProperty(firProperty: FirProperty): FirExpression?
abstract fun getNewFirAnnotationWithConstantValues(
firAnnotationContainer: FirAnnotationContainer,
@@ -44,13 +44,13 @@ abstract class ConstValueProvider {
firAnnotation: FirAnnotation,
): FirAnnotation
fun FirConstExpression<*>?.toProtoBuf(annotationSerializer: FirAnnotationSerializer): ProtoBuf.Annotation.Argument.Value? {
fun FirExpression?.toProtoBuf(annotationSerializer: FirAnnotationSerializer): ProtoBuf.Annotation.Argument.Value? {
val constantValue = this?.toConstantValue(session) ?: return null
return annotationSerializer.valueProto(constantValue).build()
}
}
fun ConstValueProvider.buildValueProtoBufIfPropertyIsConst(
fun ConstValueProvider.buildValueProtoBufIfPropertyHasConst(
firProperty: FirProperty, annotationSerializer: FirAnnotationSerializer
): ProtoBuf.Annotation.Argument.Value? {
return getConstantValueForProperty(firProperty).toProtoBuf(annotationSerializer)
@@ -6,16 +6,15 @@
package org.jetbrains.kotlin.fir.serialization.constant
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.declarations.FirEnumEntry
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationArgumentMapping
import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationCall
import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirArrayOfCallTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirArrayOfCallTransformer.Companion.isArrayOfCall
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.classId
@@ -28,6 +27,10 @@ internal fun FirExpression.toConstantValue(session: FirSession): ConstantValue<*
return accept(FirToConstantValueTransformerUnsafe, session)
}
internal fun FirExpression?.hasConstantValue(session: FirSession): Boolean {
return this?.accept(FirToConstantValueChecker, session) == true
}
private object FirToConstantValueTransformerSafe : FirToConstantValueTransformer(failOnNonConst = false)
private object FirToConstantValueTransformerUnsafe : FirToConstantValueTransformer(failOnNonConst = true)
@@ -162,6 +165,9 @@ private abstract class FirToConstantValueTransformer(
functionCall: FirFunctionCall,
data: FirSession
): ConstantValue<*>? {
if (functionCall.isArrayOfCall) {
return FirArrayOfCallTransformer().transformFunctionCall(functionCall, null).accept(this, data)
}
return visitQualifiedAccessExpression(functionCall, data)
}
@@ -176,3 +182,75 @@ private abstract class FirToConstantValueTransformer(
return namedArgumentExpression.expression.accept(this, data)
}
}
internal object FirToConstantValueChecker : FirDefaultVisitor<Boolean, FirSession>() {
// `null` value is not treated as a const
private val supportedConstKinds = setOf<ConstantValueKind<*>>(
ConstantValueKind.Boolean, ConstantValueKind.Char, ConstantValueKind.String, ConstantValueKind.Float, ConstantValueKind.Double,
ConstantValueKind.Byte, ConstantValueKind.UnsignedByte, ConstantValueKind.Short, ConstantValueKind.UnsignedShort,
ConstantValueKind.Int, ConstantValueKind.UnsignedInt, ConstantValueKind.Long, ConstantValueKind.UnsignedLong,
)
override fun visitElement(element: FirElement, data: FirSession): Boolean {
return false
}
override fun <T> visitConstExpression(
constExpression: FirConstExpression<T>,
data: FirSession
): Boolean {
return constExpression.kind in supportedConstKinds
}
override fun visitArrayOfCall(arrayOfCall: FirArrayOfCall, data: FirSession): Boolean {
return arrayOfCall.arguments.all { it.accept(this, data) }
}
override fun visitAnnotation(annotation: FirAnnotation, data: FirSession): Boolean = true
override fun visitAnnotationCall(annotationCall: FirAnnotationCall, data: FirSession): Boolean = true
override fun visitGetClassCall(getClassCall: FirGetClassCall, data: FirSession): Boolean {
return KClassValue.create(getClassCall.argument.typeRef.coneTypeUnsafe()) != null
}
override fun visitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression, data: FirSession): Boolean {
val symbol = qualifiedAccessExpression.toResolvedCallableSymbol() ?: return false
return when {
symbol.fir is FirEnumEntry -> symbol.fir.returnTypeRef.coneTypeSafe<ConeClassLikeType>()?.classId != null
symbol is FirConstructorSymbol -> {
symbol.containingClassLookupTag()?.toFirRegularClassSymbol(data)?.classKind == ClassKind.ANNOTATION_CLASS
}
symbol.callableId.packageName.asString() == "kotlin" -> {
val dispatchReceiver = qualifiedAccessExpression.dispatchReceiver
when (symbol.callableId.callableName.asString()) {
in setOf("toByte", "toLong", "toShort", "toFloat", "toDouble", "toChar") -> true
"unaryMinus" -> dispatchReceiver.accept(this, data)
else -> false
}
}
else -> false
}
}
override fun visitPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression, data: FirSession): Boolean {
return visitQualifiedAccessExpression(propertyAccessExpression, data)
}
override fun visitFunctionCall(functionCall: FirFunctionCall, data: FirSession): Boolean {
if (functionCall.isArrayOfCall) return functionCall.arguments.all { it.accept(this, data) }
return visitQualifiedAccessExpression(functionCall, data)
}
override fun visitVarargArgumentsExpression(varargArgumentsExpression: FirVarargArgumentsExpression, data: FirSession): Boolean {
return varargArgumentsExpression.arguments.all { it.accept(this, data) }
}
override fun visitNamedArgumentExpression(namedArgumentExpression: FirNamedArgumentExpression, data: FirSession): Boolean {
return namedArgumentExpression.expression.accept(this, data)
}
}
@@ -28,11 +28,11 @@ class ConstValueProviderImpl(
) : ConstValueProvider() {
override val session: FirSession = components.session
override fun getConstantValueForProperty(firProperty: FirProperty): FirConstExpression<*>? {
val irProperty: IrProperty = components.declarationStorage.getCachedIrProperty(firProperty) ?: return null
if (!irProperty.isConst) return null
val irConst = irProperty.backingField?.initializer?.expression as? IrConst<*> ?: return null
return irConst.toFirConst()
override fun getConstantValueForProperty(firProperty: FirProperty): FirExpression? {
val firInitializer = firProperty.initializer
val irProperty: IrProperty = components.declarationStorage.getCachedIrProperty(firProperty) ?: return firInitializer
val irConst = irProperty.backingField?.initializer?.expression as? IrConst<*> ?: return firInitializer
return irConst.toFirConst() ?: firInitializer
}
override fun getNewFirAnnotationWithConstantValues(
@@ -23,16 +23,7 @@ import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
*
* Note that arrayOf() calls only in [FirAnnotation] or the default value of annotation constructor are transformed.
*/
internal class FirArrayOfCallTransformer : FirDefaultTransformer<Nothing?>() {
private val FirFunctionCall.isArrayOfCall: Boolean
get() {
val function: FirCallableDeclaration = getOriginalFunction() ?: return false
return function is FirSimpleFunction &&
function.returnTypeRef.isArrayType &&
isArrayOf(function, arguments) &&
function.receiverParameter == null
}
class FirArrayOfCallTransformer : FirDefaultTransformer<Nothing?>() {
private fun toArrayOfCall(functionCall: FirFunctionCall): FirArrayOfCall? {
if (!functionCall.isArrayOfCall) {
return null
@@ -63,6 +54,15 @@ internal class FirArrayOfCallTransformer : FirDefaultTransformer<Nothing?>() {
}
companion object {
val FirFunctionCall.isArrayOfCall: Boolean
get() {
val function: FirCallableDeclaration = getOriginalFunction() ?: return false
return function is FirSimpleFunction &&
function.returnTypeRef.isArrayType &&
isArrayOf(function, arguments) &&
function.receiverParameter == null
}
private val arrayOfNames = hashSetOf("kotlin/arrayOf") +
hashSetOf(
"boolean", "byte", "char", "double", "float", "int", "long", "short",
@@ -153,7 +153,11 @@ internal class DefaultCallInterceptor(override val interpreter: IrInterpreter) :
val receiverType = irFunction.dispatchReceiverParameter?.type ?: irFunction.extensionReceiverParameter?.type
val argsType = (listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type }).map {
// TODO: for consistency with current K/JS implementation Float constant should be treated as a Double (KT-35422)
if (environment.configuration.treatFloatInSpecialWay && it.isFloat()) irBuiltIns.doubleType else it
if (environment.configuration.treatFloatInSpecialWay && it.makeNotNull().isFloat()) {
if (it.isNullable()) irBuiltIns.doubleType.makeNullable() else irBuiltIns.doubleType
} else {
it
}
}
val argsValues = args.wrap(this, irFunction)
@@ -52,7 +52,7 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
private val forbiddenMethodsOnStrings = setOf("subSequence", "hashCode", "<init>")
private val allowedExtensionFunctions = setOf(
"kotlin.floorDiv", "kotlin.mod", "kotlin.NumbersKt.floorDiv", "kotlin.NumbersKt.mod", "kotlin.<get-code>",
"kotlin.internal.ir.EQEQ",
"kotlin.internal.ir.EQEQ", "kotlin.internal.ir.ieee754equals",
)
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
@@ -1,5 +1,5 @@
// KT-56190 K2 does not emit const initializers
// MUTED_WHEN: K2
// `val b = a + 20` will not be serialized in K2
package test
enum class Weapon {
@@ -1,5 +1,3 @@
// KT-56190 K2 does not emit const initializers
// MUTED_WHEN: K2
package test
class ClassA {
@@ -18,7 +16,9 @@ class ClassA {
inner class classC {
val memberFromC: Int = 200
}
}
class E {
companion object {
val stat: Int = 250
@@ -26,7 +26,9 @@ class ClassA {
val memberFromD: Int = 275
}
}
}
class F {
object ObjA {
val memberFromObjA: Int = 300
}
@@ -1,13 +1,4 @@
class ClassA constructor() {
companion object {
val stat: Int = 250
class D constructor() {
val memberFromD: Int = 275
}
}
object ObjA {
val memberFromObjA: Int = 300
}
class classB constructor() {
fun memberFromB(): Int
class BC constructor() {
@@ -20,4 +11,17 @@
inner class classC constructor() {
val memberFromC: Int = 200
}
}
class E constructor() {
companion object {
val stat: Int = 250
class D constructor() {
val memberFromD: Int = 275
}
}
}
class F constructor() {
object ObjA {
val memberFromObjA: Int = 300
}
}
@@ -1,5 +1,3 @@
// KT-56190 K2 does not emit const initializers
// MUTED_WHEN: K2
package test
annotation class Anno(val value: String)