Kotlin: Handle file classes better

This commit is contained in:
Ian Lynagh
2022-01-12 17:28:18 +00:00
parent 4340fe7044
commit 194e9fd2da
4 changed files with 113 additions and 74 deletions

View File

@@ -34,7 +34,7 @@ open class KotlinFileExtractor(
genericSpecialisationsExtracted: MutableSet<String>
): KotlinUsesExtractor(logger, tw, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted) {
fun extractDeclaration(declaration: IrDeclaration, parentId: Label<out DbReftype>) {
fun extractDeclaration(declaration: IrDeclaration) {
when (declaration) {
is IrClass -> {
if (isExternalDeclaration(declaration)) {
@@ -43,14 +43,30 @@ open class KotlinFileExtractor(
extractClassSource(declaration)
}
}
is IrFunction -> extractFunctionIfReal(declaration, parentId)
is IrFunction -> {
@Suppress("UNCHECKED_CAST")
val parentId = useDeclarationParent(declaration.parent, false) as Label<DbReftype>
extractFunctionIfReal(declaration, parentId)
}
is IrAnonymousInitializer -> {
// Leaving this intentionally empty. init blocks are extracted during class extraction.
}
is IrProperty -> extractProperty(declaration, parentId)
is IrEnumEntry -> extractEnumEntry(declaration, parentId)
is IrField -> extractField(declaration, parentId)
is IrTypeAlias -> extractTypeAlias(declaration) // TODO: Pass in and use parentId
is IrProperty -> {
@Suppress("UNCHECKED_CAST")
val parentId = useDeclarationParent(declaration.parent, false) as Label<DbReftype>
extractProperty(declaration, parentId)
}
is IrEnumEntry -> {
@Suppress("UNCHECKED_CAST")
val parentId = useDeclarationParent(declaration.parent, false) as Label<DbReftype>
extractEnumEntry(declaration, parentId)
}
is IrField -> {
@Suppress("UNCHECKED_CAST")
val parentId = useDeclarationParent(declaration.parent, false) as Label<DbReftype>
extractField(declaration, parentId)
}
is IrTypeAlias -> extractTypeAlias(declaration)
else -> logger.warnElement(Severity.ErrorSevere, "Unrecognised IrDeclaration: " + declaration.javaClass, declaration)
}
}
@@ -251,7 +267,7 @@ open class KotlinFileExtractor(
extractEnclosingClass(c, id, locId, listOf())
c.typeParameters.map { extractTypeParameter(it) }
c.declarations.map { extractDeclaration(it, id) }
c.declarations.map { extractDeclaration(it) }
extractObjectInitializerFunction(c, id)
if(c.isNonCompanionObject) {
// For `object MyObject { ... }`, the .class has an
@@ -443,9 +459,7 @@ open class KotlinFileExtractor(
if (f.isLocalFunction())
getLocallyVisibleFunctionLabels(f).function
else
// TODO: figure out whether to standardise on naming top-level functions for the file-class
// or (as temporarily done here) for their containing package.
useFunction<DbCallable>(f, if (f.parent is IrFile) useDeclarationParent(f.parent) else parentId)
useFunction<DbCallable>(f, parentId)
val extReceiver = f.extensionReceiverParameter
val idxOffset = if (extReceiver != null) 1 else 0
@@ -2499,7 +2513,8 @@ open class KotlinFileExtractor(
if (parent is IrFile) {
if (this is KotlinSourceFileExtractor && this.file == parent) {
tw.writeEnclInReftype(id, this.fileClass)
val fileId = extractFileClass(parent)
tw.writeEnclInReftype(id, fileId)
} else {
logger.warn(Severity.ErrorSevere, "Unexpected file parent found")
}

View File

@@ -22,54 +22,13 @@ class KotlinSourceFileExtractor(
) :
KotlinFileExtractor(logger, tw, null, externalClassExtractor, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted) {
val fileClass by lazy {
extractFileClass(file)
}
fun extractFileContents(id: Label<DbFile>) {
val locId = tw.getWholeFileLocation()
val pkg = file.fqName.asString()
val pkgId = extractPackage(pkg)
tw.writeHasLocation(id, locId)
tw.writeCupackage(id, pkgId)
// TODO: Use of fileClass looks like it will defeat laziness since 3502e5c5720e981c913bdafdccf7f5e9237be070
// TODO: Fix, then reenable consistency-queries/file_classes.ql
file.declarations.map { extractDeclaration(it, fileClass) }
file.declarations.map { extractDeclaration(it) }
CommentExtractor(this).extract()
}
@OptIn(kotlin.ExperimentalStdlibApi::class) // Annotation required by kotlin versions < 1.5
fun extractFileClass(f: IrFile): Label<out DbClass> {
val fileName = f.fileEntry.name
val pkg = f.fqName.asString()
val defaultName = fileName.replaceFirst(Regex(""".*[/\\]"""), "").replaceFirst(Regex("""\.kt$"""), "").replaceFirstChar({ it.uppercase() }) + "Kt"
var jvmName = defaultName
for(a: IrConstructorCall in f.annotations) {
val t = a.type
if(t is IrSimpleType && a.valueArgumentsCount == 1) {
val owner = t.classifier.owner
val v = a.getValueArgument(0)
if(owner is IrClass) {
val aPkg = owner.packageFqName?.asString()
val name = owner.name.asString()
if(aPkg == "kotlin.jvm" && name == "JvmName" && v is IrConst<*>) {
val value = v.value
if(value is String) {
jvmName = value
}
}
}
}
}
val qualClassName = if (pkg.isEmpty()) jvmName else "$pkg.$jvmName"
val label = "@\"class;$qualClassName\""
val id: Label<DbClass> = tw.getLabelFor(label)
val locId = tw.getWholeFileLocation()
val pkgId = extractPackage(pkg)
tw.writeClasses(id, jvmName, pkgId, id)
tw.writeFile_class(id)
tw.writeHasLocation(id, locId)
return id
}
}

View File

@@ -7,6 +7,8 @@ import org.jetbrains.kotlin.backend.jvm.codegen.isRawType
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.*
@@ -39,6 +41,42 @@ open class KotlinUsesExtractor(
return id
}
@OptIn(kotlin.ExperimentalStdlibApi::class) // Annotation required by kotlin versions < 1.5
fun extractFileClass(f: IrFile): Label<out DbClass> {
val fileName = f.fileEntry.name
val pkg = f.fqName.asString()
val defaultName = fileName.replaceFirst(Regex(""".*[/\\]"""), "").replaceFirst(Regex("""\.kt$"""), "").replaceFirstChar({ it.uppercase() }) + "Kt"
var jvmName = defaultName
for(a: IrConstructorCall in f.annotations) {
val t = a.type
if(t is IrSimpleType && a.valueArgumentsCount == 1) {
val owner = t.classifier.owner
val v = a.getValueArgument(0)
if(owner is IrClass) {
val aPkg = owner.packageFqName?.asString()
val name = owner.name.asString()
if(aPkg == "kotlin.jvm" && name == "JvmName" && v is IrConst<*>) {
val value = v.value
if(value is String) {
jvmName = value
}
}
}
}
}
val qualClassName = if (pkg.isEmpty()) jvmName else "$pkg.$jvmName"
val label = "@\"class;$qualClassName\""
val id: Label<DbClass> = tw.getLabelFor(label)
val fileId = tw.mkFileId(f.path, false)
val locId = tw.getWholeFileLocation(fileId)
val pkgId = extractPackage(pkg)
tw.writeClasses(id, jvmName, pkgId, id)
tw.writeFile_class(id)
tw.writeHasLocation(id, locId)
return id
}
data class UseClassInstanceResult(val typeResult: TypeResult<DbClassorinterface>, val javaClass: IrClass)
/**
* A triple of a type's database label, its signature for use in callable signatures, and its short name for use
@@ -481,9 +519,24 @@ class X {
}
}
fun useDeclarationParent(dp: IrDeclarationParent, classTypeArguments: List<IrTypeArgument>? = null, inReceiverContext: Boolean = false): Label<out DbElement> =
fun useDeclarationParent(
// The declaration parent according to Kotlin
dp: IrDeclarationParent,
// Whether the type of entity whose parent this is can be a
// top-level entity in the JVM's eyes. If so, then its parent may
// be a file; otherwise, if dp is a file foo.kt, then the parent
// is really the JVM class FooKt.
canBeTopLevel: Boolean,
classTypeArguments: List<IrTypeArgument>? = null,
inReceiverContext: Boolean = false):
Label<out DbElement> =
when(dp) {
is IrFile -> usePackage(dp.fqName.asString())
is IrFile ->
if(canBeTopLevel) {
usePackage(dp.fqName.asString())
} else {
extractFileClass(dp)
}
is IrClass -> if (classTypeArguments != null && !dp.isAnonymousObject) useClassInstance(dp, classTypeArguments, inReceiverContext).typeResult.id else useClassSource(dp)
is IrFunction -> useFunction(dp)
else -> {
@@ -525,7 +578,7 @@ class X {
extensionReceiverParameter: IrValueParameter?,
classTypeArguments: List<IrTypeArgument>? = null
): String {
val parentId = useDeclarationParent(parent, classTypeArguments, true)
val parentId = useDeclarationParent(parent, false, classTypeArguments, true)
return getFunctionLabel(parentId, name, parameters, returnType, extensionReceiverParameter)
}
@@ -726,7 +779,7 @@ class X {
}
fun getTypeParameterLabel(param: IrTypeParameter): String {
val parentLabel = useDeclarationParent(param.parent)
val parentLabel = useDeclarationParent(param.parent, false)
return "@\"typevar;{$parentLabel};${param.name}\""
}
@@ -844,7 +897,7 @@ class X {
* `parent` is null.
*/
fun getValueParameterLabel(vp: IrValueParameter, parent: Label<out DbCallable>?): String {
val parentId = parent ?: useDeclarationParent(vp.parent)
val parentId = parent ?: useDeclarationParent(vp.parent, false)
val idx = vp.index
if (idx < 0) {
// We're not extracting this and this@TYPE parameters of functions:
@@ -858,7 +911,7 @@ class X {
tw.getLabelFor(getValueParameterLabel(vp, parent))
fun getFieldLabel(f: IrField): String {
val parentId = useDeclarationParent(f.parent)
val parentId = useDeclarationParent(f.parent, false)
return "@\"field;{$parentId};${f.name.asString()}\""
}
@@ -866,7 +919,7 @@ class X {
tw.getLabelFor(getFieldLabel(f))
fun getPropertyLabel(p: IrProperty) =
getPropertyLabel(p, useDeclarationParent(p.parent))
getPropertyLabel(p, useDeclarationParent(p.parent, false))
fun getPropertyLabel(p: IrProperty, parentId: Label<out DbElement>) =
"@\"property;{$parentId};${p.name.asString()}\""
@@ -878,7 +931,7 @@ class X {
tw.getLabelFor(getPropertyLabel(p, parentId))
fun getEnumEntryLabel(ee: IrEnumEntry): String {
val parentId = useDeclarationParent(ee.parent)
val parentId = useDeclarationParent(ee.parent, false)
return "@\"field;{$parentId};${ee.name.asString()}\""
}
@@ -886,7 +939,7 @@ class X {
tw.getLabelFor(getEnumEntryLabel(ee))
private fun getTypeAliasLabel(ta: IrTypeAlias): String {
val parentId = useDeclarationParent(ta.parent)
val parentId = useDeclarationParent(ta.parent, true)
return "@\"type_alias;{$parentId};${ta.name.asString()}\""
}

View File

@@ -130,7 +130,28 @@ open class TrapWriter (protected val lm: TrapLabelManager, private val bw: Buffe
* location.
*/
val unknownLocation: Label<DbLocation> by lazy {
getLocation(unknownFileId, 0, 0, 0, 0)
getWholeFileLocation(unknownFileId)
}
fun mkFileId(filePath: String, populateFileTables: Boolean): Label<DbFile> {
// If a file is in a jar, then the Kotlin compiler gives
// `<jar file>!/<path within jar>` as its path. We need to split
// it as appropriate, to make the right file ID.
val populateFile = PopulateFile(this)
val splitFilePath = filePath.split("!/")
if(splitFilePath.size == 1) {
return populateFile.getFileLabel(File(filePath), populateFileTables)
} else {
return populateFile.getFileInJarLabel(File(splitFilePath.get(0)), splitFilePath.get(1), populateFileTables)
}
}
/**
* If you have an ID for a file, then this gets a label for the
* location representing the whole of that file.
*/
fun getWholeFileLocation(fileId: Label<DbFile>): Label<DbLocation> {
return getLocation(fileId, 0, 0, 0, 0)
}
/**
@@ -180,16 +201,7 @@ open class FileTrapWriter (
val filePath: String,
populateFileTables: Boolean
): TrapWriter (lm, bw) {
// If a file is in a jar, then the Kotlin compiler gives
// `<jar file>!/<path within jar>` as its path. We need to split
// it as appropriate, to make the right file ID.
val populateFile = PopulateFile(this)
val splitFilePath = filePath.split("!/")
val fileId =
if(splitFilePath.size == 1)
populateFile.getFileLabel(File(filePath), populateFileTables)
else
populateFile.getFileInJarLabel(File(splitFilePath.get(0)), splitFilePath.get(1), populateFileTables)
val fileId = mkFileId(filePath, populateFileTables)
/**
* Gets a label for the location of `e`.
@@ -201,7 +213,7 @@ open class FileTrapWriter (
* Gets a label for the location representing the whole of this file.
*/
fun getWholeFileLocation(): Label<DbLocation> {
return getLocation(fileId, 0, 0, 0, 0)
return getWholeFileLocation(fileId)
}
/**
* Gets a label for the location corresponding to `startOffset` and