KT-44741: Improve handling of constants defined in companion objects

This commit is contained in:
Hung Nguyen
2021-12-16 21:57:08 +00:00
committed by nataliya.valtman
parent a900f2b66d
commit 9a995af0df
5 changed files with 65 additions and 54 deletions
@@ -20,7 +20,6 @@ import com.intellij.util.io.EnumeratorStringDescriptor
import org.jetbrains.kotlin.incremental.storage.* import org.jetbrains.kotlin.incremental.storage.*
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.metadata.deserialization.TypeTable import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.metadata.deserialization.supertypes import org.jetbrains.kotlin.metadata.deserialization.supertypes
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
@@ -125,7 +124,9 @@ abstract class AbstractIncrementalCache<ClassName>(
* *
* The `srcFile` argument may be `null` (e.g., if we are processing .class files in jars where source files are not available). * The `srcFile` argument may be `null` (e.g., if we are processing .class files in jars where source files are not available).
*/ */
protected fun addToClassStorage(proto: ProtoBuf.Class, nameResolver: NameResolver, srcFile: File?) { protected fun addToClassStorage(classProtoData: ClassProtoData, srcFile: File?) {
val (proto, nameResolver) = classProtoData
val supertypes = proto.supertypes(TypeTable(proto.typeTable)) val supertypes = proto.supertypes(TypeTable(proto.typeTable))
val parents = supertypes.map { nameResolver.getClassId(it.className).asSingleFqName() } val parents = supertypes.map { nameResolver.getClassId(it.className).asSingleFqName() }
.filter { it.asString() != "kotlin.Any" } .filter { it.asString() != "kotlin.Any" }
@@ -117,7 +117,7 @@ open class IncrementalJsCache(
registerOutputForFile(srcFile, classId.asSingleFqName()) registerOutputForFile(srcFile, classId.asSingleFqName())
if (protoData is ClassProtoData) { if (protoData is ClassProtoData) {
addToClassStorage(protoData.proto, protoData.nameResolver, srcFile) addToClassStorage(protoData, srcFile)
} }
} }
@@ -30,11 +30,10 @@ import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.*
@@ -182,12 +181,7 @@ open class IncrementalJvmCache(
inlineFunctionsMap.process(kotlinClassInfo, changesCollector) inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
} }
KotlinClassHeader.Kind.CLASS -> { KotlinClassHeader.Kind.CLASS -> {
if (sourceFiles != null) { addToClassStorage(kotlinClassInfo.protoData as ClassProtoData, sourceFiles?.let { sourceFiles.single() })
assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" }
addToClassStorage(kotlinClassInfo, sourceFiles.single())
} else {
addToClassStorage(kotlinClassInfo, null)
}
protoMap.process(kotlinClassInfo, changesCollector) protoMap.process(kotlinClassInfo, changesCollector)
constantsMap.process(kotlinClassInfo, changesCollector) constantsMap.process(kotlinClassInfo, changesCollector)
@@ -202,8 +196,7 @@ open class IncrementalJvmCache(
val jvmClassName = JvmClassName.byClassId(serializedJavaClass.classId) val jvmClassName = JvmClassName.byClassId(serializedJavaClass.classId)
javaSourcesProtoMap.process(jvmClassName, serializedJavaClass, collector) javaSourcesProtoMap.process(jvmClassName, serializedJavaClass, collector)
source?.let { sourceToClassesMap.add(source, jvmClassName) } source?.let { sourceToClassesMap.add(source, jvmClassName) }
val (proto, nameResolver) = serializedJavaClass.toProtoData() addToClassStorage(serializedJavaClass.toProtoData(), source)
addToClassStorage(proto, nameResolver, source)
// collector.addJavaProto(ClassProtoData(proto, nameResolver)) // collector.addJavaProto(ClassProtoData(proto, nameResolver))
dirtyOutputClassesMap.notDirty(jvmClassName) dirtyOutputClassesMap.notDirty(jvmClassName)
} }
@@ -317,15 +310,11 @@ open class IncrementalJvmCache(
private fun put(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) { private fun put(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
val key = kotlinClassInfo.className.internalName val key = kotlinClassInfo.className.internalName
val oldData = storage[key] val oldData = storage[key]
val newData = ProtoMapValue( val newData = kotlinClassInfo.protoMapValue
kotlinClassInfo.classKind != KotlinClassHeader.Kind.CLASS,
BitEncoding.decodeBytes(kotlinClassInfo.classHeaderData),
kotlinClassInfo.classHeaderStrings
)
storage[key] = newData storage[key] = newData
val packageFqName = kotlinClassInfo.className.packageFqName val packageFqName = kotlinClassInfo.className.packageFqName
changesCollector.collectProtoChanges(oldData?.toProtoData(packageFqName), newData.toProtoData(packageFqName), packageProtoKey = key) changesCollector.collectProtoChanges(oldData?.toProtoData(packageFqName), kotlinClassInfo.protoData, packageProtoKey = key)
} }
operator fun contains(className: JvmClassName): Boolean = operator fun contains(className: JvmClassName): Boolean =
@@ -402,20 +391,29 @@ open class IncrementalJvmCache(
storage.remove(key) storage.remove(key)
} }
for (const in oldMap.keys + newMap.keys) { val allConstants = oldMap.keys + newMap.keys
//Constant can be declared via companion object or via const field declaration if (allConstants.isEmpty()) return
changesCollector.collectMemberIfValueWasChanged(
kotlinClassInfo.scopeFqName(companion = true), // If a constant is defined in a companion object, it will be found in the constantsMap of the containing class, not the
const, // companion object's class, so we will need to correct its scope.
oldMap[const], // (See https://youtrack.jetbrains.com/issue/KT-44741#focus=Comments-27-5659564.0-0 for more details.)
newMap[const] // Note: This only applies to a *constant* defined in a *companion object* (it's not an issue for inline functions, or top-level
) // constants, or constants in non-companion objects).
changesCollector.collectMemberIfValueWasChanged( val companionObjectClassId = if (kotlinClassInfo.classKind == KotlinClassHeader.Kind.CLASS) {
kotlinClassInfo.scopeFqName(companion = false), val protoData = kotlinClassInfo.protoData as ClassProtoData
const, if (protoData.proto.hasCompanionObjectName()) {
oldMap[const], val companionObjectName = Name.identifier(protoData.nameResolver.getString(protoData.proto.companionObjectName))
newMap[const] kotlinClassInfo.classId.createNestedClassId(companionObjectName)
) } else null
} else null
val scope = companionObjectClassId?.asSingleFqName() ?: kotlinClassInfo.scopeFqName()
// Here we assume that the old and new classes have the same KotlinClassHeader.Kind, so that the scopes of the old and new
// constants are the same and their values can be compared.
// If the class kinds are different, the changes will be detected when comparing protos (in that case, the changes collected
// here will be a subset of those changes).
for (const in allConstants) {
changesCollector.collectMemberIfValueWasChanged(scope, const, oldMap[const], newMap[const])
} }
} }
@@ -503,11 +501,6 @@ open class IncrementalJvmCache(
value.dumpCollection() value.dumpCollection()
} }
private fun addToClassStorage(classInfo: KotlinClassInfo, srcFile: File?) {
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(classInfo.classHeaderData, classInfo.classHeaderStrings)
addToClassStorage(proto, nameResolver, srcFile)
}
private inner class InlineFunctionsMap(storageFile: File) : private inner class InlineFunctionsMap(storageFile: File) :
BasicStringMap<LinkedHashMap<String, Long>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) { BasicStringMap<LinkedHashMap<String, Long>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) {
@@ -545,6 +538,11 @@ open class IncrementalJvmCache(
override fun dumpValue(value: LinkedHashMap<String, Long>): String = override fun dumpValue(value: LinkedHashMap<String, Long>): String =
value.dumpMap { java.lang.Long.toHexString(it) } value.dumpMap { java.lang.Long.toHexString(it) }
} }
private fun KotlinClassInfo.scopeFqName() = when (classKind) {
KotlinClassHeader.Kind.CLASS -> classId.asSingleFqName()
else -> classId.packageFqName
}
} }
private object PathCollectionExternalizer : private object PathCollectionExternalizer :
@@ -613,18 +611,32 @@ class KotlinClassInfo constructor(
val classKind: KotlinClassHeader.Kind, val classKind: KotlinClassHeader.Kind,
val classHeaderData: Array<String>, // Can be empty val classHeaderData: Array<String>, // Can be empty
val classHeaderStrings: Array<String>, // Can be empty val classHeaderStrings: Array<String>, // Can be empty
@Suppress("SpellCheckingInspection") val multifileClassName: String?, val multifileClassName: String?,
val constantsMap: LinkedHashMap<String, Any>, val constantsMap: LinkedHashMap<String, Any>,
val inlineFunctionsMap: LinkedHashMap<String, Long> val inlineFunctionsMap: LinkedHashMap<String, Long>
) { ) {
val className: JvmClassName by lazy { JvmClassName.byClassId(classId) } val className: JvmClassName by lazy { JvmClassName.byClassId(classId) }
fun scopeFqName(companion: Boolean = false) = when (classKind) { val protoMapValue: ProtoMapValue by lazy {
KotlinClassHeader.Kind.CLASS -> { ProtoMapValue(
className.fqNameForClassNameWithoutDollars.let { if (companion) it.child(DEFAULT_NAME_FOR_COMPANION_OBJECT) else it } isPackageFacade = classKind != KotlinClassHeader.Kind.CLASS,
BitEncoding.decodeBytes(classHeaderData),
classHeaderStrings
)
}
/**
* Returns the [ProtoData] of this class.
*
* NOTE: The caller needs to ensure `classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS` first, as the compiler doesn't write proto
* data to [KotlinClassHeader.Kind.MULTIFILE_CLASS] classes.
*/
val protoData: ProtoData by lazy {
check(classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS) {
"Proto data is not available for KotlinClassHeader.Kind.MULTIFILE_CLASS: $classId"
} }
else -> className.packageFqName protoMapValue.toProtoData(className.packageFqName)
} }
companion object { companion object {
@@ -171,27 +171,17 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
Changes( Changes(
lookupSymbols = setOf( lookupSymbols = setOf(
LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass.CompanionObject"), LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass.CompanionObject"),
// TODO (Fix in next commit). Missing: LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass.CompanionObject"),
// LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass.CompanionObject")
LookupSymbol(name = "inlineFunctionChangedSignature", scope = "com.example.SomeClass"), LookupSymbol(name = "inlineFunctionChangedSignature", scope = "com.example.SomeClass"),
LookupSymbol(name = "inlineFunctionChangedImplementation", scope = "com.example.SomeClass"), LookupSymbol(name = "inlineFunctionChangedImplementation", scope = "com.example.SomeClass"),
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"), LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"),
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass.CompanionObject"), LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass.CompanionObject"),
// TODO (Fix in next commit). Incorrect:
LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass"),
LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass.Companion"),
LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass"),
LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass.Companion"),
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass.Companion"),
), ),
fqNames = setOf( fqNames = setOf(
"com.example.SomeClass", "com.example.SomeClass",
"com.example.SomeClass.CompanionObject", "com.example.SomeClass.CompanionObject",
// TODO (Fix in next commit). Incorrect:
"com.example.SomeClass.Companion"
) )
).assertEquals(changes) ).assertEquals(changes)
} }
@@ -34,6 +34,14 @@
"className$delegate": { "className$delegate": {
"initializer": {}, "initializer": {},
"_value": {} "_value": {}
},
"protoMapValue$delegate": {
"initializer": {},
"_value": {}
},
"protoData$delegate": {
"initializer": {},
"_value": {}
} }
}, },
"supertypes": [ "supertypes": [