[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:tree"))
api(project(":compiler:fir:providers")) api(project(":compiler:fir:providers"))
api(project(":compiler:fir:semantics")) api(project(":compiler:fir:semantics"))
api(project(":compiler:fir:resolve"))
compileOnly(intellijCore()) 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.*
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.scopes.* 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.EnumValue
import org.jetbrains.kotlin.fir.serialization.constant.IntValue import org.jetbrains.kotlin.fir.serialization.constant.IntValue
import org.jetbrains.kotlin.fir.serialization.constant.StringValue 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( val flags = Flags.getPropertyFlags(
hasAnnotations, hasAnnotations,
ProtoEnumFlags.visibility(normalizeVisibility(property)), ProtoEnumFlags.visibility(normalizeVisibility(property)),
ProtoEnumFlags.modality(modality), ProtoEnumFlags.modality(modality),
ProtoBuf.MemberKind.DECLARATION, 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 property.isExternal, property.delegateFieldSymbol != null, property.isExpect
) )
if (flags != builder.flags) { 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.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.serialization.constant.ConstValueProvider 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.ConeErrorType
import org.jetbrains.kotlin.fir.types.ConeFlexibleType import org.jetbrains.kotlin.fir.types.ConeFlexibleType
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion 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.metadata.serialization.MutableVersionRequirementTable
import org.jetbrains.kotlin.name.FqName 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.FirAnnotationContainer
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation 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.serialization.constant.buildValueProtoBufIfPropertyIsConst
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.GeneratedMessageLite import org.jetbrains.kotlin.protobuf.GeneratedMessageLite
@@ -81,7 +80,8 @@ abstract class FirSerializerExtensionBase(
property.setter?.serializeAnnotations(proto, protocol.propertySetterAnnotation) property.setter?.serializeAnnotations(proto, protocol.propertySetterAnnotation)
property.receiverParameter?.serializeAnnotations(proto, protocol.propertyExtensionReceiverAnnotation, property) 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) 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.FirReceiverParameter
import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirAnnotation 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.fir.serialization.FirAnnotationSerializer
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
abstract class ConstValueProvider { abstract class ConstValueProvider {
abstract val session: FirSession abstract val session: FirSession
abstract fun getConstantValueForProperty(firProperty: FirProperty): FirConstExpression<*>? abstract fun getConstantValueForProperty(firProperty: FirProperty): FirExpression?
abstract fun getNewFirAnnotationWithConstantValues( abstract fun getNewFirAnnotationWithConstantValues(
firAnnotationContainer: FirAnnotationContainer, firAnnotationContainer: FirAnnotationContainer,
@@ -44,13 +44,13 @@ abstract class ConstValueProvider {
firAnnotation: FirAnnotation, firAnnotation: 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 val constantValue = this?.toConstantValue(session) ?: return null
return annotationSerializer.valueProto(constantValue).build() return annotationSerializer.valueProto(constantValue).build()
} }
} }
fun ConstValueProvider.buildValueProtoBufIfPropertyIsConst( fun ConstValueProvider.buildValueProtoBufIfPropertyHasConst(
firProperty: FirProperty, annotationSerializer: FirAnnotationSerializer firProperty: FirProperty, annotationSerializer: FirAnnotationSerializer
): ProtoBuf.Annotation.Argument.Value? { ): ProtoBuf.Annotation.Argument.Value? {
return getConstantValueForProperty(firProperty).toProtoBuf(annotationSerializer) return getConstantValueForProperty(firProperty).toProtoBuf(annotationSerializer)
@@ -6,16 +6,15 @@
package org.jetbrains.kotlin.fir.serialization.constant package org.jetbrains.kotlin.fir.serialization.constant
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.FirEnumEntry import org.jetbrains.kotlin.fir.declarations.FirEnumEntry
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationArgumentMapping import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationArgumentMapping
import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationCall import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationCall
import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference 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.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.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.classId
@@ -28,6 +27,10 @@ internal fun FirExpression.toConstantValue(session: FirSession): ConstantValue<*
return accept(FirToConstantValueTransformerUnsafe, session) 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 FirToConstantValueTransformerSafe : FirToConstantValueTransformer(failOnNonConst = false)
private object FirToConstantValueTransformerUnsafe : FirToConstantValueTransformer(failOnNonConst = true) private object FirToConstantValueTransformerUnsafe : FirToConstantValueTransformer(failOnNonConst = true)
@@ -162,6 +165,9 @@ private abstract class FirToConstantValueTransformer(
functionCall: FirFunctionCall, functionCall: FirFunctionCall,
data: FirSession data: FirSession
): ConstantValue<*>? { ): ConstantValue<*>? {
if (functionCall.isArrayOfCall) {
return FirArrayOfCallTransformer().transformFunctionCall(functionCall, null).accept(this, data)
}
return visitQualifiedAccessExpression(functionCall, data) return visitQualifiedAccessExpression(functionCall, data)
} }
@@ -176,3 +182,75 @@ private abstract class FirToConstantValueTransformer(
return namedArgumentExpression.expression.accept(this, data) 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() { ) : ConstValueProvider() {
override val session: FirSession = components.session override val session: FirSession = components.session
override fun getConstantValueForProperty(firProperty: FirProperty): FirConstExpression<*>? { override fun getConstantValueForProperty(firProperty: FirProperty): FirExpression? {
val irProperty: IrProperty = components.declarationStorage.getCachedIrProperty(firProperty) ?: return null val firInitializer = firProperty.initializer
if (!irProperty.isConst) return null val irProperty: IrProperty = components.declarationStorage.getCachedIrProperty(firProperty) ?: return firInitializer
val irConst = irProperty.backingField?.initializer?.expression as? IrConst<*> ?: return null val irConst = irProperty.backingField?.initializer?.expression as? IrConst<*> ?: return firInitializer
return irConst.toFirConst() return irConst.toFirConst() ?: firInitializer
} }
override fun getNewFirAnnotationWithConstantValues( 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. * Note that arrayOf() calls only in [FirAnnotation] or the default value of annotation constructor are transformed.
*/ */
internal class FirArrayOfCallTransformer : FirDefaultTransformer<Nothing?>() { 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
}
private fun toArrayOfCall(functionCall: FirFunctionCall): FirArrayOfCall? { private fun toArrayOfCall(functionCall: FirFunctionCall): FirArrayOfCall? {
if (!functionCall.isArrayOfCall) { if (!functionCall.isArrayOfCall) {
return null return null
@@ -63,6 +54,15 @@ internal class FirArrayOfCallTransformer : FirDefaultTransformer<Nothing?>() {
} }
companion object { 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") + private val arrayOfNames = hashSetOf("kotlin/arrayOf") +
hashSetOf( hashSetOf(
"boolean", "byte", "char", "double", "float", "int", "long", "short", "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 receiverType = irFunction.dispatchReceiverParameter?.type ?: irFunction.extensionReceiverParameter?.type
val argsType = (listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type }).map { 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) // 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) 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 forbiddenMethodsOnStrings = setOf("subSequence", "hashCode", "<init>")
private val allowedExtensionFunctions = setOf( private val allowedExtensionFunctions = setOf(
"kotlin.floorDiv", "kotlin.mod", "kotlin.NumbersKt.floorDiv", "kotlin.NumbersKt.mod", "kotlin.<get-code>", "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 { override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
@@ -1,5 +1,5 @@
// KT-56190 K2 does not emit const initializers
// MUTED_WHEN: K2 // MUTED_WHEN: K2
// `val b = a + 20` will not be serialized in K2
package test package test
enum class Weapon { enum class Weapon {
@@ -1,5 +1,3 @@
// KT-56190 K2 does not emit const initializers
// MUTED_WHEN: K2
package test package test
class ClassA { class ClassA {
@@ -18,7 +16,9 @@ class ClassA {
inner class classC { inner class classC {
val memberFromC: Int = 200 val memberFromC: Int = 200
} }
}
class E {
companion object { companion object {
val stat: Int = 250 val stat: Int = 250
@@ -26,7 +26,9 @@ class ClassA {
val memberFromD: Int = 275 val memberFromD: Int = 275
} }
} }
}
class F {
object ObjA { object ObjA {
val memberFromObjA: Int = 300 val memberFromObjA: Int = 300
} }
@@ -1,13 +1,4 @@
class ClassA constructor() { 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() { class classB constructor() {
fun memberFromB(): Int fun memberFromB(): Int
class BC constructor() { class BC constructor() {
@@ -20,4 +11,17 @@
inner class classC constructor() { inner class classC constructor() {
val memberFromC: Int = 200 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 package test
annotation class Anno(val value: String) annotation class Anno(val value: String)