mirror of
https://github.com/github/codeql.git
synced 2025-12-22 11:46:32 +01:00
1721 lines
83 KiB
Kotlin
1721 lines
83 KiB
Kotlin
package com.github.codeql
|
|
|
|
import com.github.codeql.utils.*
|
|
import com.github.codeql.utils.versions.codeQlWithHasQuestionMark
|
|
import com.github.codeql.utils.versions.isRawType
|
|
import com.semmle.extractor.java.OdasaOutput
|
|
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
|
import org.jetbrains.kotlin.backend.common.ir.*
|
|
import org.jetbrains.kotlin.backend.common.lower.parents
|
|
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
|
|
import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor
|
|
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
|
|
import org.jetbrains.kotlin.descriptors.*
|
|
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
|
import org.jetbrains.kotlin.ir.declarations.*
|
|
import org.jetbrains.kotlin.ir.expressions.*
|
|
import org.jetbrains.kotlin.ir.symbols.*
|
|
import org.jetbrains.kotlin.ir.types.*
|
|
import org.jetbrains.kotlin.ir.types.impl.*
|
|
import org.jetbrains.kotlin.ir.util.*
|
|
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
|
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
|
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
|
|
import org.jetbrains.kotlin.load.java.structure.*
|
|
import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor
|
|
import org.jetbrains.kotlin.name.FqName
|
|
import org.jetbrains.kotlin.name.NameUtils
|
|
import org.jetbrains.kotlin.name.SpecialNames
|
|
import org.jetbrains.kotlin.types.Variance
|
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
|
|
|
open class KotlinUsesExtractor(
|
|
open val logger: Logger,
|
|
open val tw: TrapWriter,
|
|
val dependencyCollector: OdasaOutput.TrapFileManager?,
|
|
val externalClassExtractor: ExternalDeclExtractor,
|
|
val primitiveTypeMapping: PrimitiveTypeMapping,
|
|
val pluginContext: IrPluginContext,
|
|
val globalExtensionState: KotlinExtractorGlobalState
|
|
) {
|
|
val javaLangObject by lazy {
|
|
val result = pluginContext.referenceClass(FqName("java.lang.Object"))?.owner
|
|
result?.let { extractExternalClassLater(it) }
|
|
result
|
|
}
|
|
|
|
val javaLangObjectType by lazy {
|
|
javaLangObject?.typeWith()
|
|
}
|
|
|
|
private fun usePackage(pkg: String): Label<out DbPackage> {
|
|
return extractPackage(pkg)
|
|
}
|
|
|
|
fun extractPackage(pkg: String): Label<out DbPackage> {
|
|
val pkgLabel = "@\"package;$pkg\""
|
|
val id: Label<DbPackage> = tw.getLabelFor(pkgLabel, {
|
|
tw.writePackages(it, pkg)
|
|
})
|
|
return id
|
|
}
|
|
|
|
fun useFileClassType(f: IrFile) = TypeResults(
|
|
TypeResult(extractFileClass(f), "", ""),
|
|
TypeResult(fakeKotlinType(), "", "")
|
|
)
|
|
|
|
@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 = getJvmName(f) ?: defaultName
|
|
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(it, jvmName, pkgId, it)
|
|
tw.writeFile_class(it)
|
|
tw.writeHasLocation(it, locId)
|
|
|
|
addModifiers(it, "public", "final")
|
|
})
|
|
return id
|
|
}
|
|
|
|
data class UseClassInstanceResult(val typeResult: TypeResult<DbClassorinterface>, val javaClass: IrClass)
|
|
|
|
fun useType(t: IrType, context: TypeContext = TypeContext.OTHER): TypeResults {
|
|
when(t) {
|
|
is IrSimpleType -> return useSimpleType(t, context)
|
|
else -> {
|
|
logger.error("Unrecognised IrType: " + t.javaClass)
|
|
return extractErrorType()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun extractJavaErrorType(): TypeResult<DbErrortype> {
|
|
val typeId = tw.getLabelFor<DbErrortype>("@\"errorType\"") {
|
|
tw.writeError_type(it)
|
|
}
|
|
return TypeResult(typeId, "<CodeQL error type>", "<CodeQL error type>")
|
|
}
|
|
|
|
private fun extractErrorType(): TypeResults {
|
|
val javaResult = extractJavaErrorType()
|
|
val kotlinTypeId = tw.getLabelFor<DbKt_nullable_type>("@\"errorKotlinType\"") {
|
|
tw.writeKt_nullable_types(it, javaResult.id)
|
|
}
|
|
return TypeResults(javaResult,
|
|
TypeResult(kotlinTypeId, "<CodeQL error type>", "<CodeQL error type>"))
|
|
}
|
|
|
|
fun getJavaEquivalentClass(c: IrClass) =
|
|
getJavaEquivalentClassId(c)?.let { pluginContext.referenceClass(it.asSingleFqName()) }?.owner
|
|
|
|
/**
|
|
* Gets a KotlinFileExtractor based on this one, except it attributes locations to the file that declares the given class.
|
|
*/
|
|
private fun withFileOfClass(cls: IrClass): KotlinFileExtractor {
|
|
val clsFile = cls.fileOrNull
|
|
|
|
if (this is KotlinFileExtractor && this.filePath == clsFile?.path) {
|
|
return this
|
|
}
|
|
|
|
val newDeclarationStack =
|
|
if (this is KotlinFileExtractor)
|
|
this.declarationStack
|
|
else
|
|
KotlinFileExtractor.DeclarationStack()
|
|
|
|
if (clsFile == null || isExternalDeclaration(cls)) {
|
|
val filePath = getIrClassBinaryPath(cls)
|
|
val newTrapWriter = tw.makeFileTrapWriter(filePath, true)
|
|
val newLoggerTrapWriter = logger.tw.makeFileTrapWriter(filePath, false)
|
|
val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter)
|
|
return KotlinFileExtractor(newLogger, newTrapWriter, null, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
|
|
}
|
|
|
|
val newTrapWriter = tw.makeSourceFileTrapWriter(clsFile, true)
|
|
val newLoggerTrapWriter = logger.tw.makeSourceFileTrapWriter(clsFile, false)
|
|
val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter)
|
|
return KotlinFileExtractor(newLogger, newTrapWriter, null, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
|
|
}
|
|
|
|
// The Kotlin compiler internal representation of Outer<T>.Inner<S>.InnerInner<R> is InnerInner<R, S, T>. This function returns just `R`.
|
|
fun removeOuterClassTypeArgs(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): List<IrTypeArgument>? {
|
|
return argsIncludingOuterClasses?.let {
|
|
if (it.size > c.typeParameters.size)
|
|
it.take(c.typeParameters.size)
|
|
else
|
|
null
|
|
} ?: argsIncludingOuterClasses
|
|
}
|
|
|
|
private fun isStaticClass(c: IrClass) = c.visibility != DescriptorVisibilities.LOCAL && !c.isInner
|
|
|
|
// Gets nested inner classes starting at `c` and proceeding outwards to the innermost enclosing static class.
|
|
// For example, for (java syntax) `class A { static class B { class C { class D { } } } }`,
|
|
// `nonStaticParentsWithSelf(D)` = `[D, C, B]`.
|
|
private fun parentsWithTypeParametersInScope(c: IrClass): List<IrDeclarationParent> {
|
|
val parentsList = c.parentsWithSelf.toList()
|
|
val firstOuterClassIdx = parentsList.indexOfFirst { it is IrClass && isStaticClass(it) }
|
|
return if (firstOuterClassIdx == -1) parentsList else parentsList.subList(0, firstOuterClassIdx + 1)
|
|
}
|
|
|
|
// Gets the type parameter symbols that are in scope for class `c` in Kotlin order (i.e. for
|
|
// `class NotInScope<T> { static class OutermostInScope<A, B> { class QueryClass<C, D> { } } }`,
|
|
// `getTypeParametersInScope(QueryClass)` = `[C, D, A, B]`.
|
|
private fun getTypeParametersInScope(c: IrClass) =
|
|
parentsWithTypeParametersInScope(c).mapNotNull({ getTypeParameters(it) }).flatten()
|
|
|
|
// Returns a map from `c`'s type variables in scope to type arguments `argsIncludingOuterClasses`.
|
|
// Hack for the time being: the substituted types are always nullable, to prevent downstream code
|
|
// from replacing a generic parameter by a primitive. As and when we extract Kotlin types we will
|
|
// need to track this information in more detail.
|
|
private fun makeTypeGenericSubstitutionMap(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>) =
|
|
getTypeParametersInScope(c).map({ it.symbol }).zip(argsIncludingOuterClasses.map { it.withQuestionMark(true) }).toMap()
|
|
|
|
fun makeGenericSubstitutionFunction(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>) =
|
|
makeTypeGenericSubstitutionMap(c, argsIncludingOuterClasses).let {
|
|
{ x: IrType, useContext: TypeContext, pluginContext: IrPluginContext ->
|
|
x.substituteTypeAndArguments(
|
|
it,
|
|
useContext,
|
|
pluginContext
|
|
)
|
|
}
|
|
}
|
|
|
|
// The Kotlin compiler internal representation of Outer<A, B>.Inner<C, D>.InnerInner<E, F>.someFunction<G, H>.LocalClass<I, J> is LocalClass<I, J, G, H, E, F, C, D, A, B>. This function returns [A, B, C, D, E, F, G, H, I, J].
|
|
private fun orderTypeArgsLeftToRight(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): List<IrTypeArgument>? {
|
|
if(argsIncludingOuterClasses.isNullOrEmpty())
|
|
return argsIncludingOuterClasses
|
|
val ret = ArrayList<IrTypeArgument>()
|
|
// Iterate over nested inner classes starting at `c`'s surrounding top-level or static nested class and ending at `c`, from the outermost inwards:
|
|
val truncatedParents = parentsWithTypeParametersInScope(c)
|
|
for(parent in truncatedParents.reversed()) {
|
|
val parentTypeParameters = getTypeParameters(parent)
|
|
val firstArgIdx = argsIncludingOuterClasses.size - (ret.size + parentTypeParameters.size)
|
|
ret.addAll(argsIncludingOuterClasses.subList(firstArgIdx, firstArgIdx + parentTypeParameters.size))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// `typeArgs` can be null to describe a raw generic type.
|
|
// For non-generic types it will be zero-length list.
|
|
fun useClassInstance(c: IrClass, typeArgs: List<IrTypeArgument>?, inReceiverContext: Boolean = false): UseClassInstanceResult {
|
|
val substituteClass = getJavaEquivalentClass(c)
|
|
|
|
val extractClass = substituteClass ?: c
|
|
|
|
// `KFunction1<T1,T2>` is substituted by `KFunction<T>`. The last type argument is the return type.
|
|
// Similarly Function23 and above get replaced by kotlin.jvm.functions.FunctionN with only one type arg, the result type.
|
|
// References to SomeGeneric<T1, T2, ...> where SomeGeneric is declared SomeGeneric<T1, T2, ...> are extracted
|
|
// as if they were references to the unbound type SomeGeneric.
|
|
val extractedTypeArgs = when {
|
|
extractClass.symbol.isKFunction() && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last())
|
|
extractClass.fqNameWhenAvailable == FqName("kotlin.jvm.functions.FunctionN") && typeArgs != null && typeArgs.isNotEmpty() -> listOf(typeArgs.last())
|
|
typeArgs != null && isUnspecialised(c, typeArgs, logger) -> listOf()
|
|
else -> typeArgs
|
|
}
|
|
|
|
val classTypeResult = addClassLabel(extractClass, extractedTypeArgs, inReceiverContext)
|
|
|
|
// Extract both the Kotlin and equivalent Java classes, so that we have database entries
|
|
// for both even if all internal references to the Kotlin type are substituted.
|
|
if(c != extractClass) {
|
|
extractClassLaterIfExternal(c)
|
|
}
|
|
|
|
return UseClassInstanceResult(classTypeResult, extractClass)
|
|
}
|
|
|
|
private fun isArray(t: IrSimpleType) = t.isBoxedArray || t.isPrimitiveArray()
|
|
|
|
private fun extractClassLaterIfExternal(c: IrClass) {
|
|
if (isExternalDeclaration(c)) {
|
|
extractExternalClassLater(c)
|
|
}
|
|
}
|
|
|
|
private fun extractExternalEnclosingClassLater(d: IrDeclaration) {
|
|
when (val parent = d.parent) {
|
|
is IrClass -> extractExternalClassLater(parent)
|
|
is IrFunction -> extractExternalEnclosingClassLater(parent)
|
|
is IrFile -> logger.error("extractExternalEnclosingClassLater but no enclosing class.")
|
|
else -> logger.error("Unrecognised extractExternalEnclosingClassLater: " + d.javaClass)
|
|
}
|
|
}
|
|
|
|
private fun extractPropertyLaterIfExternalFileMember(p: IrProperty) {
|
|
if (isExternalFileClassMember(p)) {
|
|
extractExternalClassLater(p.parentAsClass)
|
|
dependencyCollector?.addDependency(p, externalClassExtractor.propertySignature)
|
|
externalClassExtractor.extractLater(p)
|
|
}
|
|
}
|
|
|
|
private fun extractFieldLaterIfExternalFileMember(f: IrField) {
|
|
if (isExternalFileClassMember(f)) {
|
|
extractExternalClassLater(f.parentAsClass)
|
|
dependencyCollector?.addDependency(f, externalClassExtractor.fieldSignature)
|
|
externalClassExtractor.extractLater(f)
|
|
}
|
|
}
|
|
|
|
private fun extractFunctionLaterIfExternalFileMember(f: IrFunction) {
|
|
if (isExternalFileClassMember(f)) {
|
|
extractExternalClassLater(f.parentAsClass)
|
|
(f as? IrSimpleFunction)?.correspondingPropertySymbol?.let {
|
|
extractPropertyLaterIfExternalFileMember(it.owner)
|
|
// No need to extract the function specifically, as the property's
|
|
// getters and setters are extracted alongside it
|
|
return
|
|
}
|
|
// Note we erase the parameter types before calling useType even though the signature should be the same
|
|
// in order to prevent an infinite loop through useTypeParameter -> useDeclarationParent -> useFunction
|
|
// -> extractFunctionLaterIfExternalFileMember, which would result for `fun <T> f(t: T) { ... }` for example.
|
|
val ext = f.extensionReceiverParameter
|
|
val parameters = if (ext != null) {
|
|
listOf(ext) + f.valueParameters
|
|
} else {
|
|
f.valueParameters
|
|
}
|
|
|
|
val paramSigs = parameters.map { useType(erase(it.type)).javaResult.signature }
|
|
val signature = paramSigs.joinToString(separator = ",", prefix = "(", postfix = ")")
|
|
dependencyCollector?.addDependency(f, signature)
|
|
externalClassExtractor.extractLater(f, signature)
|
|
}
|
|
}
|
|
|
|
fun extractExternalClassLater(c: IrClass) {
|
|
dependencyCollector?.addDependency(c)
|
|
externalClassExtractor.extractLater(c)
|
|
}
|
|
|
|
private fun tryReplaceAndroidSyntheticClass(c: IrClass): IrClass {
|
|
// The Android Kotlin Extensions Gradle plugin introduces synthetic functions, fields and classes. The most
|
|
// obvious signature is that they lack any supertype information even though they are not root classes.
|
|
// If possible, replace them by a real version of the same class.
|
|
if (c.superTypes.isNotEmpty() ||
|
|
c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB ||
|
|
c.hasEqualFqName(FqName("java.lang.Object")))
|
|
return c
|
|
return globalExtensionState.syntheticToRealClassMap.getOrPut(c) {
|
|
val qualifiedName = c.fqNameWhenAvailable
|
|
if (qualifiedName == null) {
|
|
logger.warn("Failed to replace synthetic class ${c.name} because it has no fully qualified name")
|
|
return@getOrPut null
|
|
}
|
|
|
|
val result = pluginContext.referenceClass(qualifiedName)?.owner
|
|
if (result != null) {
|
|
logger.info("Replaced synthetic class ${c.name} with its real equivalent")
|
|
return@getOrPut result
|
|
}
|
|
|
|
// The above doesn't work for (some) generated nested classes, such as R$id, which should be R.id
|
|
val fqn = qualifiedName.asString()
|
|
if (fqn.indexOf('$') >= 0) {
|
|
val nested = pluginContext.referenceClass(FqName(fqn.replace('$', '.')))?.owner
|
|
if (nested != null) {
|
|
logger.info("Replaced synthetic nested class ${c.name} with its real equivalent")
|
|
return@getOrPut nested
|
|
}
|
|
}
|
|
|
|
logger.warn("Failed to replace synthetic class ${c.name}")
|
|
return@getOrPut null
|
|
} ?: c
|
|
}
|
|
|
|
private fun tryReplaceFunctionInSyntheticClass(f: IrFunction, getClassReplacement: (IrClass) -> IrClass): IrFunction {
|
|
val parentClass = f.parent as? IrClass ?: return f
|
|
val replacementClass = getClassReplacement(parentClass)
|
|
if (replacementClass === parentClass)
|
|
return f
|
|
return globalExtensionState.syntheticToRealFunctionMap.getOrPut(f) {
|
|
val result = replacementClass.declarations.findSubType<IrSimpleFunction> { replacementDecl ->
|
|
replacementDecl.name == f.name && replacementDecl.valueParameters.size == f.valueParameters.size && replacementDecl.valueParameters.zip(f.valueParameters).all {
|
|
erase(it.first.type) == erase(it.second.type)
|
|
}
|
|
}
|
|
if (result == null) {
|
|
logger.warn("Failed to replace synthetic class function ${f.name}")
|
|
} else {
|
|
logger.info("Replaced synthetic class function ${f.name} with its real equivalent")
|
|
}
|
|
result
|
|
} ?: f
|
|
}
|
|
|
|
fun tryReplaceSyntheticFunction(f: IrFunction): IrFunction {
|
|
val androidReplacement = tryReplaceFunctionInSyntheticClass(f) { tryReplaceAndroidSyntheticClass(it) }
|
|
return tryReplaceFunctionInSyntheticClass(androidReplacement) { tryReplaceParcelizeRawType(it)?.first ?: it }
|
|
}
|
|
|
|
fun tryReplaceAndroidSyntheticField(f: IrField): IrField {
|
|
val parentClass = f.parent as? IrClass ?: return f
|
|
val replacementClass = tryReplaceAndroidSyntheticClass(parentClass)
|
|
if (replacementClass === parentClass)
|
|
return f
|
|
return globalExtensionState.syntheticToRealFieldMap.getOrPut(f) {
|
|
val result = replacementClass.declarations.findSubType<IrField> { replacementDecl -> replacementDecl.name == f.name }
|
|
?: replacementClass.declarations.findSubType<IrProperty> { it.backingField?.name == f.name}?.backingField
|
|
if (result == null) {
|
|
logger.warn("Failed to replace synthetic class field ${f.name}")
|
|
} else {
|
|
logger.info("Replaced synthetic class field ${f.name} with its real equivalent")
|
|
}
|
|
result
|
|
} ?: f
|
|
}
|
|
|
|
private fun tryReplaceType(cBeforeReplacement: IrClass, argsIncludingOuterClassesBeforeReplacement: List<IrTypeArgument>?): Pair<IrClass, List<IrTypeArgument>?> {
|
|
val c = tryReplaceAndroidSyntheticClass(cBeforeReplacement)
|
|
val p = tryReplaceParcelizeRawType(c)
|
|
return Pair(
|
|
p?.first ?: c,
|
|
p?.second ?: argsIncludingOuterClassesBeforeReplacement
|
|
)
|
|
}
|
|
|
|
// `typeArgs` can be null to describe a raw generic type.
|
|
// For non-generic types it will be zero-length list.
|
|
private fun addClassLabel(cBeforeReplacement: IrClass, argsIncludingOuterClassesBeforeReplacement: List<IrTypeArgument>?, inReceiverContext: Boolean = false): TypeResult<DbClassorinterface> {
|
|
val replaced = tryReplaceType(cBeforeReplacement, argsIncludingOuterClassesBeforeReplacement)
|
|
val replacedClass = replaced.first
|
|
val replacedArgsIncludingOuterClasses = replaced.second
|
|
|
|
val classLabelResult = getClassLabel(replacedClass, replacedArgsIncludingOuterClasses)
|
|
|
|
var instanceSeenBefore = true
|
|
|
|
val classLabel : Label<out DbClassorinterface> = tw.getLabelFor(classLabelResult.classLabel) {
|
|
instanceSeenBefore = false
|
|
|
|
extractClassLaterIfExternal(replacedClass)
|
|
}
|
|
|
|
if (replacedArgsIncludingOuterClasses == null || replacedArgsIncludingOuterClasses.isNotEmpty()) {
|
|
// If this is a generic type instantiation or a raw type then it has no
|
|
// source entity, so we need to extract it here
|
|
val shouldExtractClassDetails = inReceiverContext && tw.lm.genericSpecialisationsExtracted.add(classLabelResult.classLabel)
|
|
if (!instanceSeenBefore || shouldExtractClassDetails) {
|
|
this.withFileOfClass(replacedClass).extractClassInstance(classLabel, replacedClass, replacedArgsIncludingOuterClasses, !instanceSeenBefore, shouldExtractClassDetails)
|
|
}
|
|
}
|
|
|
|
val fqName = replacedClass.fqNameWhenAvailable
|
|
val signature = if (replacedClass.isAnonymousObject) {
|
|
null
|
|
} else if (fqName == null) {
|
|
logger.error("Unable to find signature/fqName for ${replacedClass.name}")
|
|
null
|
|
} else {
|
|
fqName.asString()
|
|
}
|
|
return TypeResult(
|
|
classLabel,
|
|
signature,
|
|
classLabelResult.shortName)
|
|
}
|
|
|
|
private fun tryReplaceParcelizeRawType(c: IrClass): Pair<IrClass, List<IrTypeArgument>?>? {
|
|
if (c.superTypes.isNotEmpty() ||
|
|
c.origin != IrDeclarationOrigin.DEFINED ||
|
|
c.hasEqualFqName(FqName("java.lang.Object"))) {
|
|
return null
|
|
}
|
|
|
|
val fqName = c.fqNameWhenAvailable
|
|
if (fqName == null) {
|
|
return null
|
|
}
|
|
|
|
fun tryGetPair(arity: Int): Pair<IrClass, List<IrTypeArgument>?>? {
|
|
val replaced = pluginContext.referenceClass(fqName)?.owner ?: return null
|
|
return Pair(replaced, List(arity) { makeTypeProjection(pluginContext.irBuiltIns.anyNType, Variance.INVARIANT) })
|
|
}
|
|
|
|
// The list of types handled here match https://github.com/JetBrains/kotlin/blob/d7c7d1efd2c0983c13b175e9e4b1cda979521159/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt
|
|
// Specifically, types are added for generic types created in AndroidSymbols.kt.
|
|
// This replacement is from a raw type to its matching parameterized type with `Object` type arguments.
|
|
return when (fqName.asString()) {
|
|
"java.util.ArrayList" -> tryGetPair(1)
|
|
"java.util.LinkedHashMap" -> tryGetPair(2)
|
|
"java.util.LinkedHashSet" -> tryGetPair(1)
|
|
"java.util.List" -> tryGetPair(1)
|
|
"java.util.TreeMap" -> tryGetPair(2)
|
|
"java.util.TreeSet" -> tryGetPair(1)
|
|
|
|
"java.lang.Class" -> tryGetPair(1)
|
|
|
|
else -> null
|
|
}
|
|
}
|
|
|
|
private fun useAnonymousClass(c: IrClass) =
|
|
tw.lm.anonymousTypeMapping.getOrPut(c) {
|
|
TypeResults(
|
|
TypeResult(tw.getFreshIdLabel<DbClass>(), "", ""),
|
|
TypeResult(fakeKotlinType(), "TODO", "TODO")
|
|
)
|
|
}
|
|
|
|
fun getExistingAnonymousClassLabel(c: IrClass): Label<out DbType>? {
|
|
if (!c.isAnonymousObject){
|
|
return null
|
|
}
|
|
|
|
return tw.lm.anonymousTypeMapping[c]?.javaResult?.id
|
|
}
|
|
|
|
fun fakeKotlinType(): Label<out DbKt_type> {
|
|
val fakeKotlinPackageId: Label<DbPackage> = tw.getLabelFor("@\"FakeKotlinPackage\"", {
|
|
tw.writePackages(it, "fake.kotlin")
|
|
})
|
|
val fakeKotlinClassId: Label<DbClass> = tw.getLabelFor("@\"FakeKotlinClass\"", {
|
|
tw.writeClasses(it, "FakeKotlinClass", fakeKotlinPackageId, it)
|
|
})
|
|
val fakeKotlinTypeId: Label<DbKt_nullable_type> = tw.getLabelFor("@\"FakeKotlinType\"", {
|
|
tw.writeKt_nullable_types(it, fakeKotlinClassId)
|
|
})
|
|
return fakeKotlinTypeId
|
|
}
|
|
|
|
// `args` can be null to describe a raw generic type.
|
|
// For non-generic types it will be zero-length list.
|
|
fun useSimpleTypeClass(c: IrClass, args: List<IrTypeArgument>?, hasQuestionMark: Boolean): TypeResults {
|
|
val classInstanceResult = useClassInstance(c, args)
|
|
val javaClassId = classInstanceResult.typeResult.id
|
|
val kotlinQualClassName = getUnquotedClassLabel(c, args).classLabel
|
|
val javaResult = classInstanceResult.typeResult
|
|
val kotlinResult = if (true) TypeResult(fakeKotlinType(), "TODO", "TODO") else
|
|
if (hasQuestionMark) {
|
|
val kotlinSignature = "$kotlinQualClassName?" // TODO: Is this right?
|
|
val kotlinLabel = "@\"kt_type;nullable;$kotlinQualClassName\""
|
|
val kotlinId: Label<DbKt_nullable_type> = tw.getLabelFor(kotlinLabel, {
|
|
tw.writeKt_nullable_types(it, javaClassId)
|
|
})
|
|
TypeResult(kotlinId, kotlinSignature, "TODO")
|
|
} else {
|
|
val kotlinSignature = kotlinQualClassName // TODO: Is this right?
|
|
val kotlinLabel = "@\"kt_type;notnull;$kotlinQualClassName\""
|
|
val kotlinId: Label<DbKt_notnull_type> = tw.getLabelFor(kotlinLabel, {
|
|
tw.writeKt_notnull_types(it, javaClassId)
|
|
})
|
|
TypeResult(kotlinId, kotlinSignature, "TODO")
|
|
}
|
|
return TypeResults(javaResult, kotlinResult)
|
|
}
|
|
|
|
// Given either a primitive array or a boxed array, returns primitive arrays unchanged,
|
|
// but returns boxed arrays with a nullable, invariant component type, with any nested arrays
|
|
// similarly transformed. For example, Array<out Array<in E>> would become Array<Array<E?>?>
|
|
// Array<*> will become Array<Any?>.
|
|
private fun getInvariantNullableArrayType(arrayType: IrSimpleType): IrSimpleType =
|
|
if (arrayType.isPrimitiveArray())
|
|
arrayType
|
|
else {
|
|
val componentType = arrayType.getArrayElementType(pluginContext.irBuiltIns)
|
|
val componentTypeBroadened = when (componentType) {
|
|
is IrSimpleType ->
|
|
if (isArray(componentType)) getInvariantNullableArrayType(componentType) else componentType
|
|
else -> componentType
|
|
}
|
|
val unchanged =
|
|
componentType == componentTypeBroadened &&
|
|
(arrayType.arguments[0] as? IrTypeProjection)?.variance == Variance.INVARIANT &&
|
|
componentType.isNullable()
|
|
if (unchanged)
|
|
arrayType
|
|
else
|
|
IrSimpleTypeImpl(
|
|
arrayType.classifier,
|
|
true,
|
|
listOf(makeTypeProjection(componentTypeBroadened, Variance.INVARIANT)),
|
|
listOf()
|
|
)
|
|
}
|
|
|
|
data class ArrayInfo(val elementTypeResults: TypeResults,
|
|
val componentTypeResults: TypeResults,
|
|
val dimensions: Int)
|
|
|
|
/**
|
|
* `t` is somewhere in a stack of array types, or possibly the
|
|
* element type of the innermost array. For example, in
|
|
* `Array<Array<Int>>`, we will be called with `t` being
|
|
* `Array<Array<Int>>`, then `Array<Int>`, then `Int`.
|
|
* `isPrimitiveArray` is true if we are immediately nested
|
|
* inside a primitive array.
|
|
*/
|
|
private fun useArrayType(t: IrType, isPrimitiveArray: Boolean): ArrayInfo {
|
|
|
|
if (!t.isBoxedArray && !t.isPrimitiveArray()) {
|
|
val nullableT = if (t.isPrimitiveType() && !isPrimitiveArray) t.makeNullable() else t
|
|
val typeResults = useType(nullableT)
|
|
return ArrayInfo(typeResults, typeResults, 0)
|
|
}
|
|
|
|
if (t !is IrSimpleType) {
|
|
logger.error("Unexpected non-simple array type: ${t.javaClass}")
|
|
return ArrayInfo(extractErrorType(), extractErrorType(), 0)
|
|
}
|
|
|
|
val arrayClass = t.classifier.owner
|
|
if (arrayClass !is IrClass) {
|
|
logger.error("Unexpected owner type for array type: ${arrayClass.javaClass}")
|
|
return ArrayInfo(extractErrorType(), extractErrorType(), 0)
|
|
}
|
|
|
|
// Because Java's arrays are covariant, Kotlin will render
|
|
// Array<in X> as Object[], Array<Array<in X>> as Object[][] etc.
|
|
val elementType = if ((t.arguments.singleOrNull() as? IrTypeProjection)?.variance == Variance.IN_VARIANCE) {
|
|
pluginContext.irBuiltIns.anyType
|
|
} else {
|
|
t.getArrayElementType(pluginContext.irBuiltIns)
|
|
}
|
|
|
|
val recInfo = useArrayType(elementType, t.isPrimitiveArray())
|
|
|
|
val javaShortName = recInfo.componentTypeResults.javaResult.shortName + "[]"
|
|
val kotlinShortName = recInfo.componentTypeResults.kotlinResult.shortName + "[]"
|
|
val elementTypeLabel = recInfo.elementTypeResults.javaResult.id
|
|
val componentTypeLabel = recInfo.componentTypeResults.javaResult.id
|
|
val dimensions = recInfo.dimensions + 1
|
|
|
|
val id = tw.getLabelFor<DbArray>("@\"array;$dimensions;{${elementTypeLabel}}\"") {
|
|
tw.writeArrays(
|
|
it,
|
|
javaShortName,
|
|
elementTypeLabel,
|
|
dimensions,
|
|
componentTypeLabel)
|
|
|
|
extractClassSupertypes(arrayClass, it, ExtractSupertypesMode.Specialised(t.arguments))
|
|
|
|
// array.length
|
|
val length = tw.getLabelFor<DbField>("@\"field;{$it};length\"")
|
|
val intTypeIds = useType(pluginContext.irBuiltIns.intType)
|
|
tw.writeFields(length, "length", intTypeIds.javaResult.id, it, length)
|
|
tw.writeFieldsKotlinType(length, intTypeIds.kotlinResult.id)
|
|
addModifiers(length, "public", "final")
|
|
|
|
// Note we will only emit one `clone()` method per Java array type, so we choose `Array<C?>` as its Kotlin
|
|
// return type, where C is the component type with any nested arrays themselves invariant and nullable.
|
|
val kotlinCloneReturnType = getInvariantNullableArrayType(t).makeNullable()
|
|
val kotlinCloneReturnTypeLabel = useType(kotlinCloneReturnType).kotlinResult.id
|
|
|
|
val clone = tw.getLabelFor<DbMethod>("@\"callable;{$it}.clone(){$it}\"")
|
|
tw.writeMethods(clone, "clone", "clone()", it, it, clone)
|
|
tw.writeMethodsKotlinType(clone, kotlinCloneReturnTypeLabel)
|
|
addModifiers(clone, "public")
|
|
}
|
|
|
|
val javaResult = TypeResult(
|
|
id,
|
|
recInfo.componentTypeResults.javaResult.signature + "[]",
|
|
javaShortName)
|
|
val kotlinResult = TypeResult(
|
|
fakeKotlinType(),
|
|
recInfo.componentTypeResults.kotlinResult.signature + "[]",
|
|
kotlinShortName)
|
|
val typeResults = TypeResults(javaResult, kotlinResult)
|
|
|
|
return ArrayInfo(recInfo.elementTypeResults, typeResults, dimensions)
|
|
}
|
|
|
|
enum class TypeContext {
|
|
RETURN, GENERIC_ARGUMENT, OTHER
|
|
}
|
|
|
|
private fun isOnDeclarationStackWithoutTypeParameters(f: IrFunction) =
|
|
this is KotlinFileExtractor && this.declarationStack.findOverriddenAttributes(f)?.typeParameters?.isEmpty() == true
|
|
|
|
private fun isStaticFunctionOnStackBeforeClass(c: IrClass) =
|
|
this is KotlinFileExtractor && (this.declarationStack.findFirst { it.first == c || it.second?.isStatic == true })?.second?.isStatic == true
|
|
|
|
private fun isUnavailableTypeParameter(t: IrType) =
|
|
t is IrSimpleType && t.classifier.owner.let { owner ->
|
|
owner is IrTypeParameter && owner.parent.let { parent ->
|
|
when (parent) {
|
|
is IrFunction -> isOnDeclarationStackWithoutTypeParameters(parent)
|
|
is IrClass -> isStaticFunctionOnStackBeforeClass(parent)
|
|
else -> false
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun argIsUnavailableTypeParameter(t: IrTypeArgument) =
|
|
t is IrTypeProjection && isUnavailableTypeParameter(t.type)
|
|
|
|
private fun useSimpleType(s: IrSimpleType, context: TypeContext): TypeResults {
|
|
if (s.abbreviation != null) {
|
|
// TODO: Extract this information
|
|
}
|
|
// We use this when we don't actually have an IrClass for a class
|
|
// we want to refer to
|
|
// TODO: Eliminate the need for this if possible
|
|
fun makeClass(pkgName: String, className: String): Label<DbClass> {
|
|
val pkgId = extractPackage(pkgName)
|
|
val label = "@\"class;$pkgName.$className\""
|
|
val classId: Label<DbClass> = tw.getLabelFor(label, {
|
|
tw.writeClasses(it, className, pkgId, it)
|
|
})
|
|
return classId
|
|
}
|
|
fun primitiveType(kotlinClass: IrClass, primitiveName: String?,
|
|
otherIsPrimitive: Boolean,
|
|
javaClass: IrClass,
|
|
kotlinPackageName: String, kotlinClassName: String): TypeResults {
|
|
val javaResult = if ((context == TypeContext.RETURN || (context == TypeContext.OTHER && otherIsPrimitive)) && !s.isNullable() && primitiveName != null) {
|
|
val label: Label<DbPrimitive> = tw.getLabelFor("@\"type;$primitiveName\"", {
|
|
tw.writePrimitives(it, primitiveName)
|
|
})
|
|
TypeResult(label, primitiveName, primitiveName)
|
|
} else {
|
|
addClassLabel(javaClass, listOf())
|
|
}
|
|
val kotlinClassId = useClassInstance(kotlinClass, listOf()).typeResult.id
|
|
val kotlinResult = if (true) TypeResult(fakeKotlinType(), "TODO", "TODO") else
|
|
if (s.isNullable()) {
|
|
val kotlinSignature = "$kotlinPackageName.$kotlinClassName?" // TODO: Is this right?
|
|
val kotlinLabel = "@\"kt_type;nullable;$kotlinPackageName.$kotlinClassName\""
|
|
val kotlinId: Label<DbKt_nullable_type> = tw.getLabelFor(kotlinLabel, {
|
|
tw.writeKt_nullable_types(it, kotlinClassId)
|
|
})
|
|
TypeResult(kotlinId, kotlinSignature, "TODO")
|
|
} else {
|
|
val kotlinSignature = "$kotlinPackageName.$kotlinClassName" // TODO: Is this right?
|
|
val kotlinLabel = "@\"kt_type;notnull;$kotlinPackageName.$kotlinClassName\""
|
|
val kotlinId: Label<DbKt_notnull_type> = tw.getLabelFor(kotlinLabel, {
|
|
tw.writeKt_notnull_types(it, kotlinClassId)
|
|
})
|
|
TypeResult(kotlinId, kotlinSignature, "TODO")
|
|
}
|
|
return TypeResults(javaResult, kotlinResult)
|
|
}
|
|
|
|
val owner = s.classifier.owner
|
|
val primitiveInfo = primitiveTypeMapping.getPrimitiveInfo(s)
|
|
|
|
when {
|
|
primitiveInfo != null -> {
|
|
if (owner is IrClass) {
|
|
return primitiveType(
|
|
owner,
|
|
primitiveInfo.primitiveName, primitiveInfo.otherIsPrimitive,
|
|
primitiveInfo.javaClass,
|
|
primitiveInfo.kotlinPackageName, primitiveInfo.kotlinClassName
|
|
)
|
|
} else {
|
|
logger.error("Got primitive info for non-class (${owner.javaClass}) for ${s.render()}")
|
|
return extractErrorType()
|
|
}
|
|
}
|
|
|
|
(s.isBoxedArray && s.arguments.isNotEmpty()) || s.isPrimitiveArray() -> {
|
|
val arrayInfo = useArrayType(s, false)
|
|
return arrayInfo.componentTypeResults
|
|
}
|
|
|
|
owner is IrClass -> {
|
|
val args = if (s.isRawType() || s.arguments.any { argIsUnavailableTypeParameter(it) }) null else s.arguments
|
|
|
|
return useSimpleTypeClass(owner, args, s.isNullable())
|
|
}
|
|
owner is IrTypeParameter -> {
|
|
if (isUnavailableTypeParameter(s))
|
|
return useType(erase(s), context)
|
|
val javaResult = useTypeParameter(owner)
|
|
val aClassId = makeClass("kotlin", "TypeParam") // TODO: Wrong
|
|
val kotlinResult = if (true) TypeResult(fakeKotlinType(), "TODO", "TODO") else
|
|
if (s.isNullable()) {
|
|
val kotlinSignature = "${javaResult.signature}?" // TODO: Wrong
|
|
val kotlinLabel = "@\"kt_type;nullable;type_param\"" // TODO: Wrong
|
|
val kotlinId: Label<DbKt_nullable_type> = tw.getLabelFor(kotlinLabel, {
|
|
tw.writeKt_nullable_types(it, aClassId)
|
|
})
|
|
TypeResult(kotlinId, kotlinSignature, "TODO")
|
|
} else {
|
|
val kotlinSignature = javaResult.signature // TODO: Wrong
|
|
val kotlinLabel = "@\"kt_type;notnull;type_param\"" // TODO: Wrong
|
|
val kotlinId: Label<DbKt_notnull_type> = tw.getLabelFor(kotlinLabel, {
|
|
tw.writeKt_notnull_types(it, aClassId)
|
|
})
|
|
TypeResult(kotlinId, kotlinSignature, "TODO")
|
|
}
|
|
return TypeResults(javaResult, kotlinResult)
|
|
}
|
|
else -> {
|
|
logger.error("Unrecognised IrSimpleType: " + s.javaClass + ": " + s.render())
|
|
return extractErrorType()
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ->
|
|
if(canBeTopLevel) {
|
|
usePackage(dp.fqName.asString())
|
|
} else {
|
|
extractFileClass(dp)
|
|
}
|
|
is IrClass ->
|
|
if (classTypeArguments != null) {
|
|
useClassInstance(dp, classTypeArguments, inReceiverContext).typeResult.id
|
|
} else {
|
|
val replacedType = tryReplaceParcelizeRawType(dp)
|
|
if (replacedType == null) {
|
|
useClassSource(dp)
|
|
} else {
|
|
useClassInstance(replacedType.first, replacedType.second, inReceiverContext).typeResult.id
|
|
}
|
|
}
|
|
is IrFunction -> useFunction(dp)
|
|
is IrExternalPackageFragment -> {
|
|
// TODO
|
|
logger.error("Unhandled IrExternalPackageFragment")
|
|
null
|
|
}
|
|
else -> {
|
|
logger.error("Unrecognised IrDeclarationParent: " + dp.javaClass)
|
|
null
|
|
}
|
|
}
|
|
|
|
private val IrDeclaration.isAnonymousFunction get() = this is IrSimpleFunction && name == SpecialNames.NO_NAME_PROVIDED
|
|
|
|
data class FunctionNames(val nameInDB: String, val kotlinName: String)
|
|
|
|
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
|
private fun getJvmModuleName(f: IrFunction) =
|
|
NameUtils.sanitizeAsJavaIdentifier(
|
|
getJvmModuleNameForDeserializedDescriptor(f.descriptor) ?: JvmCodegenUtil.getModuleName(pluginContext.moduleDescriptor)
|
|
)
|
|
|
|
fun getFunctionShortName(f: IrFunction) : FunctionNames {
|
|
if (f.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA || f.isAnonymousFunction)
|
|
return FunctionNames(
|
|
OperatorNameConventions.INVOKE.asString(),
|
|
OperatorNameConventions.INVOKE.asString())
|
|
|
|
fun getSuffixIfInternal() =
|
|
if (f.visibility == DescriptorVisibilities.INTERNAL) {
|
|
"\$" + getJvmModuleName(f)
|
|
} else {
|
|
""
|
|
}
|
|
|
|
(f as? IrSimpleFunction)?.correspondingPropertySymbol?.let {
|
|
val propName = it.owner.name.asString()
|
|
val getter = it.owner.getter
|
|
val setter = it.owner.setter
|
|
|
|
if (it.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS) {
|
|
if (getter == null) {
|
|
logger.error("Expected to find a getter for a property inside an annotation class")
|
|
return FunctionNames(propName, propName)
|
|
} else {
|
|
val jvmName = getJvmName(getter)
|
|
return FunctionNames(jvmName ?: propName, propName)
|
|
}
|
|
}
|
|
|
|
val maybeFunctionName = when (f) {
|
|
getter -> JvmAbi.getterName(propName)
|
|
setter -> JvmAbi.setterName(propName)
|
|
else -> {
|
|
logger.error(
|
|
"Function has a corresponding property, but is neither the getter nor the setter"
|
|
)
|
|
null
|
|
}
|
|
}
|
|
maybeFunctionName?.let { defaultFunctionName ->
|
|
val suffix = if (f.visibility == DescriptorVisibilities.PRIVATE && f.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) {
|
|
"\$private"
|
|
} else {
|
|
getSuffixIfInternal()
|
|
}
|
|
return FunctionNames(getJvmName(f) ?: "$defaultFunctionName$suffix", defaultFunctionName)
|
|
}
|
|
}
|
|
return FunctionNames(getJvmName(f) ?: "${f.name.asString()}${getSuffixIfInternal()}", f.name.asString())
|
|
}
|
|
|
|
// This excludes class type parameters that show up in (at least) constructors' typeParameters list.
|
|
fun getFunctionTypeParameters(f: IrFunction): List<IrTypeParameter> {
|
|
return if (f is IrConstructor) f.typeParameters else f.typeParameters.filter { it.parent == f }
|
|
}
|
|
|
|
private fun getTypeParameters(dp: IrDeclarationParent): List<IrTypeParameter> =
|
|
when(dp) {
|
|
is IrClass -> dp.typeParameters
|
|
is IrFunction -> getFunctionTypeParameters(dp)
|
|
else -> listOf()
|
|
}
|
|
|
|
private fun getEnclosingClass(it: IrDeclarationParent): IrClass? =
|
|
when(it) {
|
|
is IrClass -> it
|
|
is IrFunction -> getEnclosingClass(it.parent)
|
|
else -> null
|
|
}
|
|
|
|
val javaUtilCollection by lazy {
|
|
val result = pluginContext.referenceClass(FqName("java.util.Collection"))?.owner
|
|
result?.let { extractExternalClassLater(it) }
|
|
result
|
|
}
|
|
|
|
val wildcardCollectionType by lazy {
|
|
javaUtilCollection?.let {
|
|
it.symbol.typeWithArguments(listOf(IrStarProjectionImpl))
|
|
}
|
|
}
|
|
|
|
private fun makeCovariant(t: IrTypeArgument) =
|
|
t.typeOrNull?.let { makeTypeProjection(it, Variance.OUT_VARIANCE) } ?: t
|
|
|
|
private fun makeArgumentsCovariant(t: IrType) = (t as? IrSimpleType)?.let {
|
|
t.toBuilder().also { b -> b.arguments = b.arguments.map(this::makeCovariant) }.buildSimpleType()
|
|
} ?: t
|
|
|
|
fun eraseCollectionsMethodParameterType(t: IrType, collectionsMethodName: String, paramIdx: Int) =
|
|
when(collectionsMethodName) {
|
|
"contains", "remove", "containsKey", "containsValue", "get", "indexOf", "lastIndexOf" -> javaLangObjectType
|
|
"getOrDefault" -> if (paramIdx == 0) javaLangObjectType else null
|
|
"containsAll", "removeAll", "retainAll" -> wildcardCollectionType
|
|
// Kotlin defines these like addAll(Collection<E>); Java uses addAll(Collection<? extends E>)
|
|
"putAll", "addAll" -> makeArgumentsCovariant(t)
|
|
else -> null
|
|
} ?: t
|
|
|
|
private fun overridesFunctionDefinedOn(f: IrFunction, packageName: String, className: String) =
|
|
(f as? IrSimpleFunction)?.let {
|
|
it.overriddenSymbols.any { overridden ->
|
|
overridden.owner.parentClassOrNull?.let { defnClass ->
|
|
defnClass.name.asString() == className &&
|
|
defnClass.packageFqName?.asString() == packageName
|
|
} ?: false
|
|
}
|
|
} ?: false
|
|
|
|
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
|
fun overridesCollectionsMethodWithAlteredParameterTypes(f: IrFunction) =
|
|
BuiltinMethodsWithSpecialGenericSignature.getOverriddenBuiltinFunctionWithErasedValueParametersInJava(f.descriptor) != null ||
|
|
(f.name.asString() == "putAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableMap")) ||
|
|
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) ||
|
|
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList"))
|
|
|
|
|
|
private val jvmWildcardAnnotation = FqName("kotlin.jvm.JvmWildcard")
|
|
private val jvmWildcardSuppressionAnnotaton = FqName("kotlin.jvm.JvmSuppressWildcards")
|
|
|
|
private fun arrayExtendsAdditionAllowed(t: IrSimpleType): Boolean =
|
|
// Note the array special case includes Array<*>, which does permit adding `? extends ...` (making `? extends Object[]` in that case)
|
|
// Surprisingly Array<in X> does permit this as well, though the contravariant array lowers to Object[] so this ends up `? extends Object[]` as well.
|
|
t.arguments[0].let {
|
|
when (it) {
|
|
is IrTypeProjection -> when (it.variance) {
|
|
Variance.INVARIANT -> false
|
|
Variance.IN_VARIANCE -> !(it.type.isAny() || it.type.isNullableAny())
|
|
Variance.OUT_VARIANCE -> extendsAdditionAllowed(it.type)
|
|
}
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
private fun extendsAdditionAllowed(t: IrType) =
|
|
if (t.isBoxedArray) {
|
|
if (t is IrSimpleType) {
|
|
arrayExtendsAdditionAllowed(t)
|
|
} else {
|
|
logger.warn("Boxed array of unexpected kind ${t.javaClass}")
|
|
// Return false, for no particular reason
|
|
false
|
|
}
|
|
} else {
|
|
((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
|
|
}
|
|
|
|
private fun wildcardAdditionAllowed(v: Variance, t: IrType, addByDefault: Boolean) =
|
|
when {
|
|
t.hasAnnotation(jvmWildcardAnnotation) -> true
|
|
!addByDefault -> false
|
|
t.hasAnnotation(jvmWildcardSuppressionAnnotaton) -> false
|
|
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
|
|
v == Variance.OUT_VARIANCE -> extendsAdditionAllowed(t)
|
|
else -> false
|
|
}
|
|
|
|
private fun addJavaLoweringArgumentWildcards(p: IrTypeParameter, t: IrTypeArgument, addByDefault: Boolean, javaType: JavaType?): IrTypeArgument =
|
|
(t as? IrTypeProjection)?.let {
|
|
val newBase = addJavaLoweringWildcards(it.type, addByDefault, javaType)
|
|
val newVariance =
|
|
if (it.variance == Variance.INVARIANT &&
|
|
p.variance != Variance.INVARIANT &&
|
|
// The next line forbids inferring a wildcard type when we have a corresponding Java type with conflicting variance.
|
|
// For example, Java might declare f(Comparable<CharSequence> cs), in which case we shouldn't add a `? super ...`
|
|
// wildcard. Note if javaType is unknown (e.g. this is a Kotlin source element), we assume wildcards should be added.
|
|
(javaType?.let { jt -> jt is JavaWildcardType && jt.isExtends == (p.variance == Variance.OUT_VARIANCE) } != false) &&
|
|
wildcardAdditionAllowed(p.variance, it.type, addByDefault))
|
|
p.variance
|
|
else
|
|
it.variance
|
|
if (newBase !== it.type || newVariance != it.variance)
|
|
makeTypeProjection(newBase, newVariance)
|
|
else
|
|
null
|
|
} ?: t
|
|
|
|
private fun getJavaTypeArgument(jt: JavaType, idx: Int) =
|
|
when(jt) {
|
|
is JavaClassifierType -> jt.typeArguments.getOrNull(idx)
|
|
is JavaArrayType -> if (idx == 0) jt.componentType else null
|
|
else -> null
|
|
}
|
|
|
|
fun addJavaLoweringWildcards(t: IrType, addByDefault: Boolean, javaType: JavaType?): IrType =
|
|
(t as? IrSimpleType)?.let {
|
|
val typeParams = it.classOrNull?.owner?.typeParameters ?: return t
|
|
val newArgs = typeParams.zip(it.arguments).mapIndexed { idx, pair ->
|
|
addJavaLoweringArgumentWildcards(
|
|
pair.first,
|
|
pair.second,
|
|
addByDefault,
|
|
javaType?.let { jt -> getJavaTypeArgument(jt, idx) }
|
|
)
|
|
}
|
|
return if (newArgs.zip(it.arguments).all { pair -> pair.first === pair.second })
|
|
t
|
|
else
|
|
it.toBuilder().also { builder -> builder.arguments = newArgs }.buildSimpleType()
|
|
} ?: t
|
|
|
|
/*
|
|
* This is the normal getFunctionLabel function to use. If you want
|
|
* to refer to the function in its source class then
|
|
* classTypeArgsIncludingOuterClasses should be null. Otherwise, it
|
|
* is the list of type arguments that need to be applied to its
|
|
* enclosing classes to get the instantiation that this function is
|
|
* in.
|
|
*/
|
|
fun getFunctionLabel(f: IrFunction, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) : String {
|
|
return getFunctionLabel(f, null, classTypeArgsIncludingOuterClasses)
|
|
}
|
|
|
|
/*
|
|
* There are some pairs of classes (e.g. `kotlin.Throwable` and
|
|
* `java.lang.Throwable`) which are really just 2 different names
|
|
* for the same class. However, we extract them as separate
|
|
* classes. When extracting `kotlin.Throwable`'s methods, if we
|
|
* looked up the parent ID ourselves, we would get as ID for
|
|
* `java.lang.Throwable`, which isn't what we want. So we have to
|
|
* allow it to be passed in.
|
|
*
|
|
* `maybeParameterList` can be supplied to override the function's
|
|
* value parameters; this is used for generating labels of overloads
|
|
* that omit one or more parameters that has a default value specified.
|
|
*/
|
|
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
|
fun getFunctionLabel(f: IrFunction, maybeParentId: Label<out DbElement>?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, maybeParameterList: List<IrValueParameter>? = null) =
|
|
getFunctionLabel(
|
|
f.parent,
|
|
maybeParentId,
|
|
getFunctionShortName(f).nameInDB,
|
|
(maybeParameterList ?: f.valueParameters).map { it.type },
|
|
getAdjustedReturnType(f),
|
|
f.extensionReceiverParameter?.type,
|
|
getFunctionTypeParameters(f),
|
|
classTypeArgsIncludingOuterClasses,
|
|
overridesCollectionsMethodWithAlteredParameterTypes(f),
|
|
getJavaCallable(f),
|
|
!hasWildcardSuppressionAnnotation(f)
|
|
)
|
|
|
|
/*
|
|
* This function actually generates the label for a function.
|
|
* Sometimes, a function is only generated by kotlinc when writing a
|
|
* class file, so there is no corresponding `IrFunction` for it.
|
|
* This function therefore takes all the constituent parts of a
|
|
* function instead.
|
|
*/
|
|
fun getFunctionLabel(
|
|
// The parent of the function; normally f.parent.
|
|
parent: IrDeclarationParent,
|
|
// The ID of the function's parent, or null if we should work it out ourselves.
|
|
maybeParentId: Label<out DbElement>?,
|
|
// The name of the function; normally f.name.asString().
|
|
name: String,
|
|
// The types of the value parameters that the functions takes; normally f.valueParameters.map { it.type }.
|
|
parameterTypes: List<IrType>,
|
|
// The return type of the function; normally f.returnType.
|
|
returnType: IrType,
|
|
// The extension receiver of the function, if any; normally f.extensionReceiverParameter?.type.
|
|
extensionParamType: IrType?,
|
|
// The type parameters of the function. This does not include type parameters of enclosing classes.
|
|
functionTypeParameters: List<IrTypeParameter>,
|
|
// The type arguments of enclosing classes of the function.
|
|
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?,
|
|
// If true, this method implements a Java Collections interface (Collection, Map or List) and may need
|
|
// parameter erasure to match the way this class will appear to an external consumer of the .class file.
|
|
overridesCollectionsMethod: Boolean,
|
|
// The Java signature of this callable, if known.
|
|
javaSignature: JavaMember?,
|
|
// If true, Java wildcards implied by Kotlin type parameter variance should be added by default to this function's value parameters' types.
|
|
// (Return-type wildcard addition is always off by default)
|
|
addParameterWildcardsByDefault: Boolean,
|
|
// The prefix used in the label. "callable", unless a property label is created, then it's "property".
|
|
prefix: String = "callable"
|
|
): String {
|
|
val parentId = maybeParentId ?: useDeclarationParent(parent, false, classTypeArgsIncludingOuterClasses, true)
|
|
val allParamTypes = if (extensionParamType == null) parameterTypes else listOf(extensionParamType) + parameterTypes
|
|
|
|
val substitutionMap = classTypeArgsIncludingOuterClasses?.let { notNullArgs ->
|
|
if (notNullArgs.isEmpty()) {
|
|
null
|
|
} else {
|
|
val enclosingClass = getEnclosingClass(parent)
|
|
enclosingClass?.let { notNullClass -> makeTypeGenericSubstitutionMap(notNullClass, notNullArgs) }
|
|
}
|
|
}
|
|
val getIdForFunctionLabel = { it: IndexedValue<IrType> ->
|
|
// Kotlin rewrites certain Java collections types adding additional generic constraints-- for example,
|
|
// Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin universe.
|
|
// If this has happened, erase the type again to get the correct Java signature.
|
|
val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value, name, it.index) else it.value
|
|
// Add any wildcard types that the Kotlin compiler would add in the Java lowering of this function:
|
|
val withAddedWildcards = addJavaLoweringWildcards(maybeAmendedForCollections, addParameterWildcardsByDefault, javaSignature?.let { sig -> getJavaValueParameterType(sig, it.index) })
|
|
// Now substitute any class type parameters in:
|
|
val maybeSubbed = withAddedWildcards.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
|
|
// Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types;
|
|
// those without type parameters are named for the generic type.
|
|
val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed)
|
|
"{${useType(maybeErased).javaResult.id}}"
|
|
}
|
|
val paramTypeIds = allParamTypes.withIndex().joinToString(separator = ",", transform = getIdForFunctionLabel)
|
|
val labelReturnType =
|
|
if (name == "<init>")
|
|
pluginContext.irBuiltIns.unitType
|
|
else
|
|
erase(returnType.substituteTypeAndArguments(substitutionMap, TypeContext.RETURN, pluginContext))
|
|
// Note that `addJavaLoweringWildcards` is not required here because the return type used to form the function
|
|
// label is always erased.
|
|
val returnTypeId = useType(labelReturnType, TypeContext.RETURN).javaResult.id
|
|
// This suffix is added to generic methods (and constructors) to match the Java extractor's behaviour.
|
|
// Comments in that extractor indicates it didn't want the label of the callable to clash with the raw
|
|
// method (and presumably that disambiguation is never needed when the method belongs to a parameterized
|
|
// instance of a generic class), but as of now I don't know when the raw method would be referred to.
|
|
val typeArgSuffix = if (functionTypeParameters.isNotEmpty() && classTypeArgsIncludingOuterClasses.isNullOrEmpty()) "<${functionTypeParameters.size}>" else "";
|
|
return "@\"$prefix;{$parentId}.$name($paramTypeIds){$returnTypeId}${typeArgSuffix}\""
|
|
}
|
|
|
|
val javaLangClass by lazy {
|
|
val result = pluginContext.referenceClass(FqName("java.lang.Class"))?.owner
|
|
result?.let { extractExternalClassLater(it) }
|
|
result
|
|
}
|
|
|
|
fun kClassToJavaClass(t: IrType): IrType {
|
|
when(t) {
|
|
is IrSimpleType -> {
|
|
if (t.classifier == pluginContext.irBuiltIns.kClassClass) {
|
|
javaLangClass?.let { jlc ->
|
|
return jlc.symbol.typeWithArguments(t.arguments)
|
|
}
|
|
} else {
|
|
t.classOrNull?.let { tCls ->
|
|
if (t.isArray() || t.isNullableArray()) {
|
|
(t.arguments.singleOrNull() as? IrTypeProjection)?.let { elementTypeArg ->
|
|
val elementType = elementTypeArg.type
|
|
val replacedElementType = kClassToJavaClass(elementType)
|
|
if (replacedElementType !== elementType) {
|
|
val newArg = makeTypeProjection(replacedElementType, elementTypeArg.variance)
|
|
return tCls.typeWithArguments(listOf(newArg)).codeQlWithHasQuestionMark(t.isNullableArray())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
fun isAnnotationClassField(f: IrField) =
|
|
f.correspondingPropertySymbol?.let {
|
|
isAnnotationClassProperty(it)
|
|
} ?: false
|
|
|
|
private fun isAnnotationClassProperty(p: IrPropertySymbol) =
|
|
p.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS
|
|
|
|
fun getAdjustedReturnType(f: IrFunction) : IrType {
|
|
// Replace annotation val accessor types as needed:
|
|
(f as? IrSimpleFunction)?.correspondingPropertySymbol?.let {
|
|
if (isAnnotationClassProperty(it) && f == it.owner.getter) {
|
|
val replaced = kClassToJavaClass(f.returnType)
|
|
if (replaced != f.returnType)
|
|
return replaced
|
|
}
|
|
}
|
|
|
|
// The return type of `java.util.concurrent.ConcurrentHashMap<K,V>.keySet/0` is defined as `Set<K>` in the stubs inside the Android SDK.
|
|
// This does not match the Java SDK return type: `ConcurrentHashMap.KeySetView<K,V>`, so it's adjusted here.
|
|
// This is a deliberate change in the Android SDK: https://github.com/AndroidSDKSources/android-sdk-sources-for-api-level-31/blob/2c56b25f619575bea12f9c5520ed2259620084ac/java/util/concurrent/ConcurrentHashMap.java#L1244-L1249
|
|
// The annotation on the source is not visible in the android.jar, so we can't make the change based on that.
|
|
// TODO: there are other instances of `dalvik.annotation.codegen.CovariantReturnType` in the Android SDK, we should handle those too if they cause DB inconsistencies
|
|
val parentClass = f.parentClassOrNull
|
|
if (parentClass == null ||
|
|
parentClass.fqNameWhenAvailable?.asString() != "java.util.concurrent.ConcurrentHashMap" ||
|
|
getFunctionShortName(f).nameInDB != "keySet" ||
|
|
f.valueParameters.isNotEmpty() ||
|
|
f.returnType.classFqName?.asString() != "kotlin.collections.MutableSet") {
|
|
return f.returnType
|
|
}
|
|
|
|
val otherKeySet = parentClass.declarations.findSubType<IrFunction> { it.name.asString() == "keySet" && it.valueParameters.size == 1 }
|
|
?: return f.returnType
|
|
|
|
return otherKeySet.returnType.codeQlWithHasQuestionMark(false)
|
|
}
|
|
|
|
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
|
fun getJavaCallable(f: IrFunction) = (f.descriptor.source as? JavaSourceElement)?.javaElement as? JavaMember
|
|
|
|
fun getJavaValueParameterType(m: JavaMember, idx: Int) = when(m) {
|
|
is JavaMethod -> m.valueParameters[idx].type
|
|
is JavaConstructor -> m.valueParameters[idx].type
|
|
else -> null
|
|
}
|
|
|
|
fun hasWildcardSuppressionAnnotation(d: IrDeclaration) =
|
|
d.hasAnnotation(jvmWildcardSuppressionAnnotaton) ||
|
|
// Note not using `parentsWithSelf` as that only works if `d` is an IrDeclarationParent
|
|
d.parents.any { (it as? IrAnnotationContainer)?.hasAnnotation(jvmWildcardSuppressionAnnotaton) == true }
|
|
|
|
/**
|
|
* Class to hold labels for generated classes around local functions, lambdas, function references, and property references.
|
|
*/
|
|
open class GeneratedClassLabels(val type: TypeResults, val constructor: Label<DbConstructor>, val constructorBlock: Label<DbBlock>)
|
|
|
|
/**
|
|
* Class to hold labels generated for locally visible functions, such as
|
|
* - local functions,
|
|
* - lambdas, and
|
|
* - wrappers around function references.
|
|
*/
|
|
class LocallyVisibleFunctionLabels(type: TypeResults, constructor: Label<DbConstructor>, constructorBlock: Label<DbBlock>, val function: Label<DbMethod>)
|
|
: GeneratedClassLabels(type, constructor, constructorBlock)
|
|
|
|
/**
|
|
* Gets the labels for functions belonging to
|
|
* - local functions, and
|
|
* - lambdas.
|
|
*/
|
|
fun getLocallyVisibleFunctionLabels(f: IrFunction): LocallyVisibleFunctionLabels {
|
|
if (!f.isLocalFunction()){
|
|
logger.error("Extracting a non-local function as a local one")
|
|
}
|
|
|
|
var res = tw.lm.locallyVisibleFunctionLabelMapping[f]
|
|
if (res == null) {
|
|
val javaResult = TypeResult(tw.getFreshIdLabel<DbClass>(), "", "")
|
|
val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
|
|
tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
|
|
res = LocallyVisibleFunctionLabels(
|
|
TypeResults(javaResult, kotlinResult),
|
|
tw.getFreshIdLabel(),
|
|
tw.getFreshIdLabel(),
|
|
tw.getFreshIdLabel()
|
|
)
|
|
tw.lm.locallyVisibleFunctionLabelMapping[f] = res
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
fun getExistingLocallyVisibleFunctionLabel(f: IrFunction): Label<DbMethod>? {
|
|
if (!f.isLocalFunction()){
|
|
return null
|
|
}
|
|
|
|
return tw.lm.locallyVisibleFunctionLabelMapping[f]?.function
|
|
}
|
|
|
|
private fun kotlinFunctionToJavaEquivalent(f: IrFunction, noReplace: Boolean): IrFunction =
|
|
if (noReplace)
|
|
f
|
|
else
|
|
f.parentClassOrNull?.let { parentClass ->
|
|
getJavaEquivalentClass(parentClass)?.let { javaClass ->
|
|
if (javaClass != parentClass) {
|
|
var jvmName = getFunctionShortName(f).nameInDB
|
|
if (f.name.asString() == "get" && parentClass.fqNameWhenAvailable?.asString() == "kotlin.String") {
|
|
// `kotlin.String.get` has an equivalent `java.lang.String.get`, which in turn will be stored in the DB as `java.lang.String.charAt`.
|
|
// Maybe all operators should be handled the same way, but so far I only found this case that needed to be special cased. This is the
|
|
// only operator in `JvmNames.specialFunctions`
|
|
jvmName = "get"
|
|
}
|
|
// Look for an exact type match...
|
|
javaClass.declarations.findSubType<IrFunction> { decl ->
|
|
decl.name.asString() == jvmName &&
|
|
decl.valueParameters.size == f.valueParameters.size &&
|
|
decl.valueParameters.zip(f.valueParameters).all { p -> erase(p.first.type).classifierOrNull == erase(p.second.type).classifierOrNull }
|
|
} ?:
|
|
// Or check property accessors:
|
|
(f.propertyIfAccessor as? IrProperty)?.let { kotlinProp ->
|
|
val javaProp = javaClass.declarations.findSubType<IrProperty> { decl ->
|
|
decl.name == kotlinProp.name
|
|
}
|
|
if (javaProp?.getter?.name == f.name)
|
|
javaProp.getter
|
|
else if (javaProp?.setter?.name == f.name)
|
|
javaProp.setter
|
|
else null
|
|
} ?: run {
|
|
val parentFqName = parentClass.fqNameWhenAvailable?.asString()
|
|
logger.warn("Couldn't find a Java equivalent function to $parentFqName.${f.name.asString()} in ${javaClass.fqNameWhenAvailable?.asString()}")
|
|
null
|
|
}
|
|
}
|
|
else
|
|
null
|
|
}
|
|
} ?: f
|
|
|
|
fun isPrivate(d: IrDeclaration) =
|
|
when(d) {
|
|
is IrDeclarationWithVisibility -> d.visibility.let { it == DescriptorVisibilities.PRIVATE || it == DescriptorVisibilities.PRIVATE_TO_THIS }
|
|
else -> false
|
|
}
|
|
|
|
fun <T: DbCallable> useFunction(f: IrFunction, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>? = null, noReplace: Boolean = false): Label<out T> {
|
|
return useFunction(f, null, classTypeArgsIncludingOuterClasses, noReplace)
|
|
}
|
|
|
|
fun <T: DbCallable> useFunction(f: IrFunction, parentId: Label<out DbElement>?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, noReplace: Boolean = false): Label<out T> {
|
|
if (f.isLocalFunction()) {
|
|
val ids = getLocallyVisibleFunctionLabels(f)
|
|
return ids.function.cast<T>()
|
|
}
|
|
val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace)
|
|
val label = getFunctionLabel(javaFun, parentId, classTypeArgsIncludingOuterClasses)
|
|
val id: Label<T> = tw.getLabelFor(label) {
|
|
extractPrivateSpecialisedDeclaration(f, classTypeArgsIncludingOuterClasses)
|
|
}
|
|
if (isExternalDeclaration(javaFun)) {
|
|
extractFunctionLaterIfExternalFileMember(javaFun)
|
|
extractExternalEnclosingClassLater(javaFun)
|
|
}
|
|
return id
|
|
}
|
|
|
|
private fun extractPrivateSpecialisedDeclaration(d: IrDeclaration, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) {
|
|
// Note here `classTypeArgsIncludingOuterClasses` being null doesn't signify a raw receiver type but rather that no type args were supplied.
|
|
// This is because a call to a private method can only be observed inside Kotlin code, and Kotlin can't represent raw types.
|
|
if (this is KotlinFileExtractor && isPrivate(d) && classTypeArgsIncludingOuterClasses != null && classTypeArgsIncludingOuterClasses.isNotEmpty()) {
|
|
d.parent.let {
|
|
when(it) {
|
|
is IrClass -> this.extractDeclarationPrototype(d, useClassInstance(it, classTypeArgsIncludingOuterClasses).typeResult.id, classTypeArgsIncludingOuterClasses)
|
|
else -> logger.warnElement("Unable to extract specialised declaration that isn't a member of a class", d)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun getTypeArgumentLabel(
|
|
arg: IrTypeArgument
|
|
): TypeResultWithoutSignature<DbReftype> {
|
|
|
|
fun extractBoundedWildcard(wildcardKind: Int, wildcardLabelStr: String, wildcardShortName: String, boundLabel: Label<out DbReftype>): Label<DbWildcard> =
|
|
tw.getLabelFor(wildcardLabelStr) { wildcardLabel ->
|
|
tw.writeWildcards(wildcardLabel, wildcardShortName, wildcardKind)
|
|
tw.writeHasLocation(wildcardLabel, tw.unknownLocation)
|
|
tw.getLabelFor<DbTypebound>("@\"bound;0;{$wildcardLabel}\"") {
|
|
tw.writeTypeBounds(it, boundLabel, 0, wildcardLabel)
|
|
}
|
|
}
|
|
|
|
// Note this function doesn't return a signature because type arguments are never incorporated into function signatures.
|
|
return when (arg) {
|
|
is IrStarProjection -> {
|
|
val anyTypeLabel = useType(pluginContext.irBuiltIns.anyType).javaResult.id.cast<DbReftype>()
|
|
TypeResultWithoutSignature(extractBoundedWildcard(1, "@\"wildcard;\"", "?", anyTypeLabel), Unit, "?")
|
|
}
|
|
is IrTypeProjection -> {
|
|
val boundResults = useType(arg.type, TypeContext.GENERIC_ARGUMENT)
|
|
val boundLabel = boundResults.javaResult.id.cast<DbReftype>()
|
|
|
|
return if(arg.variance == Variance.INVARIANT)
|
|
boundResults.javaResult.cast<DbReftype>().forgetSignature()
|
|
else {
|
|
val keyPrefix = if (arg.variance == Variance.IN_VARIANCE) "super" else "extends"
|
|
val wildcardKind = if (arg.variance == Variance.IN_VARIANCE) 2 else 1
|
|
val wildcardShortName = "? $keyPrefix ${boundResults.javaResult.shortName}"
|
|
TypeResultWithoutSignature(
|
|
extractBoundedWildcard(wildcardKind, "@\"wildcard;$keyPrefix{$boundLabel}\"", wildcardShortName, boundLabel),
|
|
Unit,
|
|
wildcardShortName)
|
|
}
|
|
}
|
|
else -> {
|
|
logger.error("Unexpected type argument.")
|
|
return extractJavaErrorType().forgetSignature()
|
|
}
|
|
}
|
|
}
|
|
|
|
data class ClassLabelResults(
|
|
val classLabel: String, val shortName: String
|
|
)
|
|
|
|
/**
|
|
* This returns the `X` in c's label `@"class;X"`.
|
|
*
|
|
* `argsIncludingOuterClasses` can be null to describe a raw generic type.
|
|
* For non-generic types it will be zero-length list.
|
|
*/
|
|
private fun getUnquotedClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): ClassLabelResults {
|
|
val pkg = c.packageFqName?.asString() ?: ""
|
|
val cls = c.name.asString()
|
|
val label =
|
|
if (c.isAnonymousObject)
|
|
"{${useAnonymousClass(c).javaResult.id}}"
|
|
else
|
|
when (val parent = c.parent) {
|
|
is IrClass -> {
|
|
"${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
|
|
}
|
|
is IrFunction -> {
|
|
"{${useFunction<DbMethod>(parent)}}.$cls"
|
|
}
|
|
is IrField -> {
|
|
"{${useField(parent)}}.$cls"
|
|
}
|
|
else -> {
|
|
if (pkg.isEmpty()) cls else "$pkg.$cls"
|
|
}
|
|
}
|
|
|
|
val reorderedArgs = orderTypeArgsLeftToRight(c, argsIncludingOuterClasses)
|
|
val typeArgLabels = reorderedArgs?.map { getTypeArgumentLabel(it) }
|
|
val typeArgsShortName =
|
|
if (typeArgLabels == null)
|
|
"<>"
|
|
else if(typeArgLabels.isEmpty())
|
|
""
|
|
else
|
|
typeArgLabels.takeLast(c.typeParameters.size).joinToString(prefix = "<", postfix = ">", separator = ",") { it.shortName }
|
|
val shortNamePrefix = if (c.isAnonymousObject) "" else cls
|
|
|
|
return ClassLabelResults(
|
|
label + (typeArgLabels?.joinToString(separator = "") { ";{${it.id}}" } ?: "<>"),
|
|
shortNamePrefix + typeArgsShortName
|
|
)
|
|
}
|
|
|
|
// `args` can be null to describe a raw generic type.
|
|
// For non-generic types it will be zero-length list.
|
|
fun getClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): ClassLabelResults {
|
|
val unquotedLabel = getUnquotedClassLabel(c, argsIncludingOuterClasses)
|
|
return ClassLabelResults(
|
|
"@\"class;${unquotedLabel.classLabel}\"",
|
|
unquotedLabel.shortName)
|
|
}
|
|
|
|
fun useClassSource(c: IrClass): Label<out DbClassorinterface> {
|
|
// For source classes, the label doesn't include any type arguments
|
|
val classTypeResult = addClassLabel(c, listOf())
|
|
return classTypeResult.id
|
|
}
|
|
|
|
fun getTypeParameterParentLabel(param: IrTypeParameter) =
|
|
param.parent.let {
|
|
(it as? IrFunction)?.let { fn ->
|
|
if (this is KotlinFileExtractor)
|
|
this.declarationStack.findOverriddenAttributes(fn)?.id
|
|
else
|
|
null
|
|
} ?:
|
|
when (it) {
|
|
is IrClass -> useClassSource(it)
|
|
is IrFunction -> useFunction(it, noReplace = true)
|
|
else -> { logger.error("Unexpected type parameter parent $it"); null }
|
|
}
|
|
}
|
|
|
|
fun getTypeParameterLabel(param: IrTypeParameter): String {
|
|
// Use this instead of `useDeclarationParent` so we can use useFunction with noReplace = true,
|
|
// ensuring that e.g. a method-scoped type variable declared on kotlin.String.transform <R> gets
|
|
// a different name to the corresponding java.lang.String.transform <R>, even though useFunction
|
|
// will usually replace references to one function with the other.
|
|
val parentLabel = getTypeParameterParentLabel(param)
|
|
return "@\"typevar;{$parentLabel};${param.name}\""
|
|
}
|
|
|
|
private fun useTypeParameter(param: IrTypeParameter) =
|
|
TypeResult(
|
|
tw.getLabelFor<DbTypevariable>(getTypeParameterLabel(param)),
|
|
useType(eraseTypeParameter(param)).javaResult.signature,
|
|
param.name.asString()
|
|
)
|
|
|
|
private fun extractModifier(m: String): Label<DbModifier> {
|
|
val modifierLabel = "@\"modifier;$m\""
|
|
val id: Label<DbModifier> = tw.getLabelFor(modifierLabel, {
|
|
tw.writeModifiers(it, m)
|
|
})
|
|
return id
|
|
}
|
|
|
|
fun addModifiers(modifiable: Label<out DbModifiable>, vararg modifiers: String) =
|
|
modifiers.forEach { tw.writeHasModifier(modifiable, extractModifier(it)) }
|
|
|
|
sealed class ExtractSupertypesMode {
|
|
object Unbound : ExtractSupertypesMode()
|
|
object Raw : ExtractSupertypesMode()
|
|
data class Specialised(val typeArgs : List<IrTypeArgument>) : ExtractSupertypesMode()
|
|
}
|
|
|
|
/**
|
|
* Extracts the supertypes of class `c`, either the unbound version, raw version or a specialisation to particular
|
|
* type arguments, depending on the value of `mode`. `id` is the label of this class or class instantiation.
|
|
*
|
|
* For example, for type `List` if `mode` `Specialised([String])` then we will extract the supertypes
|
|
* of `List<String>`, i.e. `Appendable<String>` etc, or if `mode` is `Unbound` we will extract `Appendable<E>`
|
|
* where `E` is the type variable declared as `List<E>`. Finally if `mode` is `Raw` we will extract the raw type
|
|
* `Appendable`, represented in QL as `Appendable<>`.
|
|
*
|
|
* Argument `inReceiverContext` will be passed onto the `useClassInstance` invocation for each supertype.
|
|
*/
|
|
fun extractClassSupertypes(c: IrClass, id: Label<out DbReftype>, mode: ExtractSupertypesMode = ExtractSupertypesMode.Unbound, inReceiverContext: Boolean = false) {
|
|
extractClassSupertypes(c.superTypes, c.typeParameters, id, c.isInterfaceLike, mode, inReceiverContext)
|
|
}
|
|
|
|
fun extractClassSupertypes(superTypes: List<IrType>, typeParameters: List<IrTypeParameter>, id: Label<out DbReftype>, isInterface: Boolean, mode: ExtractSupertypesMode = ExtractSupertypesMode.Unbound, inReceiverContext: Boolean = false) {
|
|
// Note we only need to substitute type args here because it is illegal to directly extend a type variable.
|
|
// (For example, we can't have `class A<E> : E`, but can have `class A<E> : Comparable<E>`)
|
|
val subbedSupertypes = when(mode) {
|
|
is ExtractSupertypesMode.Specialised -> {
|
|
superTypes.map {
|
|
it.substituteTypeArguments(typeParameters, mode.typeArgs)
|
|
}
|
|
}
|
|
else -> superTypes
|
|
}
|
|
|
|
for(t in subbedSupertypes) {
|
|
when(t) {
|
|
is IrSimpleType -> {
|
|
when (val owner = t.classifier.owner) {
|
|
is IrClass -> {
|
|
val typeArgs = if (t.arguments.isNotEmpty() && mode is ExtractSupertypesMode.Raw) null else t.arguments
|
|
val l = useClassInstance(owner, typeArgs, inReceiverContext).typeResult.id
|
|
if (isInterface || !owner.isInterfaceLike) {
|
|
tw.writeExtendsReftype(id, l)
|
|
} else {
|
|
tw.writeImplInterface(id.cast(), l.cast())
|
|
}
|
|
}
|
|
else -> {
|
|
logger.error("Unexpected simple type supertype: " + t.javaClass + ": " + t.render())
|
|
}
|
|
}
|
|
} else -> {
|
|
logger.error("Unexpected supertype: " + t.javaClass + ": " + t.render())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun useValueDeclaration(d: IrValueDeclaration): Label<out DbVariable>? =
|
|
when(d) {
|
|
is IrValueParameter -> useValueParameter(d, null)
|
|
is IrVariable -> useVariable(d)
|
|
else -> {
|
|
logger.error("Unrecognised IrValueDeclaration: " + d.javaClass)
|
|
null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns `t` with generic types replaced by raw types, and type parameters replaced by their first bound.
|
|
*
|
|
* Note that `Array<T>` is retained (with `T` itself erased) because these are expected to be lowered to Java
|
|
* arrays, which are not generic.
|
|
*/
|
|
fun erase (t: IrType): IrType {
|
|
if (t is IrSimpleType) {
|
|
val classifier = t.classifier
|
|
val owner = classifier.owner
|
|
if(owner is IrTypeParameter) {
|
|
return eraseTypeParameter(owner)
|
|
}
|
|
|
|
if (owner is IrClass) {
|
|
if (t.isArray() || t.isNullableArray()) {
|
|
val elementType = t.getArrayElementType(pluginContext.irBuiltIns)
|
|
val erasedElementType = erase(elementType)
|
|
return owner.typeWith(erasedElementType).codeQlWithHasQuestionMark(t.isNullable())
|
|
}
|
|
|
|
return if (t.arguments.isNotEmpty())
|
|
t.addAnnotations(listOf(RawTypeAnnotation.annotationConstructor))
|
|
else
|
|
t
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
private fun eraseTypeParameter(t: IrTypeParameter) =
|
|
erase(t.superTypes[0])
|
|
|
|
fun getValueParameterLabel(parentId: Label<out DbElement>?, idx: Int) = "@\"params;{$parentId};$idx\""
|
|
|
|
/**
|
|
* Gets the label for `vp` in the context of function instance `parent`, or in that of its declaring function if
|
|
* `parent` is null.
|
|
*/
|
|
fun getValueParameterLabel(vp: IrValueParameter, parent: Label<out DbCallable>?): String {
|
|
val declarationParent = vp.parent
|
|
val overriddenParentAttributes = (declarationParent as? IrFunction)?.let {
|
|
// Note the check 'vp.fileOrNull?.path == this.filePath' should never actually do anything, since references
|
|
// to a value parameter should always come from within the same .kt file.
|
|
if (this is KotlinFileExtractor && vp.fileOrNull?.path == this.filePath)
|
|
this.declarationStack.findOverriddenAttributes(it)
|
|
else
|
|
null
|
|
}
|
|
val parentId = parent ?: overriddenParentAttributes?.id ?: useDeclarationParent(declarationParent, false)
|
|
|
|
val idxBase = overriddenParentAttributes?.valueParameters?.indexOf(vp) ?: vp.index
|
|
val idxOffset = if (declarationParent is IrFunction && declarationParent.extensionReceiverParameter != null)
|
|
// For extension functions increase the index to match what the java extractor sees:
|
|
1
|
|
else
|
|
0
|
|
val idx = idxBase + idxOffset
|
|
|
|
if (idx < 0) {
|
|
// We're not extracting this and this@TYPE parameters of functions:
|
|
logger.error("Unexpected negative index for parameter")
|
|
}
|
|
|
|
return getValueParameterLabel(parentId, idx)
|
|
}
|
|
|
|
|
|
fun useValueParameter(vp: IrValueParameter, parent: Label<out DbCallable>?): Label<out DbParam> =
|
|
tw.getLabelFor(getValueParameterLabel(vp, parent))
|
|
|
|
private fun isDirectlyExposedCompanionObjectField(f: IrField) =
|
|
f.hasAnnotation(FqName("kotlin.jvm.JvmField")) ||
|
|
f.correspondingPropertySymbol?.owner?.let {
|
|
it.isConst || it.isLateinit
|
|
} ?: false
|
|
|
|
fun getFieldParent(f: IrField) =
|
|
f.parentClassOrNull?.let {
|
|
if (it.isCompanion && isDirectlyExposedCompanionObjectField(f))
|
|
it.parent
|
|
else
|
|
null
|
|
} ?: f.parent
|
|
|
|
// Gets a field's corresponding property's extension receiver type, if any
|
|
fun getExtensionReceiverType(f: IrField) =
|
|
f.correspondingPropertySymbol?.owner?.let {
|
|
(it.getter ?: it.setter)?.extensionReceiverParameter?.type
|
|
}
|
|
|
|
fun getFieldLabel(f: IrField): String {
|
|
val parentId = useDeclarationParent(getFieldParent(f), false)
|
|
// Distinguish backing fields of properties based on their extension receiver type;
|
|
// otherwise two extension properties declared in the same enclosing context will get
|
|
// clashing trap labels. These are always private, so we can just make up a label without
|
|
// worrying about their names as seen from Java.
|
|
val extensionPropertyDiscriminator = getExtensionReceiverType(f)?.let { "extension;${useType(it).javaResult.id}" } ?: ""
|
|
return "@\"field;{$parentId};${extensionPropertyDiscriminator}${f.name.asString()}\""
|
|
}
|
|
|
|
fun useField(f: IrField): Label<out DbField> =
|
|
tw.getLabelFor<DbField>(getFieldLabel(f)).also { extractFieldLaterIfExternalFileMember(f) }
|
|
|
|
fun getPropertyLabel(p: IrProperty): String? {
|
|
val parentId = useDeclarationParent(p.parent, false)
|
|
if (parentId == null) {
|
|
return null
|
|
} else {
|
|
return getPropertyLabel(p, parentId, null)
|
|
}
|
|
}
|
|
|
|
private fun getPropertyLabel(p: IrProperty, parentId: Label<out DbElement>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?): String {
|
|
val getter = p.getter
|
|
val setter = p.setter
|
|
|
|
val func = getter ?: setter
|
|
val ext = func?.extensionReceiverParameter
|
|
|
|
return if (ext == null) {
|
|
"@\"property;{$parentId};${p.name.asString()}\""
|
|
} else {
|
|
val returnType = getter?.returnType ?: setter?.valueParameters?.singleOrNull()?.type ?: pluginContext.irBuiltIns.unitType
|
|
val typeParams = getFunctionTypeParameters(func)
|
|
|
|
getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext.type, typeParams, classTypeArgsIncludingOuterClasses, overridesCollectionsMethod = false, javaSignature = null, addParameterWildcardsByDefault = false, prefix = "property")
|
|
}
|
|
}
|
|
|
|
fun useProperty(p: IrProperty, parentId: Label<out DbElement>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) =
|
|
tw.getLabelFor<DbKt_property>(getPropertyLabel(p, parentId, classTypeArgsIncludingOuterClasses)) {
|
|
extractPropertyLaterIfExternalFileMember(p)
|
|
extractPrivateSpecialisedDeclaration(p, classTypeArgsIncludingOuterClasses)
|
|
}
|
|
|
|
fun getEnumEntryLabel(ee: IrEnumEntry): String {
|
|
val parentId = useDeclarationParent(ee.parent, false)
|
|
return "@\"field;{$parentId};${ee.name.asString()}\""
|
|
}
|
|
|
|
fun useEnumEntry(ee: IrEnumEntry): Label<out DbField> =
|
|
tw.getLabelFor(getEnumEntryLabel(ee))
|
|
|
|
fun getTypeAliasLabel(ta: IrTypeAlias): String {
|
|
val parentId = useDeclarationParent(ta.parent, true)
|
|
return "@\"type_alias;{$parentId};${ta.name.asString()}\""
|
|
}
|
|
|
|
fun useTypeAlias(ta: IrTypeAlias): Label<out DbKt_type_alias> =
|
|
tw.getLabelFor(getTypeAliasLabel(ta))
|
|
|
|
fun useVariable(v: IrVariable): Label<out DbLocalvar> {
|
|
return tw.getVariableLabelFor<DbLocalvar>(v)
|
|
}
|
|
|
|
}
|