Files
codeql/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt
Ian Lynagh 8ab52dba83 Kotlin: Don't write the erasure relation
It's no longer used
2024-10-29 11:32:18 +00:00

9191 lines
376 KiB
Kotlin

package com.github.codeql
import com.github.codeql.comments.CommentExtractorLighterAST
import com.github.codeql.comments.CommentExtractorPSI
import com.github.codeql.utils.*
import com.github.codeql.utils.versions.*
import com.semmle.extractor.java.OdasaOutput
import java.io.Closeable
import java.util.*
import kotlin.collections.ArrayList
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity
import org.jetbrains.kotlin.config.JvmAnalysisFlags
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.java.NOT_NULL_ANNOTATIONS
import org.jetbrains.kotlin.load.java.NULLABLE_ANNOTATIONS
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaConstructor
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
open class KotlinFileExtractor(
override val logger: FileLogger,
override val tw: FileTrapWriter,
val linesOfCode: LinesOfCode?,
val filePath: String,
dependencyCollector: OdasaOutput.TrapFileManager?,
externalClassExtractor: ExternalDeclExtractor,
primitiveTypeMapping: PrimitiveTypeMapping,
pluginContext: IrPluginContext,
val declarationStack: DeclarationStack,
globalExtensionState: KotlinExtractorGlobalState,
) :
KotlinUsesExtractor(
logger,
tw,
dependencyCollector,
externalClassExtractor,
primitiveTypeMapping,
pluginContext,
globalExtensionState
) {
val usesK2 = usesK2(pluginContext)
val metaAnnotationSupport = MetaAnnotationSupport(logger, pluginContext, this)
private inline fun <T> with(kind: String, element: IrElement, f: () -> T): T {
val name =
when (element) {
is IrFile -> element.name
is IrDeclarationWithName -> element.name.asString()
else -> "<no name>"
}
val loc = tw.getLocationString(element)
val context = logger.loggerBase.extractorContextStack
context.push(ExtractorContext(kind, element, name, loc))
try {
val depth = context.size
val depthDescription = "${"-".repeat(depth)} (${depth.toString()})"
logger.trace("$depthDescription: Starting a $kind ($name) at $loc")
val result = f()
logger.trace("$depthDescription: Finished a $kind ($name) at $loc")
return result
} catch (exception: Exception) {
throw Exception("While extracting a $kind ($name) at $loc", exception)
} finally {
context.pop()
}
}
fun extractFileContents(file: IrFile, id: Label<DbFile>) {
with("file", file) {
val locId = tw.getWholeFileLocation()
val pkg = file.packageFqName.asString()
val pkgId = extractPackage(pkg)
tw.writeHasLocation(id, locId)
tw.writeCupackage(id, pkgId)
val exceptionOnFile =
System.getenv("CODEQL_KOTLIN_INTERNAL_EXCEPTION_WHILE_EXTRACTING_FILE")
if (exceptionOnFile != null) {
if (exceptionOnFile.lowercase() == file.name.lowercase()) {
throw Exception("Internal testing exception")
}
}
file.declarations.forEach {
extractDeclaration(
it,
extractPrivateMembers = true,
extractFunctionBodies = true,
extractAnnotations = true
)
if (it is IrProperty || it is IrField || it is IrFunction) {
externalClassExtractor.writeStubTrapFile(it, getTrapFileSignature(it))
}
}
extractStaticInitializer(file, { extractFileClass(file) })
val psiCommentsExtracted = CommentExtractorPSI(this, file, tw.fileId).extract()
val lighterAstCommentsExtracted =
CommentExtractorLighterAST(this, file, tw.fileId).extract()
if (psiCommentsExtracted == lighterAstCommentsExtracted) {
if (psiCommentsExtracted) {
logger.warnElement(
"Found both PSI and LighterAST comments in ${file.path}.",
file
)
} else {
logger.warnElement("Comments could not be processed in ${file.path}.", file)
}
}
if (!declarationStack.isEmpty()) {
logger.errorElement(
"Declaration stack is not empty after processing the file",
file
)
}
linesOfCode?.linesOfCodeInFile(id)
externalClassExtractor.writeStubTrapFile(file)
}
}
private fun javaBinaryDeclaresMethod(c: IrClass, name: String) =
((c.source as? JavaSourceElement)?.javaElement as? BinaryJavaClass)?.methods?.any {
it.name.asString() == name
}
private fun isJavaBinaryDeclaration(f: IrFunction) =
f.parentClassOrNull?.let { javaBinaryDeclaresMethod(it, f.name.asString()) } ?: false
private fun isJavaBinaryObjectMethodRedeclaration(d: IrDeclaration) =
when (d) {
is IrFunction ->
when (d.name.asString()) {
"toString" -> d.valueParameters.isEmpty()
"hashCode" -> d.valueParameters.isEmpty()
"equals" -> d.valueParameters.singleOrNull()?.type?.isNullableAny() ?: false
else -> false
} && isJavaBinaryDeclaration(d)
else -> false
}
private fun FunctionDescriptor.tryIsHiddenToOvercomeSignatureClash(d: IrFunction): Boolean {
// `org.jetbrains.kotlin.ir.descriptors.IrBasedClassConstructorDescriptor.isHiddenToOvercomeSignatureClash`
// throws one exception or other in Kotlin 2, depending on the version.
// TODO: We need a replacement for this for Kotlin 2
try {
return this.isHiddenToOvercomeSignatureClash
} catch (e: NotImplementedError) {
if (!usesK2) {
logger.warnElement("Couldn't query if element is fake, deciding it's not.", d, e)
}
return false
} catch (e: IllegalStateException) {
if (!usesK2) {
logger.warnElement("Couldn't query if element is fake, deciding it's not.", d, e)
}
return false
}
}
@OptIn(ObsoleteDescriptorBasedAPI::class)
fun isFake(d: IrDeclarationWithVisibility): Boolean {
val hasFakeVisibility =
d.visibility.let {
it is DelegatedDescriptorVisibility && it.delegate == Visibilities.InvisibleFake
} || d.isFakeOverride
if (hasFakeVisibility && !isJavaBinaryObjectMethodRedeclaration(d)) return true
return (d as? IrFunction)?.descriptor?.tryIsHiddenToOvercomeSignatureClash(d) == true
}
private fun shouldExtractDecl(declaration: IrDeclaration, extractPrivateMembers: Boolean) =
extractPrivateMembers || !isPrivate(declaration)
fun extractDeclaration(
declaration: IrDeclaration,
extractPrivateMembers: Boolean,
extractFunctionBodies: Boolean,
extractAnnotations: Boolean
) {
with("declaration", declaration) {
if (!shouldExtractDecl(declaration, extractPrivateMembers)) return
when (declaration) {
is IrClass -> {
if (isExternalDeclaration(declaration)) {
extractExternalClassLater(declaration)
} else {
extractClassSource(
declaration,
extractDeclarations = true,
extractStaticInitializer = true,
extractPrivateMembers = extractPrivateMembers,
extractFunctionBodies = extractFunctionBodies
)
}
}
is IrFunction -> {
val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
if (parentId != null) {
extractFunction(
declaration,
parentId,
extractBody = extractFunctionBodies,
extractMethodAndParameterTypeAccesses = extractFunctionBodies,
extractAnnotations = extractAnnotations,
null,
listOf()
)
}
Unit
}
is IrAnonymousInitializer -> {
// Leaving this intentionally empty. init blocks are extracted during class
// extraction.
}
is IrProperty -> {
val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
if (parentId != null) {
extractProperty(
declaration,
parentId,
extractBackingField = true,
extractFunctionBodies = extractFunctionBodies,
extractPrivateMembers = extractPrivateMembers,
extractAnnotations = extractAnnotations,
null,
listOf()
)
}
Unit
}
is IrEnumEntry -> {
val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
if (parentId != null) {
extractEnumEntry(
declaration,
parentId,
extractPrivateMembers,
extractFunctionBodies
)
}
Unit
}
is IrField -> {
val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
if (parentId != null) {
// For consistency with the Java extractor, enum entries get type accesses
// only if we're extracting from .kt source (i.e., when
// `extractFunctionBodies` is set)
extractField(
declaration,
parentId,
extractAnnotationEnumTypeAccesses = extractFunctionBodies
)
}
Unit
}
is IrTypeAlias -> extractTypeAlias(declaration)
else ->
logger.errorElement(
"Unrecognised IrDeclaration: " + declaration.javaClass,
declaration
)
}
}
}
private fun extractTypeParameter(
tp: IrTypeParameter,
apparentIndex: Int,
javaTypeParameter: JavaTypeParameter?
): Label<out DbTypevariable>? {
with("type parameter", tp) {
val parentId = getTypeParameterParentLabel(tp) ?: return null
val id = tw.getLabelFor<DbTypevariable>(getTypeParameterLabel(tp))
// Note apparentIndex does not necessarily equal `tp.index`, because at least
// constructor type parameters
// have indices offset from the type parameters of the constructed class (i.e. the
// parameter S of
// `class Generic<T> { public <S> Generic(T t, S s) { ... } }` will have `tp.index` 1,
// not 0).
tw.writeTypeVars(id, tp.name.asString(), apparentIndex, parentId)
val locId = tw.getLocation(tp)
tw.writeHasLocation(id, locId)
// Annoyingly, we have no obvious way to pair up the bounds of an IrTypeParameter and a
// JavaTypeParameter
// because JavaTypeParameter provides a Collection not an ordered list, so we can only
// do our best here:
fun tryGetJavaBound(idx: Int) =
when (tp.superTypes.size) {
1 -> javaTypeParameter?.upperBounds?.singleOrNull()
else -> (javaTypeParameter?.upperBounds as? List)?.getOrNull(idx)
}
tp.superTypes.forEachIndexed { boundIdx, bound ->
if (!(bound.isAny() || bound.isNullableAny())) {
tw.getLabelFor<DbTypebound>("@\"bound;$boundIdx;{$id}\"") {
// Note we don't look for @JvmSuppressWildcards here because it doesn't seem
// to have any impact
// on kotlinc adding wildcards to type parameter bounds.
val boundWithWildcards =
addJavaLoweringWildcards(bound, true, tryGetJavaBound(tp.index))
tw.writeTypeBounds(
it,
useType(boundWithWildcards).javaResult.id.cast<DbReftype>(),
boundIdx,
id
)
}
}
}
if (tp.isReified) {
addModifiers(id, "reified")
}
if (tp.variance == Variance.IN_VARIANCE) {
addModifiers(id, "in")
} else if (tp.variance == Variance.OUT_VARIANCE) {
addModifiers(id, "out")
}
// extractAnnotations(tp, id)
// TODO: introduce annotations once they can be disambiguated from bounds, which are
// also child expressions.
return id
}
}
private fun extractVisibility(
elementForLocation: IrElement,
id: Label<out DbModifiable>,
v: DescriptorVisibility
) {
with("visibility", elementForLocation) {
when (v) {
DescriptorVisibilities.PRIVATE -> addModifiers(id, "private")
DescriptorVisibilities.PRIVATE_TO_THIS -> addModifiers(id, "private")
DescriptorVisibilities.PROTECTED -> addModifiers(id, "protected")
DescriptorVisibilities.PUBLIC -> addModifiers(id, "public")
DescriptorVisibilities.INTERNAL -> addModifiers(id, "internal")
DescriptorVisibilities.LOCAL ->
if (elementForLocation is IrFunction && elementForLocation.isLocalFunction()) {
// The containing class is `private`.
addModifiers(id, "public")
} else {
addVisibilityModifierToLocalOrAnonymousClass(id)
}
is DelegatedDescriptorVisibility -> {
when (v.delegate) {
JavaVisibilities.ProtectedStaticVisibility -> {
addModifiers(id, "protected")
addModifiers(id, "static")
}
JavaVisibilities.PackageVisibility -> {
// default java visibility (top level)
}
JavaVisibilities.ProtectedAndPackage -> {
addModifiers(id, "protected")
}
else ->
logger.errorElement(
"Unexpected delegated visibility: $v",
elementForLocation
)
}
}
else -> logger.errorElement("Unexpected visibility: $v", elementForLocation)
}
}
}
private fun extractClassModifiers(c: IrClass, id: Label<out DbClassorinterface>) {
with("class modifiers", c) {
when (c.modality) {
Modality.FINAL -> addModifiers(id, "final")
Modality.SEALED -> addModifiers(id, "sealed")
Modality.OPEN -> {} // This is the default
Modality.ABSTRACT -> addModifiers(id, "abstract")
else -> logger.errorElement("Unexpected class modality: ${c.modality}", c)
}
extractVisibility(c, id, c.visibility)
}
}
fun extractClassInstance(
classLabel: Label<out DbClassorinterface>,
c: IrClass,
argsIncludingOuterClasses: List<IrTypeArgument>?,
shouldExtractOutline: Boolean,
shouldExtractDetails: Boolean
) {
DeclarationStackAdjuster(c).use {
if (shouldExtractOutline) {
extractClassWithoutMembers(c, argsIncludingOuterClasses)
}
if (shouldExtractDetails) {
val supertypeMode =
if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw
else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
extractClassSupertypes(c, classLabel, supertypeMode, true)
extractNonPrivateMemberPrototypes(c, argsIncludingOuterClasses, classLabel)
}
}
}
// `argsIncludingOuterClasses` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
private fun extractClassWithoutMembers(
c: IrClass,
argsIncludingOuterClasses: List<IrTypeArgument>?
): Label<out DbClassorinterface> {
with("class instance", c) {
if (argsIncludingOuterClasses?.isEmpty() == true) {
logger.error("Instance without type arguments: " + c.name.asString())
}
val classLabelResults = getClassLabel(c, argsIncludingOuterClasses)
val id = tw.getLabelFor<DbClassorinterface>(classLabelResults.classLabel)
val pkg = c.packageFqName?.asString() ?: ""
val cls = classLabelResults.shortName
val pkgId = extractPackage(pkg)
// TODO: There's lots of duplication between this and extractClassSource.
// Can we share it?
val sourceId = useClassSource(c)
tw.writeClasses_or_interfaces(id, cls, pkgId, sourceId)
if (c.isInterfaceLike) {
tw.writeIsInterface(id)
} else {
val kind = c.kind
if (kind == ClassKind.ENUM_CLASS) {
tw.writeIsEnumType(id)
} else if (
kind != ClassKind.CLASS &&
kind != ClassKind.OBJECT &&
kind != ClassKind.ENUM_ENTRY
) {
logger.errorElement("Unrecognised class kind $kind", c)
}
}
val typeArgs = removeOuterClassTypeArgs(c, argsIncludingOuterClasses)
if (typeArgs != null) {
// From 1.9, the list might change when we call erase,
// so we make a copy that it is safe to iterate over.
val typeArgsCopy = typeArgs.toList()
for ((idx, arg) in typeArgsCopy.withIndex()) {
val argId = getTypeArgumentLabel(arg).id
tw.writeTypeArgs(argId, idx, id)
}
tw.writeIsParameterized(id)
} else {
tw.writeIsRaw(id)
}
extractClassModifiers(c, id)
extractClassSupertypes(
c,
id,
if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw
else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
)
val locId = getLocation(c, argsIncludingOuterClasses)
tw.writeHasLocation(id, locId)
// Extract the outer <-> inner class relationship, passing on any type arguments in
// excess to this class' parameters if this is an inner class.
// For example, in `class Outer<T> { inner class Inner<S> { } }`, `Inner<Int, String>`
// nests within `Outer<Int>` and raw `Inner<>` within `Outer<>`,
// but for a similar non-`inner` (in Java terms, static nested) class both `Inner<Int>`
// and `Inner<>` nest within the unbound type `Outer`.
val useBoundOuterType =
(c.isInner || c.isLocal) &&
(c.parents.firstNotNullOfOrNull {
when (it) {
is IrClass ->
when {
it.typeParameters.isNotEmpty() ->
true // Type parameters visible to this class -- extract an
// enclosing bound or raw type.
!(it.isInner || it.isLocal) ->
false // No type parameters seen yet, and this is a static
// class -- extract an enclosing unbound type.
else ->
null // No type parameters seen here, but may be visible
// enclosing type parameters; keep searching.
}
else ->
null // Look through enclosing non-class entities (this may need to
// change)
}
} ?: false)
extractEnclosingClass(
c.parent,
id,
c,
locId,
if (useBoundOuterType) argsIncludingOuterClasses?.drop(c.typeParameters.size)
else listOf()
)
return id
}
}
private fun getLocation(
decl: IrDeclaration,
typeArgs: List<IrTypeArgument>?
): Label<DbLocation> {
return if (typeArgs != null && typeArgs.isNotEmpty()) {
val binaryPath = getIrDeclarationBinaryPath(decl)
if (binaryPath == null) {
tw.getLocation(decl)
} else {
val newTrapWriter = tw.makeFileTrapWriter(binaryPath, true)
newTrapWriter.getWholeFileLocation()
}
} else {
tw.getLocation(decl)
}
}
private fun makeTypeParamSubstitution(
c: IrClass,
argsIncludingOuterClasses: List<IrTypeArgument>?
) =
when (argsIncludingOuterClasses) {
null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
}
fun extractDeclarationPrototype(
d: IrDeclaration,
parentId: Label<out DbReftype>,
argsIncludingOuterClasses: List<IrTypeArgument>?,
typeParamSubstitutionQ: TypeSubstitution? = null
) {
val typeParamSubstitution =
typeParamSubstitutionQ
?: when (val parent = d.parent) {
is IrClass -> makeTypeParamSubstitution(parent, argsIncludingOuterClasses)
else -> {
logger.warnElement("Unable to extract prototype of local declaration", d)
return
}
}
when (d) {
is IrFunction ->
extractFunction(
d,
parentId,
extractBody = false,
extractMethodAndParameterTypeAccesses = false,
extractAnnotations = false,
typeParamSubstitution,
argsIncludingOuterClasses
)
is IrProperty ->
extractProperty(
d,
parentId,
extractBackingField = false,
extractFunctionBodies = false,
extractPrivateMembers = false,
extractAnnotations = false,
typeParamSubstitution,
argsIncludingOuterClasses
)
else -> {}
}
}
// `argsIncludingOuterClasses` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
private fun extractNonPrivateMemberPrototypes(
c: IrClass,
argsIncludingOuterClasses: List<IrTypeArgument>?,
id: Label<out DbClassorinterface>
) {
with("member prototypes", c) {
val typeParamSubstitution = makeTypeParamSubstitution(c, argsIncludingOuterClasses)
c.declarations.map {
if (shouldExtractDecl(it, false)) {
extractDeclarationPrototype(
it,
id,
argsIncludingOuterClasses,
typeParamSubstitution
)
}
}
}
}
private fun extractLocalTypeDeclStmt(
c: IrClass,
callable: Label<out DbCallable>,
parent: Label<out DbStmtparent>,
idx: Int
) {
val id =
extractClassSource(
c,
extractDeclarations = true,
extractStaticInitializer = true,
extractPrivateMembers = true,
extractFunctionBodies = true
)
extractLocalTypeDeclStmt(id, c, callable, parent, idx)
}
private fun extractLocalTypeDeclStmt(
id: Label<out DbClassorinterface>,
locElement: IrElement,
callable: Label<out DbCallable>,
parent: Label<out DbStmtparent>,
idx: Int
) {
val stmtId = tw.getFreshIdLabel<DbLocaltypedeclstmt>()
tw.writeStmts_localtypedeclstmt(stmtId, parent, idx, callable)
tw.writeIsLocalClassOrInterface(id, stmtId)
val locId = tw.getLocation(locElement)
tw.writeHasLocation(stmtId, locId)
}
private fun extractObinitFunction(c: IrClass, parentId: Label<out DbClassorinterface>) {
// add method:
val obinitLabel = getObinitLabel(c, parentId)
val obinitId = tw.getLabelFor<DbMethod>(obinitLabel)
val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
tw.writeMethods(
obinitId,
"<obinit>",
"<obinit>()",
returnType.javaResult.id,
parentId,
obinitId
)
tw.writeMethodsKotlinType(obinitId, returnType.kotlinResult.id)
val locId = tw.getLocation(c)
tw.writeHasLocation(obinitId, locId)
addModifiers(obinitId, "private")
// add body:
val blockId = extractBlockBody(obinitId, locId)
extractDeclInitializers(c.declarations, false) { Pair(blockId, obinitId) }
}
private val javaLangDeprecated by lazy { referenceExternalClass("java.lang.Deprecated") }
private val javaLangDeprecatedConstructor by lazy {
javaLangDeprecated?.constructors?.singleOrNull()
}
private fun replaceKotlinDeprecatedAnnotation(
annotations: List<IrConstructorCall>
): List<IrConstructorCall> {
val shouldReplace =
annotations.any {
(it.type as? IrSimpleType)?.classFqName?.asString() == "kotlin.Deprecated"
} && annotations.none { it.type.classOrNull == javaLangDeprecated?.symbol }
val jldConstructor = javaLangDeprecatedConstructor
if (!shouldReplace || jldConstructor == null) return annotations
return annotations.filter {
(it.type as? IrSimpleType)?.classFqName?.asString() != "kotlin.Deprecated"
} +
// Note we lose any arguments to @java.lang.Deprecated that were written in source.
IrConstructorCallImpl.fromSymbolOwner(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
jldConstructor.returnType,
jldConstructor.symbol,
0
)
}
private fun extractAnnotations(
c: IrAnnotationContainer,
annotations: List<IrConstructorCall>,
parent: Label<out DbExprparent>,
extractEnumTypeAccesses: Boolean
) {
val origin =
(c as? IrDeclaration)?.origin
?: run {
logger.warn("Unexpected annotation container: $c")
return
}
val replacedAnnotations =
if (origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB)
replaceKotlinDeprecatedAnnotation(annotations)
else annotations
val groupedAnnotations =
metaAnnotationSupport.groupRepeatableAnnotations(replacedAnnotations)
for ((idx, constructorCall: IrConstructorCall) in
groupedAnnotations.sortedBy { v -> v.type.classFqName?.asString() }.withIndex()) {
extractAnnotation(constructorCall, parent, idx, extractEnumTypeAccesses)
}
}
private fun extractAnnotations(
c: IrAnnotationContainer,
parent: Label<out DbExprparent>,
extractEnumTypeAccesses: Boolean
) {
extractAnnotations(c, c.annotations, parent, extractEnumTypeAccesses)
}
private fun extractAnnotation(
constructorCall: IrConstructorCall,
parent: Label<out DbExprparent>,
idx: Int,
extractEnumTypeAccesses: Boolean,
contextLabel: String? = null
): Label<out DbExpr> {
// Erase the type here because the JVM lowering erases the annotation type, and so the Java
// extractor will see it in erased form.
val t = useType(erase(constructorCall.type))
val annotationContextLabel = contextLabel ?: "{${t.javaResult.id}}"
val id =
tw.getLabelFor<DbDeclannotation>("@\"annotation;{$parent};$annotationContextLabel\"")
tw.writeExprs_declannotation(id, t.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, t.kotlinResult.id)
val locId = tw.getLocation(constructorCall)
tw.writeHasLocation(id, locId)
for (i in 0 until constructorCall.valueArgumentsCount) {
val param = constructorCall.symbol.owner.valueParameters[i]
val prop =
constructorCall.symbol.owner.parentAsClass.declarations
.filterIsInstance<IrProperty>()
.first { it.name == param.name }
val v = constructorCall.getValueArgument(i) ?: param.defaultValue?.expression
val getter = prop.getter
if (getter == null) {
logger.warnElement("Expected annotation property to define a getter", prop)
} else {
val getterId = useFunction<DbMethod>(getter)
if (getterId == null) {
logger.errorElement("Couldn't get ID for getter", getter)
} else {
val exprId =
extractAnnotationValueExpression(
v,
id,
i,
"{$getterId}",
getter.returnType,
extractEnumTypeAccesses
)
if (exprId != null) {
tw.writeAnnotValue(id, getterId, exprId)
}
}
}
}
return id
}
private fun extractAnnotationValueExpression(
v: IrExpression?,
parent: Label<out DbExprparent>,
idx: Int,
contextLabel: String,
contextType: IrType?,
extractEnumTypeAccesses: Boolean
): Label<out DbExpr>? {
fun exprId() = tw.getLabelFor<DbExpr>("@\"annotationExpr;{$parent};$idx\"")
return when (v) {
is CodeQLIrConst<*> -> {
extractConstant(v, parent, idx, null, null, overrideId = exprId())
}
is IrGetEnumValue -> {
extractEnumValue(
v,
parent,
idx,
null,
null,
extractTypeAccess = extractEnumTypeAccesses,
overrideId = exprId()
)
}
is IrClassReference -> {
val classRefId = exprId()
val typeAccessId =
tw.getLabelFor<DbUnannotatedtypeaccess>("@\"annotationExpr;{$classRefId};0\"")
extractClassReference(
v,
parent,
idx,
null,
null,
overrideId = classRefId,
typeAccessOverrideId = typeAccessId,
useJavaLangClassType = true
)
}
is IrConstructorCall -> {
extractAnnotation(v, parent, idx, extractEnumTypeAccesses, contextLabel)
}
is IrVararg -> {
tw.getLabelFor<DbArrayinit>("@\"annotationarray;{$parent};$contextLabel\"").also {
arrayId ->
// Use the context type (i.e., the type the annotation expects, not the actual
// type of the array)
// because the Java extractor fills in array types using the same technique.
// These should only
// differ for generic annotations.
if (contextType == null) {
logger.warnElement(
"Expected an annotation array to have an enclosing context",
v
)
} else {
val type = useType(kClassToJavaClass(contextType))
tw.writeExprs_arrayinit(arrayId, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(arrayId, type.kotlinResult.id)
tw.writeHasLocation(arrayId, tw.getLocation(v))
v.elements.forEachIndexed { index, irVarargElement ->
run {
val argExpr =
when (irVarargElement) {
is IrExpression -> irVarargElement
is IrSpreadElement -> irVarargElement.expression
else -> {
logger.errorElement(
"Unrecognised IrVarargElement: " +
irVarargElement.javaClass,
irVarargElement
)
null
}
}
extractAnnotationValueExpression(
argExpr,
arrayId,
index,
"child;$index",
null,
extractEnumTypeAccesses
)
}
}
}
}
}
// is IrErrorExpression
// null
// Note: emitting an ErrorExpr here would induce an inconsistency if this annotation is
// later seen from source or by the Java extractor,
// in both of which cases the real value will get extracted.
else -> null
}
}
fun extractClassSource(
c: IrClass,
extractDeclarations: Boolean,
extractStaticInitializer: Boolean,
extractPrivateMembers: Boolean,
extractFunctionBodies: Boolean
): Label<out DbClassorinterface> {
with("class source", c) {
DeclarationStackAdjuster(c).use {
val id = useClassSource(c)
val pkg = c.packageFqName?.asString() ?: ""
val cls = if (c.isAnonymousObject) "" else c.name.asString()
val pkgId = extractPackage(pkg)
tw.writeClasses_or_interfaces(id, cls, pkgId, id)
if (c.isInterfaceLike) {
tw.writeIsInterface(id)
if (c.kind == ClassKind.ANNOTATION_CLASS) {
tw.writeIsAnnotType(id)
}
} else {
val kind = c.kind
if (kind == ClassKind.ENUM_CLASS) {
tw.writeIsEnumType(id)
} else if (
kind != ClassKind.CLASS &&
kind != ClassKind.OBJECT &&
kind != ClassKind.ENUM_ENTRY
) {
logger.warnElement("Unrecognised class kind $kind", c)
}
if (c.origin == IrDeclarationOrigin.FILE_CLASS) {
tw.writeFile_class(id)
}
if (c.isData) {
tw.writeKtDataClasses(id)
}
}
val locId = tw.getLocation(c)
tw.writeHasLocation(id, locId)
extractEnclosingClass(c.parent, id, c, locId, listOf())
val javaClass = (c.source as? JavaSourceElement)?.javaElement as? JavaClass
c.typeParameters.mapIndexed { idx, param ->
extractTypeParameter(param, idx, javaClass?.typeParameters?.getOrNull(idx))
}
if (extractDeclarations) {
if (c.kind == ClassKind.ANNOTATION_CLASS) {
c.declarations.filterIsInstance<IrProperty>().forEach {
val getter = it.getter
if (getter == null) {
logger.warnElement(
"Expected an annotation property to have a getter",
it
)
} else {
extractFunction(
getter,
id,
extractBody = false,
extractMethodAndParameterTypeAccesses =
extractFunctionBodies,
extractAnnotations = true,
null,
listOf()
)
?.also { functionLabel ->
tw.writeIsAnnotElem(functionLabel.cast())
}
}
}
} else {
try {
c.declarations.forEach {
extractDeclaration(
it,
extractPrivateMembers = extractPrivateMembers,
extractFunctionBodies = extractFunctionBodies,
extractAnnotations = true
)
}
if (extractStaticInitializer) extractStaticInitializer(c, { id })
extractJvmStaticProxyMethods(
c,
id,
extractPrivateMembers,
extractFunctionBodies
)
} catch (e: IllegalArgumentException) {
// A Kotlin bug causes this to throw: https://youtrack.jetbrains.com/issue/KT-63847/K2-IllegalStateException-IrFieldPublicSymbolImpl-for-java.time-Clock.OffsetClock.offset0-is-already-bound
// TODO: This should either be removed or log something, once the bug is fixed
}
}
}
if (c.isNonCompanionObject) {
// For `object MyObject { ... }`, the .class has an
// automatically-generated `public static final MyObject INSTANCE`
// field that may be referenced from Java code, and is used in our
// IrGetObjectValue support. We therefore need to fabricate it
// here.
val instance = useObjectClassInstance(c)
val type = useSimpleTypeClass(c, emptyList(), false)
tw.writeFields(instance.id, instance.name, type.javaResult.id, id)
tw.writeFieldsKotlinType(instance.id, type.kotlinResult.id)
tw.writeHasLocation(instance.id, locId)
addModifiers(instance.id, "public", "static", "final")
tw.writeClass_object(id, instance.id)
}
if (c.isObject) {
addModifiers(id, "static")
}
if (extractFunctionBodies && needsObinitFunction(c)) {
extractObinitFunction(c, id)
}
extractClassModifiers(c, id)
extractClassSupertypes(
c,
id,
inReceiverContext = true
) // inReceiverContext = true is specified to force extraction of member prototypes
// of base types
linesOfCode?.linesOfCodeInDeclaration(c, id)
val additionalAnnotations =
if (
c.kind == ClassKind.ANNOTATION_CLASS &&
c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
)
metaAnnotationSupport.generateJavaMetaAnnotations(c, extractFunctionBodies)
else listOf()
extractAnnotations(
c,
c.annotations + additionalAnnotations,
id,
extractFunctionBodies
)
if (extractFunctionBodies && !c.isAnonymousObject && !c.isLocal)
externalClassExtractor.writeStubTrapFile(c)
return id
}
}
}
val jvmStaticFqName = FqName("kotlin.jvm.JvmStatic")
private fun extractJvmStaticProxyMethods(
c: IrClass,
classId: Label<out DbClassorinterface>,
extractPrivateMembers: Boolean,
extractFunctionBodies: Boolean
) {
// Add synthetic forwarders for any JvmStatic methods or properties:
val companionObject = c.companionObject() ?: return
val cType = c.typeWith()
val companionType = companionObject.typeWith()
fun makeProxyFunction(f: IrFunction) {
// Emit a function in class `c` that delegates to the same function defined on
// `c.CompanionInstance`.
val proxyFunctionId = tw.getLabelFor<DbMethod>(getFunctionLabel(f, classId, listOf()))
// We extract the function prototype with its ID overridden to belong to `c` not the
// companion object,
// but suppress outputting the body, which we will replace with a delegating call below.
forceExtractFunction(
f,
classId,
extractBody = false,
extractMethodAndParameterTypeAccesses = extractFunctionBodies,
extractAnnotations = false,
typeSubstitution = null,
classTypeArgsIncludingOuterClasses = listOf(),
extractOrigin = false,
OverriddenFunctionAttributes(id = proxyFunctionId)
)
addModifiers(proxyFunctionId, "static")
tw.writeCompiler_generated(
proxyFunctionId,
CompilerGeneratedKinds.JVMSTATIC_PROXY_METHOD.kind
)
if (extractFunctionBodies) {
val realFunctionLocId = tw.getLocation(f)
extractExpressionBody(proxyFunctionId, realFunctionLocId).also { returnId ->
extractRawMethodAccess(
f,
realFunctionLocId,
f.returnType,
proxyFunctionId,
returnId,
0,
returnId,
f.valueParameters.size,
{ argParent, idxOffset ->
f.valueParameters.forEachIndexed { idx, param ->
val syntheticParamId = useValueParameter(param, proxyFunctionId)
extractVariableAccess(
syntheticParamId,
param.type,
realFunctionLocId,
argParent,
idxOffset + idx,
proxyFunctionId,
returnId
)
}
},
companionType,
{ callId ->
val companionField =
useCompanionObjectClassInstance(companionObject)?.id
extractVariableAccess(
companionField,
companionType,
realFunctionLocId,
callId,
-1,
proxyFunctionId,
returnId
)
.also { varAccessId ->
extractTypeAccessRecursive(
cType,
realFunctionLocId,
varAccessId,
-1,
proxyFunctionId,
returnId
)
}
},
null
)
}
}
}
companionObject.declarations.forEach {
if (shouldExtractDecl(it, extractPrivateMembers)) {
val wholeDeclAnnotated = it.hasAnnotation(jvmStaticFqName)
when (it) {
is IrFunction -> {
if (wholeDeclAnnotated) {
makeProxyFunction(it)
if (it.hasAnnotation(jvmOverloadsFqName)) {
extractGeneratedOverloads(
it,
classId,
classId,
extractFunctionBodies,
extractMethodAndParameterTypeAccesses = extractFunctionBodies,
typeSubstitution = null,
classTypeArgsIncludingOuterClasses = listOf()
)
}
}
}
is IrProperty -> {
it.getter?.let { getter ->
if (wholeDeclAnnotated || getter.hasAnnotation(jvmStaticFqName))
makeProxyFunction(getter)
}
it.setter?.let { setter ->
if (wholeDeclAnnotated || setter.hasAnnotation(jvmStaticFqName))
makeProxyFunction(setter)
}
}
}
}
}
}
/**
* This function traverses the declaration-parent hierarchy upwards, and retrieves the enclosing
* class of a class to extract the `enclInReftype` relation. Additionally, it extracts a
* companion field for a companion object into its parent class.
*
* Note that the nested class can also be a local class declared inside a function, so the
* upwards traversal is skipping the non-class parents. Also, in some cases the file class is
* the enclosing one, which has no IR representation.
*/
private fun extractEnclosingClass(
declarationParent:
IrDeclarationParent, // The declaration parent of the element for which we are
// extracting the enclosing class
innerId: Label<out DbClassorinterface>, // ID of the inner class
innerClass:
IrClass?, // The inner class, if available. It's not available if the enclosing class of
// a generated class is being extracted
innerLocId: Label<DbLocation>, // Location of the inner class
parentClassTypeArguments:
List<
IrTypeArgument
>? // Type arguments of the parent class. If `parentClassTypeArguments` is null, the
// parent class is a raw type
) {
with("enclosing class", declarationParent) {
var parent: IrDeclarationParent? = declarationParent
while (parent != null) {
if (parent is IrClass) {
val parentId = useClassInstance(parent, parentClassTypeArguments).typeResult.id
tw.writeEnclInReftype(innerId, parentId)
if (innerClass != null && innerClass.isCompanion) {
// If we are a companion then our parent has a
// public static final ParentClass$CompanionObjectClass
// CompanionObjectName;
// that we need to fabricate here
val instance = useCompanionObjectClassInstance(innerClass)
if (instance != null) {
val type = useSimpleTypeClass(innerClass, emptyList(), false)
tw.writeFields(
instance.id,
instance.name,
type.javaResult.id,
parentId
)
tw.writeFieldsKotlinType(instance.id, type.kotlinResult.id)
tw.writeHasLocation(instance.id, innerLocId)
addModifiers(instance.id, "public", "static", "final")
tw.writeType_companion_object(parentId, instance.id, innerId)
}
}
break
} else if (parent is IrFile) {
if (innerClass != null && !innerClass.isLocal) {
// We don't have to extract file class containers for classes except for
// local classes
break
}
if (this.filePath != parent.path) {
logger.error("Unexpected file parent found")
}
val fileId = extractFileClass(parent)
tw.writeEnclInReftype(innerId, fileId)
break
}
parent = (parent as? IrDeclaration)?.parent
}
}
}
private data class FieldResult(val id: Label<DbField>, val name: String)
private fun useCompanionObjectClassInstance(c: IrClass): FieldResult? {
val parent = c.parent
if (!c.isCompanion) {
logger.error("Using companion instance for non-companion class")
return null
} else if (parent !is IrClass) {
logger.error("Using companion instance for non-companion class")
return null
} else {
val parentId = useClassInstance(parent, listOf()).typeResult.id
val instanceName = c.name.asString()
val instanceLabel = "@\"field;{$parentId};$instanceName\""
val instanceId: Label<DbField> = tw.getLabelFor(instanceLabel)
return FieldResult(instanceId, instanceName)
}
}
private fun useObjectClassInstance(c: IrClass): FieldResult {
if (!c.isNonCompanionObject) {
logger.error("Using instance for non-object class")
}
val classId = useClassInstance(c, listOf()).typeResult.id
val instanceName = "INSTANCE"
val instanceLabel = "@\"field;{$classId};$instanceName\""
val instanceId: Label<DbField> = tw.getLabelFor(instanceLabel)
return FieldResult(instanceId, instanceName)
}
@OptIn(ObsoleteDescriptorBasedAPI::class)
private fun hasSynthesizedParameterNames(f: IrFunction) =
f.descriptor.hasSynthesizedParameterNames()
private fun extractValueParameter(
vp: IrValueParameter,
parent: Label<out DbCallable>,
idx: Int,
typeSubstitution: TypeSubstitution?,
parentSourceDeclaration: Label<out DbCallable>,
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?,
extractTypeAccess: Boolean,
locOverride: Label<DbLocation>? = null
): TypeResults {
with("value parameter", vp) {
val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses)
val maybeAlteredType =
(vp.parent as? IrFunction)?.let {
if (overridesCollectionsMethodWithAlteredParameterTypes(it))
eraseCollectionsMethodParameterType(vp.type, it.name.asString(), idx)
else if (
(vp.parent as? IrConstructor)?.parentClassOrNull?.kind ==
ClassKind.ANNOTATION_CLASS
)
kClassToJavaClass(vp.type)
else null
} ?: vp.type
val javaType =
(vp.parent as? IrFunction)?.let {
getJavaCallable(it)?.let { jCallable ->
getJavaValueParameterType(jCallable, idx)
}
}
val typeWithWildcards =
addJavaLoweringWildcards(
maybeAlteredType,
!getInnermostWildcardSupppressionAnnotation(vp),
javaType
)
val substitutedType =
typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) }
?: typeWithWildcards
val id = useValueParameter(vp, parent)
if (extractTypeAccess) {
extractTypeAccessRecursive(substitutedType, location, id, -1)
}
val syntheticParameterNames =
isUnderscoreParameter(vp) ||
((vp.parent as? IrFunction)?.let { hasSynthesizedParameterNames(it) } ?: true)
val javaParameter =
when (val callable = (vp.parent as? IrFunction)?.let { getJavaCallable(it) }) {
is JavaConstructor -> callable.valueParameters.getOrNull(idx)
is JavaMethod -> callable.valueParameters.getOrNull(idx)
else -> null
}
val extraAnnotations =
listOfNotNull(
getNullabilityAnnotation(
vp.type,
vp.origin,
vp.annotations,
javaParameter?.annotations
)
)
extractAnnotations(vp, vp.annotations + extraAnnotations, id, extractTypeAccess)
return extractValueParameter(
id,
substitutedType,
vp.name.asString(),
location,
parent,
idx,
useValueParameter(vp, parentSourceDeclaration),
syntheticParameterNames,
vp.isVararg,
vp.isNoinline,
vp.isCrossinline
)
}
}
private fun extractValueParameter(
id: Label<out DbParam>,
t: IrType,
name: String,
locId: Label<DbLocation>,
parent: Label<out DbCallable>,
idx: Int,
paramSourceDeclaration: Label<out DbParam>,
syntheticParameterNames: Boolean,
isVararg: Boolean,
isNoinline: Boolean,
isCrossinline: Boolean
): TypeResults {
val type = useType(t)
tw.writeParams(id, type.javaResult.id, idx, parent, paramSourceDeclaration)
tw.writeParamsKotlinType(id, type.kotlinResult.id)
tw.writeHasLocation(id, locId)
if (!syntheticParameterNames) {
tw.writeParamName(id, name)
}
if (isVararg) {
tw.writeIsVarargsParam(id)
}
if (isNoinline) {
addModifiers(id, "noinline")
}
if (isCrossinline) {
addModifiers(id, "crossinline")
}
return type
}
/**
* mkContainerLabel is a lambda so that we get laziness: If the container is a file, then we
* don't want to extract the file class unless something actually needs it.
*/
private fun extractStaticInitializer(
container: IrDeclarationContainer,
mkContainerLabel: () -> Label<out DbClassorinterface>
) {
with("static initializer extraction", container) {
extractDeclInitializers(container.declarations, true) {
val containerId = mkContainerLabel()
val clinitLabel =
getFunctionLabel(
container,
containerId,
"<clinit>",
listOf(),
pluginContext.irBuiltIns.unitType,
extensionParamType = null,
functionTypeParameters = listOf(),
classTypeArgsIncludingOuterClasses = listOf(),
overridesCollectionsMethod = false,
javaSignature = null,
addParameterWildcardsByDefault = false
)
val clinitId = tw.getLabelFor<DbMethod>(clinitLabel)
val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
tw.writeMethods(
clinitId,
"<clinit>",
"<clinit>()",
returnType.javaResult.id,
containerId,
clinitId
)
tw.writeMethodsKotlinType(clinitId, returnType.kotlinResult.id)
tw.writeCompiler_generated(
clinitId,
CompilerGeneratedKinds.CLASS_INITIALISATION_METHOD.kind
)
val locId = tw.getWholeFileLocation()
tw.writeHasLocation(clinitId, locId)
addModifiers(clinitId, "static")
// add and return body block:
Pair(extractBlockBody(clinitId, locId), clinitId)
}
}
}
private fun extractInstanceInitializerBlock(
parent: StmtParent,
enclosingConstructor: IrConstructor
) {
with("object initializer block", enclosingConstructor) {
val constructorId = useFunction<DbConstructor>(enclosingConstructor)
if (constructorId == null) {
logger.errorElement("Cannot get ID for constructor", enclosingConstructor)
return
}
val enclosingClass = enclosingConstructor.parentClassOrNull
if (enclosingClass == null) {
logger.errorElement("Constructor's parent is not a class", enclosingConstructor)
return
}
// Don't make this block lazily since we need to insert something at the given
// parent.idx position,
// and in the case where there are no initializers to emit an empty block is an
// acceptable filler.
val initBlockId =
tw.getFreshIdLabel<DbBlock>().also {
tw.writeStmts_block(it, parent.parent, parent.idx, constructorId)
val locId = tw.getLocation(enclosingConstructor)
tw.writeHasLocation(it, locId)
}
extractDeclInitializers(enclosingClass.declarations, false) {
Pair(initBlockId, constructorId)
}
}
}
private fun extractDeclInitializers(
declarations: List<IrDeclaration>,
extractStaticInitializers: Boolean,
makeEnclosingBlock: () -> Pair<Label<DbBlock>, Label<out DbCallable>>
) {
val blockAndFunctionId by lazy { makeEnclosingBlock() }
// Extract field initializers and init blocks (the latter can only occur in object
// initializers)
var idx = 0
fun extractFieldInitializer(f: IrDeclaration) {
val static: Boolean
val initializer: IrExpressionBody?
val lhsType: TypeResults?
val vId: Label<out DbVariable>?
val isAnnotationClassField: Boolean
if (f is IrField) {
static = f.isStatic
initializer = f.initializer
isAnnotationClassField = isAnnotationClassField(f)
lhsType = useType(if (isAnnotationClassField) kClassToJavaClass(f.type) else f.type)
vId = useField(f)
} else if (f is IrEnumEntry) {
static = true
initializer = f.initializerExpression
isAnnotationClassField = false
lhsType = getEnumEntryType(f)
if (lhsType == null) {
return
}
vId = useEnumEntry(f)
} else {
return
}
if (static != extractStaticInitializers || initializer == null) {
return
}
val expr = initializer.expression
val declLocId = tw.getLocation(f)
extractExpressionStmt(
declLocId,
blockAndFunctionId.first,
idx++,
blockAndFunctionId.second
)
.also { stmtId ->
val type =
if (isAnnotationClassField) kClassToJavaClass(expr.type) else expr.type
extractAssignExpr(type, declLocId, stmtId, 0, blockAndFunctionId.second, stmtId)
.also { assignmentId ->
tw.writeKtInitializerAssignment(assignmentId)
extractVariableAccess(
vId,
lhsType,
declLocId,
assignmentId,
0,
blockAndFunctionId.second,
stmtId
)
.also { lhsId ->
if (static) {
extractStaticTypeAccessQualifier(
f,
lhsId,
declLocId,
blockAndFunctionId.second,
stmtId
)
}
}
extractExpressionExpr(
expr,
blockAndFunctionId.second,
assignmentId,
1,
stmtId
)
}
}
}
for (decl in declarations) {
when (decl) {
is IrProperty -> {
decl.backingField?.let { extractFieldInitializer(it) }
}
is IrField -> {
extractFieldInitializer(decl)
}
is IrEnumEntry -> {
extractFieldInitializer(decl)
}
is IrAnonymousInitializer -> {
if (decl.isStatic != extractStaticInitializers) {
continue
}
for (stmt in decl.body.statements) {
extractStatement(
stmt,
blockAndFunctionId.second,
blockAndFunctionId.first,
idx++
)
}
}
else -> continue
}
}
}
private fun isKotlinDefinedInterface(cls: IrClass?) =
cls != null &&
cls.isInterface &&
cls.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
private fun needsInterfaceForwarder(f: IrFunction) =
// jvmDefaultModeEnabledIsEnabled means that -Xjvm-default=all or all-compatibility was
// used, in which case real Java default interfaces are used, and we don't need to do
// anything.
// Otherwise, for a Kotlin-defined method inheriting a Kotlin-defined default, we need to
// create a synthetic method like
// `int f(int x) { return super.InterfaceWithDefault.f(x); }`, because kotlinc will generate
// a public method and Java callers may directly target it.
// (NB. kotlinc's actual implementation strategy is different -- it makes an inner class
// called InterfaceWithDefault$DefaultImpls and stores the default methods
// there to allow default method usage in Java < 8, but this is hopefully niche.
!jvmDefaultModeEnabledIsEnabled(
pluginContext.languageVersionSettings
.getFlag(JvmAnalysisFlags.jvmDefaultMode)) &&
f.parentClassOrNull.let {
it != null &&
it.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB &&
it.modality != Modality.ABSTRACT
} &&
f.realOverrideTarget.let {
it != f &&
(it as? IrSimpleFunction)?.modality != Modality.ABSTRACT &&
isKotlinDefinedInterface(it.parentClassOrNull)
}
private fun makeInterfaceForwarder(
f: IrFunction,
parentId: Label<out DbReftype>,
extractBody: Boolean,
extractMethodAndParameterTypeAccesses: Boolean,
typeSubstitution: TypeSubstitution?,
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
) =
forceExtractFunction(
f,
parentId,
extractBody = false,
extractMethodAndParameterTypeAccesses,
extractAnnotations = false,
typeSubstitution,
classTypeArgsIncludingOuterClasses,
overriddenAttributes =
OverriddenFunctionAttributes(
visibility = DescriptorVisibilities.PUBLIC,
modality = Modality.OPEN
)
)
.also { functionId ->
tw.writeCompiler_generated(
functionId,
CompilerGeneratedKinds.INTERFACE_FORWARDER.kind
)
if (extractBody) {
val realFunctionLocId = tw.getLocation(f)
val inheritedDefaultFunction = f.realOverrideTarget
val directlyInheritedSymbol =
when (f) {
is IrSimpleFunction ->
f.overriddenSymbols.find { it.owner === inheritedDefaultFunction }
?: f.overriddenSymbols.find {
it.owner.realOverrideTarget === inheritedDefaultFunction
}
?: inheritedDefaultFunction.symbol
else ->
inheritedDefaultFunction
.symbol // This is strictly invalid, since we shouldn't use
// A.super.f(...) where A may not be a direct supertype,
// but this path should also be unreachable.
}
val defaultDefiningInterfaceType =
(directlyInheritedSymbol.owner.parentClassOrNull ?: return functionId)
.typeWith()
extractExpressionBody(functionId, realFunctionLocId).also { returnId ->
extractRawMethodAccess(
f,
realFunctionLocId,
f.returnType,
functionId,
returnId,
0,
returnId,
f.valueParameters.size,
{ argParentId, idxOffset ->
f.valueParameters.mapIndexed { idx, param ->
val syntheticParamId = useValueParameter(param, functionId)
extractVariableAccess(
syntheticParamId,
param.type,
realFunctionLocId,
argParentId,
idxOffset + idx,
functionId,
returnId
)
}
},
f.dispatchReceiverParameter?.type,
{ callId ->
extractSuperAccess(
defaultDefiningInterfaceType,
functionId,
callId,
-1,
returnId,
realFunctionLocId
)
},
null
)
}
}
}
private fun extractFunction(
f: IrFunction,
parentId: Label<out DbReftype>,
extractBody: Boolean,
extractMethodAndParameterTypeAccesses: Boolean,
extractAnnotations: Boolean,
typeSubstitution: TypeSubstitution?,
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
) =
if (isFake(f)) {
if (needsInterfaceForwarder(f))
makeInterfaceForwarder(
f,
parentId,
extractBody,
extractMethodAndParameterTypeAccesses,
typeSubstitution,
classTypeArgsIncludingOuterClasses
)
else null
} else {
// Work around an apparent bug causing redeclarations of `fun toString(): String`
// specifically in interfaces loaded from Java classes show up like fake overrides.
val overriddenVisibility =
if (f.isFakeOverride && isJavaBinaryObjectMethodRedeclaration(f))
OverriddenFunctionAttributes(visibility = DescriptorVisibilities.PUBLIC)
else null
forceExtractFunction(
f,
parentId,
extractBody,
extractMethodAndParameterTypeAccesses,
extractAnnotations,
typeSubstitution,
classTypeArgsIncludingOuterClasses,
overriddenAttributes = overriddenVisibility
)
.also {
// The defaults-forwarder function is a static utility, not a member, so we only
// need to extract this for the unspecialised instance of this class.
if (classTypeArgsIncludingOuterClasses.isNullOrEmpty())
extractDefaultsFunction(
f,
parentId,
extractBody,
extractMethodAndParameterTypeAccesses
)
extractGeneratedOverloads(
f,
parentId,
null,
extractBody,
extractMethodAndParameterTypeAccesses,
typeSubstitution,
classTypeArgsIncludingOuterClasses
)
}
}
private fun extractDefaultsFunction(
f: IrFunction,
parentId: Label<out DbReftype>,
extractBody: Boolean,
extractMethodAndParameterTypeAccesses: Boolean
) {
if (f.valueParameters.none { it.defaultValue != null }) return
val id = getDefaultsMethodLabel(f)
if (id == null) {
logger.errorElement("Cannot get defaults method label for function", f)
return
}
val locId = getLocation(f, null)
val extReceiver = f.extensionReceiverParameter
val dispatchReceiver = if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter
val parameterTypes = getDefaultsMethodArgTypes(f)
val allParamTypeResults =
parameterTypes.mapIndexed { i, paramType ->
val paramId = tw.getLabelFor<DbParam>(getValueParameterLabel(id, i))
extractValueParameter(
paramId,
paramType,
"p$i",
locId,
id,
i,
paramId,
isVararg = false,
syntheticParameterNames = true,
isCrossinline = false,
isNoinline = false
)
.also {
if (extractMethodAndParameterTypeAccesses)
extractTypeAccess(useType(paramType), locId, paramId, -1)
}
}
val paramsSignature =
allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") {
signatureOrWarn(it.javaResult, f)
}
val shortName = getDefaultsMethodName(f)
if (f.symbol is IrConstructorSymbol) {
val constrId = id.cast<DbConstructor>()
extractConstructor(constrId, shortName, paramsSignature, parentId, constrId)
} else {
val methodId = id.cast<DbMethod>()
extractMethod(
methodId,
locId,
shortName,
erase(f.returnType),
paramsSignature,
parentId,
methodId,
origin = null,
extractTypeAccess = extractMethodAndParameterTypeAccesses
)
addModifiers(id, "static")
if (extReceiver != null) {
val idx = if (dispatchReceiver != null) 1 else 0
val extendedType = allParamTypeResults[idx]
tw.writeKtExtensionFunctions(
methodId,
extendedType.javaResult.id,
extendedType.kotlinResult.id
)
}
}
tw.writeHasLocation(id, locId)
if (
f.visibility != DescriptorVisibilities.PRIVATE &&
f.visibility != DescriptorVisibilities.PRIVATE_TO_THIS
) {
// Private methods have package-private (default) visibility $default methods; all other
// visibilities seem to produce a public $default method.
addModifiers(id, "public")
}
tw.writeCompiler_generated(id, CompilerGeneratedKinds.DEFAULT_ARGUMENTS_METHOD.kind)
if (extractBody) {
val nonSyntheticParams = listOfNotNull(dispatchReceiver) + f.valueParameters
// This stack entry represents as if we're extracting the 'real' function `f`, giving
// the indices of its non-synthetic parameters
// such that when we extract the default expressions below, any reference to f's nth
// parameter will resolve to f$default's
// n + o'th parameter, where `o` is the parameter offset caused by adding any dispatch
// receiver to the parameter list.
// Note we don't need to add the extension receiver here because `useValueParameter`
// always assumes an extension receiver
// will be prepended if one exists.
val realFunctionId = useFunction<DbCallable>(f, parentId, null)
DeclarationStackAdjuster(
f,
OverriddenFunctionAttributes(
id,
id,
locId,
nonSyntheticParams,
typeParameters = listOf(),
isStatic = true
)
)
.use {
val realParamsVarId = getValueParameterLabel(id, parameterTypes.size - 2)
val intType = pluginContext.irBuiltIns.intType
val paramIdxOffset =
listOf(dispatchReceiver, f.extensionReceiverParameter).count { it != null }
extractBlockBody(id, locId).also { blockId ->
var nextStmt = 0
// For each parameter with a default, sub in the default value if the caller
// hasn't supplied a value:
f.valueParameters.forEachIndexed { paramIdx, param ->
val defaultVal = param.defaultValue
if (defaultVal != null) {
extractIfStmt(locId, blockId, nextStmt++, id).also { ifId ->
// if (realParams & thisParamBit == 0) ...
extractEqualsExpression(locId, ifId, 0, id, ifId).also { eqId ->
extractAndbitExpression(intType, locId, eqId, 0, id, ifId)
.also { opId ->
extractConstantInteger(
1 shl paramIdx,
locId,
opId,
0,
id,
ifId
)
extractVariableAccess(
tw.getLabelFor<DbParam>(realParamsVarId),
intType,
locId,
opId,
1,
id,
ifId
)
}
extractConstantInteger(0, locId, eqId, 1, id, ifId)
}
// thisParamVar = defaultExpr...
extractExpressionStmt(locId, ifId, 1, id).also { exprStmtId ->
extractAssignExpr(
param.type,
locId,
exprStmtId,
0,
id,
exprStmtId
)
.also { assignId ->
extractVariableAccess(
tw.getLabelFor<DbParam>(
getValueParameterLabel(
id,
paramIdx + paramIdxOffset
)
),
param.type,
locId,
assignId,
0,
id,
exprStmtId
)
extractExpressionExpr(
defaultVal.expression,
id,
assignId,
1,
exprStmtId
)
}
}
}
}
}
// Now call the real function:
if (f is IrConstructor) {
tw.getFreshIdLabel<DbConstructorinvocationstmt>().also { thisCallId ->
tw.writeStmts_constructorinvocationstmt(
thisCallId,
blockId,
nextStmt++,
id
)
tw.writeHasLocation(thisCallId, locId)
f.valueParameters.forEachIndexed { idx, param ->
extractVariableAccess(
tw.getLabelFor<DbParam>(getValueParameterLabel(id, idx)),
param.type,
locId,
thisCallId,
idx,
id,
thisCallId
)
}
tw.writeCallableBinding(thisCallId, realFunctionId)
}
} else {
tw.getFreshIdLabel<DbReturnstmt>().also { returnId ->
tw.writeStmts_returnstmt(returnId, blockId, nextStmt++, id)
tw.writeHasLocation(returnId, locId)
extractMethodAccessWithoutArgs(
f.returnType,
locId,
id,
returnId,
0,
returnId,
realFunctionId
)
.also { thisCallId ->
val realFnIdxOffset =
if (f.extensionReceiverParameter != null) 1 else 0
val paramMappings =
f.valueParameters.mapIndexed { idx, param ->
Triple(
param.type,
idx + paramIdxOffset,
idx + realFnIdxOffset
)
} +
listOfNotNull(
dispatchReceiver?.let {
Triple(it.type, 0, -1)
},
extReceiver?.let {
Triple(
it.type,
if (dispatchReceiver != null) 1 else 0,
0
)
}
)
paramMappings.forEach { (type, fromIdx, toIdx) ->
extractVariableAccess(
tw.getLabelFor<DbParam>(
getValueParameterLabel(id, fromIdx)
),
type,
locId,
thisCallId,
toIdx,
id,
returnId
)
}
if (f.shouldExtractAsStatic)
extractStaticTypeAccessQualifier(
f,
thisCallId,
locId,
id,
returnId
)
else if (f.isLocalFunction()) {
extractNewExprForLocalFunction(
getLocallyVisibleFunctionLabels(f),
thisCallId,
locId,
id,
returnId
)
}
}
}
}
}
}
}
}
private val jvmOverloadsFqName = FqName("kotlin.jvm.JvmOverloads")
private fun extractGeneratedOverloads(
f: IrFunction,
parentId: Label<out DbReftype>,
maybeSourceParentId: Label<out DbReftype>?,
extractBody: Boolean,
extractMethodAndParameterTypeAccesses: Boolean,
typeSubstitution: TypeSubstitution?,
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
) {
fun extractGeneratedOverload(paramList: List<IrValueParameter?>) {
val overloadParameters = paramList.filterNotNull()
// Note `overloadParameters` have incorrect parents and indices, since there is no
// actual IrFunction describing the required synthetic overload.
// We have to use the `overriddenAttributes` element of `DeclarationStackAdjuster` to
// fix up references to these parameters while we're extracting
// these synthetic overloads.
val overloadId =
tw.getLabelFor<DbCallable>(
getFunctionLabel(
f,
parentId,
classTypeArgsIncludingOuterClasses,
overloadParameters
)
)
val sourceParentId =
maybeSourceParentId
?: if (typeSubstitution != null) useDeclarationParentOf(f, false) else parentId
if (sourceParentId == null) {
logger.errorElement("Cannot get source parent ID for function", f)
return
}
val sourceDeclId =
tw.getLabelFor<DbCallable>(
getFunctionLabel(f, sourceParentId, listOf(), overloadParameters)
)
val overriddenAttributes =
OverriddenFunctionAttributes(
id = overloadId,
sourceDeclarationId = sourceDeclId,
valueParameters = overloadParameters
)
forceExtractFunction(
f,
parentId,
extractBody = false,
extractMethodAndParameterTypeAccesses,
extractAnnotations = false,
typeSubstitution,
classTypeArgsIncludingOuterClasses,
overriddenAttributes = overriddenAttributes
)
tw.writeCompiler_generated(overloadId, CompilerGeneratedKinds.JVMOVERLOADS_METHOD.kind)
val realFunctionLocId = tw.getLocation(f)
if (extractBody) {
DeclarationStackAdjuster(f, overriddenAttributes).use {
// Create a synthetic function body that calls the corresponding $default
// function:
val regularArgs =
paramList.map { it?.let { p -> IrGetValueImpl(-1, -1, p.symbol) } }
if (f is IrConstructor) {
val blockId = extractBlockBody(overloadId, realFunctionLocId)
val constructorCallId = tw.getFreshIdLabel<DbConstructorinvocationstmt>()
tw.writeStmts_constructorinvocationstmt(
constructorCallId,
blockId,
0,
overloadId
)
tw.writeHasLocation(constructorCallId, realFunctionLocId)
tw.writeCallableBinding(
constructorCallId,
getDefaultsMethodLabel(f, parentId)
)
extractDefaultsCallArguments(
constructorCallId,
f,
overloadId,
constructorCallId,
regularArgs,
null,
null
)
} else {
val dispatchReceiver =
f.dispatchReceiverParameter?.let { IrGetValueImpl(-1, -1, it.symbol) }
val extensionReceiver =
f.extensionReceiverParameter?.let { IrGetValueImpl(-1, -1, it.symbol) }
extractExpressionBody(overloadId, realFunctionLocId).also { returnId ->
extractsDefaultsCall(
f,
realFunctionLocId,
f.returnType,
overloadId,
returnId,
0,
returnId,
regularArgs,
dispatchReceiver,
extensionReceiver
)
}
}
}
}
}
if (!f.hasAnnotation(jvmOverloadsFqName)) {
if (
f is IrConstructor &&
f.valueParameters.isNotEmpty() &&
f.valueParameters.all { it.defaultValue != null } &&
f.parentClassOrNull?.let {
// Don't create a default constructor for an annotation class, or a class
// that explicitly declares a no-arg constructor.
!it.isAnnotationClass &&
it.declarations.none { d ->
d is IrConstructor && d.valueParameters.isEmpty()
}
} == true
) {
// Per https://kotlinlang.org/docs/classes.html#creating-instances-of-classes, a
// single default overload gets created specifically
// when we have all default parameters, regardless of `@JvmOverloads`.
extractGeneratedOverload(f.valueParameters.map { _ -> null })
}
return
}
val paramList: MutableList<IrValueParameter?> = f.valueParameters.toMutableList()
for (n in (f.valueParameters.size - 1) downTo 0) {
if (f.valueParameters[n].defaultValue != null) {
paramList[n] = null // Remove this parameter, to be replaced by a default value
extractGeneratedOverload(paramList)
}
}
}
private fun extractConstructor(
id: Label<out DbConstructor>,
shortName: String,
paramsSignature: String,
parentId: Label<out DbReftype>,
sourceDeclaration: Label<out DbConstructor>
) {
val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
tw.writeConstrs(
id,
shortName,
"$shortName$paramsSignature",
unitType.javaResult.id,
parentId,
sourceDeclaration
)
tw.writeConstrsKotlinType(id, unitType.kotlinResult.id)
}
private fun extractMethod(
id: Label<out DbMethod>,
locId: Label<out DbLocation>,
shortName: String,
returnType: IrType,
paramsSignature: String,
parentId: Label<out DbReftype>,
sourceDeclaration: Label<out DbMethod>,
origin: IrDeclarationOrigin?,
extractTypeAccess: Boolean
) {
val returnTypeResults = useType(returnType, TypeContext.RETURN)
tw.writeMethods(
id,
shortName,
"$shortName$paramsSignature",
returnTypeResults.javaResult.id,
parentId,
sourceDeclaration
)
tw.writeMethodsKotlinType(id, returnTypeResults.kotlinResult.id)
when (origin) {
IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER ->
tw.writeCompiler_generated(
id,
CompilerGeneratedKinds.GENERATED_DATA_CLASS_MEMBER.kind
)
IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR ->
tw.writeCompiler_generated(
id,
CompilerGeneratedKinds.DEFAULT_PROPERTY_ACCESSOR.kind
)
IrDeclarationOrigin.ENUM_CLASS_SPECIAL_MEMBER ->
tw.writeCompiler_generated(
id,
CompilerGeneratedKinds.ENUM_CLASS_SPECIAL_MEMBER.kind
)
}
if (extractTypeAccess) {
extractTypeAccessRecursive(returnType, locId, id, -1)
}
}
private fun signatureOrWarn(t: TypeResult<*>, associatedElement: IrElement?) =
t.signature
?: "<signature unavailable>"
.also {
if (associatedElement != null)
logger.warnElement(
"Needed a signature for a type that doesn't have one",
associatedElement
)
else logger.warn("Needed a signature for a type that doesn't have one")
}
private fun getNullabilityAnnotationName(
t: IrType,
declOrigin: IrDeclarationOrigin,
existingAnnotations: List<IrConstructorCall>,
javaAnnotations: Collection<JavaAnnotation>?
): FqName? {
if (t !is IrSimpleType) return null
fun hasExistingAnnotation(name: FqName) =
existingAnnotations.any { existing -> existing.type.classFqName == name }
return if (declOrigin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) {
// Java declaration: restore a NotNull or Nullable annotation if the original Java
// member had one but the Kotlin compiler removed it.
javaAnnotations
?.mapNotNull { it.classId?.asSingleFqName() }
?.singleOrNull {
NOT_NULL_ANNOTATIONS.contains(it) || NULLABLE_ANNOTATIONS.contains(it)
}
?.takeUnless { hasExistingAnnotation(it) }
} else {
// Kotlin declaration: add a NotNull annotation to a non-nullable non-primitive type,
// unless one is already present.
// Usually Kotlin declarations can't have a manual `@NotNull`, but this happens at least
// when delegating members are
// synthesised and inherit the annotation from the delegate (which given it has
// @NotNull, is likely written in Java)
JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION.takeUnless {
t.isNullable() ||
primitiveTypeMapping.getPrimitiveInfo(t) != null ||
hasExistingAnnotation(it)
}
}
}
private fun getNullabilityAnnotation(
t: IrType,
declOrigin: IrDeclarationOrigin,
existingAnnotations: List<IrConstructorCall>,
javaAnnotations: Collection<JavaAnnotation>?
) =
getNullabilityAnnotationName(t, declOrigin, existingAnnotations, javaAnnotations)?.let {
getClassByFqName(pluginContext, it)?.let { annotationClass ->
annotationClass.owner.declarations.firstIsInstanceOrNull<IrConstructor>()?.let {
annotationConstructor ->
IrConstructorCallImpl.fromSymbolOwner(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
annotationConstructor.returnType,
annotationConstructor.symbol,
0
)
}
}
}
private fun forceExtractFunction(
f: IrFunction,
parentId: Label<out DbReftype>,
extractBody: Boolean,
extractMethodAndParameterTypeAccesses: Boolean,
extractAnnotations: Boolean,
typeSubstitution: TypeSubstitution?,
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?,
extractOrigin: Boolean = true,
overriddenAttributes: OverriddenFunctionAttributes? = null
): Label<out DbCallable> {
with("function", f) {
DeclarationStackAdjuster(f, overriddenAttributes).use {
val javaCallable = getJavaCallable(f)
getFunctionTypeParameters(f).mapIndexed { idx, tp ->
extractTypeParameter(
tp,
idx,
(javaCallable as? JavaTypeParameterListOwner)
?.typeParameters
?.getOrNull(idx)
)
}
val id =
overriddenAttributes?.id
?: // If this is a class that would ordinarily be replaced by a Java
// equivalent (e.g. kotlin.Map -> java.util.Map),
// don't replace here, really extract the Kotlin version:
useFunction<DbCallable>(
f,
parentId,
classTypeArgsIncludingOuterClasses,
noReplace = true
)
val sourceDeclaration =
overriddenAttributes?.sourceDeclarationId
?: if (typeSubstitution != null && overriddenAttributes?.id == null) {
val sourceFunId = useFunction<DbCallable>(f)
if (sourceFunId == null) {
logger.errorElement("Cannot get source ID for function", f)
id // TODO: This is wrong; we ought to just fail in this case
} else {
sourceFunId
}
} else {
id
}
val extReceiver = f.extensionReceiverParameter
// The following parameter order is correct, because member $default methods (where
// the order would be [dispatchParam], [extensionParam], normalParams) are not
// extracted here
val fParameters =
listOfNotNull(extReceiver) +
(overriddenAttributes?.valueParameters ?: f.valueParameters)
val paramTypes =
fParameters.mapIndexed { i, vp ->
extractValueParameter(
vp,
id,
i,
typeSubstitution,
sourceDeclaration,
classTypeArgsIncludingOuterClasses,
extractTypeAccess = extractMethodAndParameterTypeAccesses,
overriddenAttributes?.sourceLoc
)
}
if (extReceiver != null) {
val extendedType = paramTypes[0]
tw.writeKtExtensionFunctions(
id.cast<DbMethod>(),
extendedType.javaResult.id,
extendedType.kotlinResult.id
)
}
val paramsSignature =
paramTypes.joinToString(separator = ",", prefix = "(", postfix = ")") {
signatureOrWarn(it.javaResult, f)
}
val adjustedReturnType =
addJavaLoweringWildcards(
getAdjustedReturnType(f),
false,
(javaCallable as? JavaMethod)?.returnType
)
val substReturnType =
typeSubstitution?.let {
it(adjustedReturnType, TypeContext.RETURN, pluginContext)
} ?: adjustedReturnType
val locId =
overriddenAttributes?.sourceLoc
?: getLocation(f, classTypeArgsIncludingOuterClasses)
if (f.symbol is IrConstructorSymbol) {
val shortName =
when {
adjustedReturnType.isAnonymous -> ""
typeSubstitution != null ->
useType(substReturnType).javaResult.shortName
else ->
adjustedReturnType.classFqName?.shortName()?.asString()
?: f.name.asString()
}
extractConstructor(
id.cast(),
shortName,
paramsSignature,
parentId,
sourceDeclaration.cast()
)
} else {
val shortNames = getFunctionShortName(f)
val methodId = id.cast<DbMethod>()
extractMethod(
methodId,
locId,
shortNames.nameInDB,
substReturnType,
paramsSignature,
parentId,
sourceDeclaration.cast(),
if (extractOrigin) f.origin else null,
extractMethodAndParameterTypeAccesses
)
if (shortNames.nameInDB != shortNames.kotlinName) {
tw.writeKtFunctionOriginalNames(methodId, shortNames.kotlinName)
}
if (f.hasInterfaceParent() && f.body != null) {
addModifiers(
methodId,
"default"
) // The actual output class file may or may not have this modifier,
// depending on the -Xjvm-default setting.
}
}
tw.writeHasLocation(id, locId)
val body = f.body
if (body != null && extractBody) {
if (typeSubstitution != null)
logger.errorElement(
"Type substitution should only be used to extract a function prototype, not the body",
f
)
extractBody(body, id)
}
extractVisibility(f, id, overriddenAttributes?.visibility ?: f.visibility)
if (f.isInline) {
addModifiers(id, "inline")
}
if (f.shouldExtractAsStatic) {
addModifiers(id, "static")
}
if (f is IrSimpleFunction && f.overriddenSymbols.isNotEmpty()) {
addModifiers(id, "override")
}
if (f.isSuspend) {
addModifiers(id, "suspend")
}
if (f.symbol !is IrConstructorSymbol) {
when (overriddenAttributes?.modality ?: (f as? IrSimpleFunction)?.modality) {
Modality.ABSTRACT -> addModifiers(id, "abstract")
Modality.FINAL -> addModifiers(id, "final")
else -> Unit
}
}
linesOfCode?.linesOfCodeInDeclaration(f, id)
if (extractAnnotations) {
val extraAnnotations =
if (f.symbol is IrConstructorSymbol) listOf()
else
listOfNotNull(
getNullabilityAnnotation(
f.returnType,
f.origin,
f.annotations,
getJavaCallable(f)?.annotations
)
)
extractAnnotations(
f,
f.annotations + extraAnnotations,
id,
extractMethodAndParameterTypeAccesses
)
}
return id
}
}
}
private fun isStaticFunction(f: IrFunction): Boolean {
return f.dispatchReceiverParameter == null // Has no dispatch receiver,
&&
!f
.isLocalFunction() // not a local function. Local functions are extracted as
// instance methods with the local class instantiation as the
// qualifier
&&
f.symbol !is IrConstructorSymbol // not a constructor
}
private fun extractField(
f: IrField,
parentId: Label<out DbReftype>,
extractAnnotationEnumTypeAccesses: Boolean
): Label<out DbField> {
with("field", f) {
DeclarationStackAdjuster(f).use {
val fNameSuffix =
getExtensionReceiverType(f)?.let {
it.classFqName?.asString()?.replace(".", "$$")
} ?: ""
val extractType =
if (isAnnotationClassField(f)) kClassToJavaClass(f.type) else f.type
val id = useField(f)
extractAnnotations(f, id, extractAnnotationEnumTypeAccesses)
return extractField(
id,
"${f.name.asString()}$fNameSuffix",
extractType,
parentId,
tw.getLocation(f),
f.visibility,
f,
isExternalDeclaration(f),
f.isFinal,
isDirectlyExposedCompanionObjectField(f)
)
}
}
}
private fun extractField(
id: Label<out DbField>,
name: String,
type: IrType,
parentId: Label<out DbReftype>,
locId: Label<DbLocation>,
visibility: DescriptorVisibility,
errorElement: IrElement,
isExternalDeclaration: Boolean,
isFinal: Boolean,
isStatic: Boolean
): Label<out DbField> {
val t = useType(type)
tw.writeFields(id, name, t.javaResult.id, parentId)
tw.writeFieldsKotlinType(id, t.kotlinResult.id)
tw.writeHasLocation(id, locId)
extractVisibility(errorElement, id, visibility)
if (isFinal) {
addModifiers(id, "final")
}
if (isStatic) {
addModifiers(id, "static")
}
if (!isExternalDeclaration) {
val fieldDeclarationId = tw.getFreshIdLabel<DbFielddecl>()
tw.writeFielddecls(fieldDeclarationId, parentId)
tw.writeFieldDeclaredIn(id, fieldDeclarationId, 0)
tw.writeHasLocation(fieldDeclarationId, locId)
extractTypeAccessRecursive(type, locId, fieldDeclarationId, 0)
}
return id
}
private fun extractProperty(
p: IrProperty,
parentId: Label<out DbReftype>,
extractBackingField: Boolean,
extractFunctionBodies: Boolean,
extractPrivateMembers: Boolean,
extractAnnotations: Boolean,
typeSubstitution: TypeSubstitution?,
classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
) {
with("property", p) {
fun needsInterfaceForwarderQ(f: IrFunction?) =
f?.let { needsInterfaceForwarder(f) } ?: false
if (
isFake(p) &&
!needsInterfaceForwarderQ(p.getter) &&
!needsInterfaceForwarderQ(p.setter)
)
return
DeclarationStackAdjuster(p).use {
val id = useProperty(p, parentId, classTypeArgsIncludingOuterClasses)
val locId = getLocation(p, classTypeArgsIncludingOuterClasses)
tw.writeKtProperties(id, p.name.asString())
tw.writeHasLocation(id, locId)
val bf = p.backingField
val getter = p.getter
val setter = p.setter
if (getter == null) {
if (!isExternalDeclaration(p)) {
logger.warnElement("IrProperty without a getter", p)
}
} else if (shouldExtractDecl(getter, extractPrivateMembers)) {
val getterId =
extractFunction(
getter,
parentId,
extractBody = extractFunctionBodies,
extractMethodAndParameterTypeAccesses = extractFunctionBodies,
extractAnnotations = extractAnnotations,
typeSubstitution,
classTypeArgsIncludingOuterClasses
)
?.cast<DbMethod>()
if (getterId != null) {
tw.writeKtPropertyGetters(id, getterId)
if (getter.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) {
tw.writeCompiler_generated(
getterId,
CompilerGeneratedKinds.DELEGATED_PROPERTY_GETTER.kind
)
}
}
}
if (setter == null) {
if (p.isVar && !isExternalDeclaration(p)) {
logger.warnElement("isVar property without a setter", p)
}
} else if (shouldExtractDecl(setter, extractPrivateMembers)) {
if (!p.isVar) {
logger.warnElement("!isVar property with a setter", p)
}
val setterId =
extractFunction(
setter,
parentId,
extractBody = extractFunctionBodies,
extractMethodAndParameterTypeAccesses = extractFunctionBodies,
extractAnnotations = extractAnnotations,
typeSubstitution,
classTypeArgsIncludingOuterClasses
)
?.cast<DbMethod>()
if (setterId != null) {
tw.writeKtPropertySetters(id, setterId)
if (setter.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) {
tw.writeCompiler_generated(
setterId,
CompilerGeneratedKinds.DELEGATED_PROPERTY_SETTER.kind
)
}
}
}
if (bf != null && extractBackingField) {
val fieldParentId = useDeclarationParentOf(bf, false)
if (fieldParentId != null) {
val fieldId = extractField(bf, fieldParentId.cast(), extractFunctionBodies)
tw.writeKtPropertyBackingFields(id, fieldId)
if (p.isDelegated) {
tw.writeKtPropertyDelegates(id, fieldId)
}
}
}
extractVisibility(p, id, p.visibility)
// TODO: extract annotations
if (p.isLateinit) {
addModifiers(id, "lateinit")
}
}
}
}
private fun getEnumEntryType(ee: IrEnumEntry): TypeResults? {
val parent = ee.parent
if (parent !is IrClass) {
logger.errorElement("Enum entry with unexpected parent: " + parent.javaClass, ee)
return null
} else if (parent.typeParameters.isNotEmpty()) {
logger.errorElement("Enum entry parent class has type parameters: " + parent.name, ee)
return null
} else {
return useSimpleTypeClass(parent, emptyList(), false)
}
}
private fun extractEnumEntry(
ee: IrEnumEntry,
parentId: Label<out DbReftype>,
extractPrivateMembers: Boolean,
extractFunctionBodies: Boolean
) {
with("enum entry", ee) {
DeclarationStackAdjuster(ee).use {
val id = useEnumEntry(ee)
val type = getEnumEntryType(ee) ?: return
tw.writeFields(id, ee.name.asString(), type.javaResult.id, parentId)
tw.writeFieldsKotlinType(id, type.kotlinResult.id)
val locId = tw.getLocation(ee)
tw.writeHasLocation(id, locId)
tw.writeIsEnumConst(id)
if (extractFunctionBodies) {
val fieldDeclarationId = tw.getFreshIdLabel<DbFielddecl>()
tw.writeFielddecls(fieldDeclarationId, parentId)
tw.writeFieldDeclaredIn(id, fieldDeclarationId, 0)
tw.writeHasLocation(fieldDeclarationId, locId)
extractTypeAccess(type, locId, fieldDeclarationId, 0)
}
ee.correspondingClass?.let {
extractDeclaration(
it,
extractPrivateMembers,
extractFunctionBodies,
extractAnnotations = true
)
}
extractAnnotations(ee, id, extractFunctionBodies)
}
}
}
private fun extractTypeAlias(ta: IrTypeAlias) {
with("type alias", ta) {
if (ta.typeParameters.isNotEmpty()) {
// TODO: Extract this information
return
}
val id = useTypeAlias(ta)
val locId = tw.getLocation(ta)
// TODO: We don't really want to generate any Java types here; we only want the KT type:
val type = useType(ta.expandedType)
tw.writeKt_type_alias(id, ta.name.asString(), type.kotlinResult.id)
tw.writeHasLocation(id, locId)
// TODO: extract annotations
}
}
private fun extractBody(b: IrBody, callable: Label<out DbCallable>) {
with("body", b) {
when (b) {
is IrBlockBody -> extractBlockBody(b, callable)
is IrSyntheticBody -> extractSyntheticBody(b, callable)
is IrExpressionBody -> extractExpressionBody(b, callable)
else -> {
logger.errorElement("Unrecognised IrBody: " + b.javaClass, b)
}
}
}
}
private fun extractBlockBody(callable: Label<out DbCallable>, locId: Label<DbLocation>) =
tw.getFreshIdLabel<DbBlock>().also {
tw.writeStmts_block(it, callable, 0, callable)
tw.writeHasLocation(it, locId)
}
private fun extractBlockBody(b: IrBlockBody, callable: Label<out DbCallable>) {
with("block body", b) {
extractBlockBody(callable, tw.getLocation(b)).also {
for ((sIdx, stmt) in b.statements.withIndex()) {
extractStatement(stmt, callable, it, sIdx)
}
}
}
}
private fun extractSyntheticBody(b: IrSyntheticBody, callable: Label<out DbCallable>) {
with("synthetic body", b) {
val kind = b.kind
when {
kind == IrSyntheticBodyKind.ENUM_VALUES -> tw.writeKtSyntheticBody(callable, 1)
kind == IrSyntheticBodyKind.ENUM_VALUEOF -> tw.writeKtSyntheticBody(callable, 2)
kind == kind_ENUM_ENTRIES -> tw.writeKtSyntheticBody(callable, 3)
else -> {
logger.errorElement("Unhandled synthetic body kind " + kind, b)
}
}
}
}
private fun extractExpressionBody(b: IrExpressionBody, callable: Label<out DbCallable>) {
with("expression body", b) {
val locId = tw.getLocation(b)
extractExpressionBody(callable, locId).also { returnId ->
extractExpressionExpr(b.expression, callable, returnId, 0, returnId)
}
}
}
fun extractExpressionBody(
callable: Label<out DbCallable>,
locId: Label<DbLocation>
): Label<out DbStmt> {
val blockId = extractBlockBody(callable, locId)
return tw.getFreshIdLabel<DbReturnstmt>().also { returnId ->
tw.writeStmts_returnstmt(returnId, blockId, 0, callable)
tw.writeHasLocation(returnId, locId)
}
}
private fun getVariableLocationProvider(v: IrVariable): IrElement {
val init = v.initializer
if (v.startOffset < 0 && init != null) {
// IR_TEMPORARY_VARIABLEs have no proper location
return init
}
return v
}
private fun extractVariable(
v: IrVariable,
callable: Label<out DbCallable>,
parent: Label<out DbStmtparent>,
idx: Int
) {
with("variable", v) {
val stmtId = tw.getFreshIdLabel<DbLocalvariabledeclstmt>()
val locId = tw.getLocation(getVariableLocationProvider(v))
tw.writeStmts_localvariabledeclstmt(stmtId, parent, idx, callable)
tw.writeHasLocation(stmtId, locId)
extractVariableExpr(v, callable, stmtId, 1, stmtId)
}
}
private fun extractVariableExpr(
v: IrVariable,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>,
extractInitializer: Boolean = true
) {
with("variable expr", v) {
val varId = useVariable(v)
val exprId = tw.getFreshIdLabel<DbLocalvariabledeclexpr>()
val locId = tw.getLocation(getVariableLocationProvider(v))
val type = useType(v.type)
tw.writeLocalvars(varId, v.name.asString(), type.javaResult.id, exprId)
tw.writeLocalvarsKotlinType(varId, type.kotlinResult.id)
tw.writeHasLocation(varId, locId)
tw.writeExprs_localvariabledeclexpr(exprId, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(exprId, type.kotlinResult.id)
extractExprContext(exprId, locId, callable, enclosingStmt)
val i = v.initializer
if (i != null && extractInitializer) {
extractExpressionExpr(i, callable, exprId, 0, enclosingStmt)
}
if (!v.isVar) {
addModifiers(varId, "final")
}
if (v.isLateinit) {
addModifiers(varId, "lateinit")
}
}
}
private fun extractIfStmt(
locId: Label<DbLocation>,
parent: Label<out DbStmtparent>,
idx: Int,
callable: Label<out DbCallable>
) =
tw.getFreshIdLabel<DbIfstmt>().also {
tw.writeStmts_ifstmt(it, parent, idx, callable)
tw.writeHasLocation(it, locId)
}
private fun extractStatement(
s: IrStatement,
callable: Label<out DbCallable>,
parent: Label<out DbStmtparent>,
idx: Int
) {
with("statement", s) {
when (s) {
is IrExpression -> {
extractExpressionStmt(s, callable, parent, idx)
}
is IrVariable -> {
extractVariable(s, callable, parent, idx)
}
is IrClass -> {
extractLocalTypeDeclStmt(s, callable, parent, idx)
}
is IrFunction -> {
if (s.isLocalFunction()) {
val compilerGeneratedKindOverride =
if (s.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) {
CompilerGeneratedKinds.DECLARING_CLASSES_OF_ADAPTER_FUNCTIONS
} else {
null
}
val classId =
extractGeneratedClass(
s,
listOf(pluginContext.irBuiltIns.anyType),
compilerGeneratedKindOverride = compilerGeneratedKindOverride
)
extractLocalTypeDeclStmt(classId, s, callable, parent, idx)
val ids = getLocallyVisibleFunctionLabels(s)
tw.writeKtLocalFunction(ids.function)
} else {
logger.errorElement("Expected to find local function", s)
}
}
is IrLocalDelegatedProperty -> {
val blockId = tw.getFreshIdLabel<DbBlock>()
val locId = tw.getLocation(s)
tw.writeStmts_block(blockId, parent, idx, callable)
tw.writeHasLocation(blockId, locId)
extractVariable(s.delegate, callable, blockId, 0)
val propId = tw.getFreshIdLabel<DbKt_property>()
tw.writeKtProperties(propId, s.name.asString())
tw.writeHasLocation(propId, locId)
tw.writeKtPropertyDelegates(propId, useVariable(s.delegate))
// Getter:
extractStatement(s.getter, callable, blockId, 1)
val getterLabel = getLocallyVisibleFunctionLabels(s.getter).function
tw.writeKtPropertyGetters(propId, getterLabel)
val setter = s.setter
if (setter != null) {
extractStatement(setter, callable, blockId, 2)
val setterLabel = getLocallyVisibleFunctionLabels(setter).function
tw.writeKtPropertySetters(propId, setterLabel)
}
}
else -> {
logger.errorElement("Unrecognised IrStatement: " + s.javaClass, s)
}
}
}
}
/**
* Returns true iff `c` is a call to the function `fName` in the `kotlin.internal.ir` package.
* This is used to find calls to builtin functions, which need to be handled specially as they
* do not have corresponding source definitions.
*/
private fun isBuiltinCallInternal(c: IrCall, fName: String) =
isBuiltinCall(c, fName, "kotlin.internal.ir")
/**
* Returns true iff `c` is a call to the function `fName` in the `kotlin` package. This is used
* to find calls to builtin functions, which need to be handled specially as they do not have
* corresponding source definitions.
*/
private fun isBuiltinCallKotlin(c: IrCall, fName: String) = isBuiltinCall(c, fName, "kotlin")
/**
* Returns true iff `c` is a call to the function `fName` in package `pName`. This is used to
* find calls to builtin functions, which need to be handled specially as they do not have
* corresponding source definitions.
*/
private fun isBuiltinCall(c: IrCall, fName: String, pName: String): Boolean {
val verbose = false
fun verboseln(s: String) {
if (verbose) println(s)
}
verboseln("Attempting builtin match for $fName")
val target = c.symbol.owner
if (target.name.asString() != fName) {
verboseln("No match as function name is ${target.name.asString()} not $fName")
return false
}
val targetPkg = target.parent
if (targetPkg !is IrPackageFragment) {
verboseln("No match as didn't find target package")
return false
}
val targetName = targetPkg.packageFqName.asString()
if (targetName != pName) {
verboseln("No match as package name is $targetName")
return false
}
verboseln("Match")
return true
}
private fun unaryOp(
id: Label<out DbExpr>,
c: IrCall,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) {
val locId = tw.getLocation(c)
extractExprContext(id, locId, callable, enclosingStmt)
val dr = c.dispatchReceiver
if (dr != null) {
logger.errorElement("Unexpected dispatch receiver found", c)
}
if (c.valueArgumentsCount < 1) {
logger.errorElement("No arguments found", c)
return
}
extractArgument(id, c, callable, enclosingStmt, 0, "Operand null")
if (c.valueArgumentsCount > 1) {
logger.errorElement("Extra arguments found", c)
}
}
private fun binOp(
id: Label<out DbExpr>,
c: IrCall,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) {
val locId = tw.getLocation(c)
extractExprContext(id, locId, callable, enclosingStmt)
val dr = c.dispatchReceiver
if (dr != null) {
logger.errorElement("Unexpected dispatch receiver found", c)
}
if (c.valueArgumentsCount < 1) {
logger.errorElement("No arguments found", c)
return
}
extractArgument(id, c, callable, enclosingStmt, 0, "LHS null")
if (c.valueArgumentsCount < 2) {
logger.errorElement("No RHS found", c)
return
}
extractArgument(id, c, callable, enclosingStmt, 1, "RHS null")
if (c.valueArgumentsCount > 2) {
logger.errorElement("Extra arguments found", c)
}
}
private fun extractArgument(
id: Label<out DbExpr>,
c: IrCall,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>,
idx: Int,
msg: String
) {
val op = c.getValueArgument(idx)
if (op == null) {
logger.errorElement(msg, c)
} else {
extractExpressionExpr(op, callable, id, idx, enclosingStmt)
}
}
private fun getDeclaringTypeArguments(
callTarget: IrFunction,
receiverType: IrSimpleType
): List<IrTypeArgument> {
val declaringType = callTarget.parentAsClass
val receiverClass = receiverType.classifier.owner as? IrClass ?: return listOf()
val ancestorTypes = ArrayList<IrSimpleType>()
// KFunctionX doesn't implement FunctionX on versions before 1.7.0:
if (
(callTarget.name.asString() == "invoke") &&
(receiverClass.fqNameWhenAvailable
?.asString()
?.startsWith("kotlin.reflect.KFunction") == true) &&
(callTarget.parentClassOrNull
?.fqNameWhenAvailable
?.asString()
?.startsWith("kotlin.Function") == true)
) {
return receiverType.arguments
}
// Populate ancestorTypes with the path from receiverType's class to its ancestor,
// callTarget's declaring type.
fun walkFrom(c: IrClass): Boolean {
if (declaringType == c) return true
else {
c.superTypes.forEach {
val ancestorClass =
(it as? IrSimpleType)?.classifier?.owner as? IrClass ?: return false
ancestorTypes.add(it)
if (walkFrom(ancestorClass)) return true else ancestorTypes.pop()
}
return false
}
}
// If a path was found, repeatedly substitute types to get the corresponding specialisation
// of that ancestor.
if (!walkFrom(receiverClass)) {
logger.errorElement(
"Failed to find a class declaring ${callTarget.name} starting at ${receiverClass.name}",
callTarget
)
return listOf()
} else {
var subbedType: IrSimpleType = receiverType
ancestorTypes.forEach {
val thisClass = subbedType.classifier.owner
if (thisClass !is IrClass) {
logger.errorElement(
"Found ancestor with unexpected type ${thisClass.javaClass}",
callTarget
)
return listOf()
}
val itSubbed =
it.substituteTypeArguments(thisClass.typeParameters, subbedType.arguments)
if (itSubbed !is IrSimpleType) {
logger.errorElement(
"Substituted type has unexpected type ${itSubbed.javaClass}",
callTarget
)
return listOf()
}
subbedType = itSubbed
}
return subbedType.arguments
}
}
private fun extractNewExprForLocalFunction(
ids: LocallyVisibleFunctionLabels,
parent: Label<out DbExprparent>,
locId: Label<DbLocation>,
enclosingCallable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) {
val idNewexpr =
extractNewExpr(
ids.constructor,
ids.type,
locId,
parent,
-1,
enclosingCallable,
enclosingStmt
)
extractTypeAccessRecursive(
pluginContext.irBuiltIns.anyType,
locId,
idNewexpr,
-3,
enclosingCallable,
enclosingStmt
)
}
private fun extractMethodAccessWithoutArgs(
returnType: IrType,
locId: Label<DbLocation>,
enclosingCallable: Label<out DbCallable>,
callsiteParent: Label<out DbExprparent>,
childIdx: Int,
enclosingStmt: Label<out DbStmt>,
methodLabel: Label<out DbCallable>?
) =
tw.getFreshIdLabel<DbMethodaccess>().also { id ->
val type = useType(returnType)
tw.writeExprs_methodaccess(id, type.javaResult.id, callsiteParent, childIdx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
// The caller should have warned about this before, so we don't repeat the warning here.
if (methodLabel != null) tw.writeCallableBinding(id, methodLabel)
}
private val defaultConstructorMarkerClass by lazy {
referenceExternalClass("kotlin.jvm.internal.DefaultConstructorMarker")
}
private val defaultConstructorMarkerType by lazy { defaultConstructorMarkerClass?.typeWith() }
private fun getDefaultsMethodLastArgType(f: IrFunction) =
(if (f is IrConstructor) defaultConstructorMarkerType else null)
?: pluginContext.irBuiltIns.anyType
private fun getDefaultsMethodArgTypes(f: IrFunction) =
// The $default method has type ([dispatchReceiver], [extensionReceiver], paramTypes...,
// int, Object)
// All parameter types are erased. The trailing int is a mask indicating which parameter
// values are real
// and which should be replaced by defaults. The final Object parameter is apparently always
// null.
(listOfNotNull(if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter?.type) +
listOfNotNull(f.extensionReceiverParameter?.type) +
f.valueParameters.map { it.type } +
listOf(pluginContext.irBuiltIns.intType, getDefaultsMethodLastArgType(f)))
.map { erase(it) }
private fun getDefaultsMethodName(f: IrFunction) =
if (f is IrConstructor) {
f.returnType.let {
when {
it.isAnonymous -> ""
else -> it.classFqName?.shortName()?.asString() ?: f.name.asString()
}
}
} else {
getFunctionShortName(f).nameInDB + "\$default"
}
private fun getDefaultsMethodLabel(f: IrFunction): Label<out DbCallable>? {
val classTypeArgsIncludingOuterClasses = null
val parentId = useDeclarationParentOf(f, false, classTypeArgsIncludingOuterClasses, true)
if (parentId == null) {
logger.errorElement("Couldn't get parent ID for defaults method", f)
return null
}
return getDefaultsMethodLabel(f, parentId)
}
private fun getDefaultsMethodLabel(
f: IrFunction,
parentId: Label<out DbElement>
): Label<out DbCallable> {
val defaultsMethodName = if (f is IrConstructor) "<init>" else getDefaultsMethodName(f)
val argTypes = getDefaultsMethodArgTypes(f)
val defaultMethodLabelStr =
getFunctionLabel(
f.parent,
parentId,
defaultsMethodName,
argTypes,
erase(f.returnType),
extensionParamType = null, // if there's any, that's included already in argTypes
functionTypeParameters = listOf(),
classTypeArgsIncludingOuterClasses = null,
overridesCollectionsMethod = false,
javaSignature = null,
addParameterWildcardsByDefault = false
)
return tw.getLabelFor(defaultMethodLabelStr)
}
private fun extractsDefaultsCall(
syntacticCallTarget: IrFunction,
locId: Label<DbLocation>,
resultType: IrType,
enclosingCallable: Label<out DbCallable>,
callsiteParent: Label<out DbExprparent>,
childIdx: Int,
enclosingStmt: Label<out DbStmt>,
valueArguments: List<IrExpression?>,
dispatchReceiver: IrExpression?,
extensionReceiver: IrExpression?
) {
val callTarget = syntacticCallTarget.target.realOverrideTarget
if (isExternalDeclaration(callTarget)) {
// Ensure the real target gets extracted, as we might not every directly touch it thanks
// to this call being redirected to a $default method.
useFunction<DbCallable>(callTarget)
}
// Default parameter values are inherited by overrides; in this case the call should
// dispatch against the $default method belonging to the class
// that specified the default values, which will in turn dynamically dispatch back to the
// relevant override.
val overriddenCallTarget =
(callTarget as? IrSimpleFunction)?.allOverriddenIncludingSelf()?.firstOrNull {
it.overriddenSymbols.isEmpty() &&
it.valueParameters.any { p -> p.defaultValue != null }
} ?: callTarget
if (isExternalDeclaration(overriddenCallTarget)) {
// Likewise, ensure the overridden target gets extracted.
useFunction<DbCallable>(overriddenCallTarget)
}
val defaultMethodLabel = getDefaultsMethodLabel(overriddenCallTarget)
val id =
extractMethodAccessWithoutArgs(
resultType,
locId,
enclosingCallable,
callsiteParent,
childIdx,
enclosingStmt,
defaultMethodLabel
)
if (overriddenCallTarget.isLocalFunction()) {
extractTypeAccess(
getLocallyVisibleFunctionLabels(overriddenCallTarget).type,
locId,
id,
-1,
enclosingCallable,
enclosingStmt
)
} else {
extractStaticTypeAccessQualifierUnchecked(
overriddenCallTarget,
id,
locId,
enclosingCallable,
enclosingStmt
)
}
extractDefaultsCallArguments(
id,
overriddenCallTarget,
enclosingCallable,
enclosingStmt,
valueArguments,
dispatchReceiver,
extensionReceiver
)
}
private fun extractDefaultsCallArguments(
id: Label<out DbExprparent>,
callTarget: IrFunction,
enclosingCallable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>,
valueArguments: List<IrExpression?>,
dispatchReceiver: IrExpression?,
extensionReceiver: IrExpression?
) {
var nextIdx = 0
if (dispatchReceiver != null && !callTarget.shouldExtractAsStatic) {
extractExpressionExpr(dispatchReceiver, enclosingCallable, id, nextIdx++, enclosingStmt)
}
if (extensionReceiver != null) {
extractExpressionExpr(
extensionReceiver,
enclosingCallable,
id,
nextIdx++,
enclosingStmt
)
}
val valueArgsWithDummies =
valueArguments.zip(callTarget.valueParameters).map { (expr, param) ->
expr ?: IrConstImpl.defaultValueForType(0, 0, param.type)
}
var realParamsMask = 0
valueArguments.forEachIndexed { index, arg ->
if (arg != null) realParamsMask = realParamsMask or (1 shl index)
}
val extraArgs =
listOf(
IrConstImpl.int(0, 0, pluginContext.irBuiltIns.intType, realParamsMask),
IrConstImpl.defaultValueForType(0, 0, getDefaultsMethodLastArgType(callTarget))
)
extractCallValueArguments(
id,
valueArgsWithDummies + extraArgs,
enclosingStmt,
enclosingCallable,
nextIdx,
extractVarargAsArray = true
)
}
private fun getFunctionInvokeMethod(typeArgs: List<IrTypeArgument>): IrFunction? {
// For `kotlin.FunctionX` and `kotlin.reflect.KFunctionX` interfaces, we're making sure that
// we
// extract the call to the `invoke` method that does exist,
// `kotlin.jvm.functions.FunctionX::invoke`.
val functionalInterface = getFunctionalInterfaceTypeWithTypeArgs(typeArgs)
if (functionalInterface == null) {
logger.warn("Cannot find functional interface type for raw method access")
return null
}
val functionalInterfaceClass = functionalInterface.classOrNull
if (functionalInterfaceClass == null) {
logger.warn("Cannot find functional interface class for raw method access")
return null
}
val interfaceType = functionalInterfaceClass.owner
val substituted = getJavaEquivalentClass(interfaceType) ?: interfaceType
val function = findFunction(substituted, OperatorNameConventions.INVOKE.asString())
if (function == null) {
logger.warn("Cannot find invoke function for raw method access")
return null
}
return function
}
private fun isFunctionInvoke(callTarget: IrFunction, drType: IrSimpleType) =
(drType.isFunctionOrKFunction() || drType.isSuspendFunctionOrKFunction()) &&
callTarget.name.asString() == OperatorNameConventions.INVOKE.asString()
private fun getCalleeMethodId(
callTarget: IrFunction,
drType: IrType?,
allowInstantiatedGenericMethod: Boolean
): Label<out DbCallable>? {
if (callTarget.isLocalFunction())
return getLocallyVisibleFunctionLabels(callTarget).function
if (
allowInstantiatedGenericMethod &&
drType is IrSimpleType &&
!isUnspecialised(drType, logger)
) {
val calleeIsInvoke = isFunctionInvoke(callTarget, drType)
val extractionMethod =
if (calleeIsInvoke) getFunctionInvokeMethod(drType.arguments) else callTarget
return extractionMethod?.let {
val typeArgs =
if (calleeIsInvoke && drType.arguments.size > BuiltInFunctionArity.BIG_ARITY) {
// Big arity `invoke` methods have a special implementation on JVM, they are
// transformed to a call to
// `kotlin.jvm.functions.FunctionN<out R>::invoke(vararg args: Any?)`, so we
// only need to pass the type
// argument for the return type. Additionally, the arguments are extracted
// inside an array literal below.
listOf(drType.arguments.last())
} else {
getDeclaringTypeArguments(callTarget, drType)
}
useFunction<DbCallable>(extractionMethod, typeArgs)
}
} else {
return useFunction<DbCallable>(callTarget)
}
}
private fun getCalleeRealOverrideTarget(f: IrFunction): IrFunction {
val target = f.target.realOverrideTarget
return if (overridesCollectionsMethodWithAlteredParameterTypes(f))
// Cope with the case where an inherited callee can be rewritten with substituted parameter
// types
// if the child class uses it to implement a collections interface
// (for example, `class A { boolean contains(Object o) { ... } }; class B<T> extends A
// implements Set<T> { ... }`
// leads to generating a function `A.contains(B::T)`, with `initialSignatureFunction`
// pointing to `A.contains(Object)`.
(target as? IrLazyFunction)?.initialSignatureFunction ?: target
else target
}
private fun callUsesDefaultArguments(
callTarget: IrFunction,
valueArguments: List<IrExpression?>
): Boolean {
val varargParam = callTarget.valueParameters.withIndex().find { it.value.isVararg }
// If the vararg param is the only one not specified, and it has no default value, then we
// don't need to call a $default method,
// as omitting it already implies passing an empty vararg array.
val nullAllowedIdx =
if (varargParam != null && varargParam.value.defaultValue == null) varargParam.index
else -1
return valueArguments.withIndex().any { (index, it) ->
it == null && index != nullAllowedIdx
}
}
fun extractRawMethodAccess(
syntacticCallTarget: IrFunction,
locElement: IrElement,
resultType: IrType,
enclosingCallable: Label<out DbCallable>,
callsiteParent: Label<out DbExprparent>,
childIdx: Int,
enclosingStmt: Label<out DbStmt>,
valueArguments: List<IrExpression?>,
dispatchReceiver: IrExpression?,
extensionReceiver: IrExpression?,
typeArguments: List<IrType> = listOf(),
extractClassTypeArguments: Boolean = false,
superQualifierSymbol: IrClassSymbol? = null
) {
val locId = tw.getLocation(locElement)
if (callUsesDefaultArguments(syntacticCallTarget, valueArguments)) {
extractsDefaultsCall(
syntacticCallTarget,
locId,
resultType,
enclosingCallable,
callsiteParent,
childIdx,
enclosingStmt,
valueArguments,
dispatchReceiver,
extensionReceiver
)
} else {
extractRawMethodAccess(
syntacticCallTarget,
locId,
resultType,
enclosingCallable,
callsiteParent,
childIdx,
enclosingStmt,
valueArguments.size,
{ argParent, idxOffset ->
extractCallValueArguments(
argParent,
valueArguments,
enclosingStmt,
enclosingCallable,
idxOffset
)
},
dispatchReceiver?.type,
dispatchReceiver?.let {
{ callId ->
extractExpressionExpr(
dispatchReceiver,
enclosingCallable,
callId,
-1,
enclosingStmt
)
}
},
extensionReceiver?.let {
{ argParent ->
extractExpressionExpr(
extensionReceiver,
enclosingCallable,
argParent,
0,
enclosingStmt
)
}
},
typeArguments,
extractClassTypeArguments,
superQualifierSymbol
)
}
}
fun extractRawMethodAccess(
syntacticCallTarget: IrFunction,
locId: Label<DbLocation>,
returnType: IrType,
enclosingCallable: Label<out DbCallable>,
callsiteParent: Label<out DbExprparent>,
childIdx: Int,
enclosingStmt: Label<out DbStmt>,
nValueArguments: Int,
extractValueArguments: (Label<out DbExpr>, Int) -> Unit,
drType: IrType?,
extractDispatchReceiver: ((Label<out DbExpr>) -> Unit)?,
extractExtensionReceiver: ((Label<out DbExpr>) -> Unit)?,
typeArguments: List<IrType> = listOf(),
extractClassTypeArguments: Boolean = false,
superQualifierSymbol: IrClassSymbol? = null
) {
val callTarget = getCalleeRealOverrideTarget(syntacticCallTarget)
val methodId = getCalleeMethodId(callTarget, drType, extractClassTypeArguments)
if (methodId == null) {
logger.warn("No method to bind call to for raw method access")
}
val id =
extractMethodAccessWithoutArgs(
returnType,
locId,
enclosingCallable,
callsiteParent,
childIdx,
enclosingStmt,
methodId
)
// type arguments at index -2, -3, ...
extractTypeArguments(typeArguments, locId, id, enclosingCallable, enclosingStmt, -2, true)
if (callTarget.isLocalFunction()) {
extractNewExprForLocalFunction(
getLocallyVisibleFunctionLabels(callTarget),
id,
locId,
enclosingCallable,
enclosingStmt
)
} else if (callTarget.shouldExtractAsStatic) {
extractStaticTypeAccessQualifier(
callTarget,
id,
locId,
enclosingCallable,
enclosingStmt
)
} else if (superQualifierSymbol != null) {
extractSuperAccess(
superQualifierSymbol.typeWith(),
enclosingCallable,
id,
-1,
enclosingStmt,
locId
)
} else if (extractDispatchReceiver != null) {
extractDispatchReceiver(id)
}
val idxOffset = if (extractExtensionReceiver != null) 1 else 0
val isBigArityFunctionInvoke =
drType is IrSimpleType &&
isFunctionInvoke(callTarget, drType) &&
drType.arguments.size > BuiltInFunctionArity.BIG_ARITY
val argParent =
if (isBigArityFunctionInvoke) {
extractArrayCreationWithInitializer(
id,
nValueArguments + idxOffset,
locId,
enclosingCallable,
enclosingStmt
)
} else {
id
}
if (extractExtensionReceiver != null) {
extractExtensionReceiver(argParent)
}
extractValueArguments(argParent, idxOffset)
}
private fun extractStaticTypeAccessQualifierUnchecked(
target: IrDeclaration,
parentExpr: Label<out DbExprparent>,
locId: Label<DbLocation>,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?
) {
val parent = target.parent
if (parent is IrExternalPackageFragment) {
// This is in a file class.
val fqName = getFileClassFqName(target)
if (fqName == null) {
logger.error(
"Can't get FqName for static type access qualifier in external package fragment ${target.javaClass}"
)
} else {
extractTypeAccess(
useFileClassType(fqName),
locId,
parentExpr,
-1,
enclosingCallable,
enclosingStmt
)
}
} else if (parent is IrClass) {
extractTypeAccessRecursive(
parent.toRawType(),
locId,
parentExpr,
-1,
enclosingCallable,
enclosingStmt
)
} else if (parent is IrFile) {
extractTypeAccess(
useFileClassType(parent),
locId,
parentExpr,
-1,
enclosingCallable,
enclosingStmt
)
} else {
logger.warnElement(
"Unexpected static type access qualifier ${parent.javaClass}",
parent
)
}
}
private fun extractStaticTypeAccessQualifier(
target: IrDeclaration,
parentExpr: Label<out DbExprparent>,
locId: Label<DbLocation>,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?
) {
if (target.shouldExtractAsStatic) {
extractStaticTypeAccessQualifierUnchecked(
target,
parentExpr,
locId,
enclosingCallable,
enclosingStmt
)
}
}
private fun isStaticAnnotatedNonCompanionMember(f: IrSimpleFunction) =
f.parentClassOrNull?.isNonCompanionObject == true &&
(f.hasAnnotation(jvmStaticFqName) ||
f.correspondingPropertySymbol?.owner?.hasAnnotation(jvmStaticFqName) == true)
private val IrDeclaration.shouldExtractAsStatic: Boolean
get() =
this is IrSimpleFunction &&
(isStaticFunction(this) || isStaticAnnotatedNonCompanionMember(this)) ||
this is IrField && this.isStatic ||
this is IrEnumEntry
private fun extractCallValueArguments(
callId: Label<out DbExprparent>,
call: IrFunctionAccessExpression,
enclosingStmt: Label<out DbStmt>,
enclosingCallable: Label<out DbCallable>,
idxOffset: Int
) =
extractCallValueArguments(
callId,
(0 until call.valueArgumentsCount).map { call.getValueArgument(it) },
enclosingStmt,
enclosingCallable,
idxOffset
)
private fun extractCallValueArguments(
callId: Label<out DbExprparent>,
valueArguments: List<IrExpression?>,
enclosingStmt: Label<out DbStmt>,
enclosingCallable: Label<out DbCallable>,
idxOffset: Int,
extractVarargAsArray: Boolean = false
) {
var i = 0
valueArguments.forEach { arg ->
if (arg != null) {
if (arg is IrVararg && !extractVarargAsArray) {
arg.elements.forEachIndexed { varargNo, vararg ->
extractVarargElement(
vararg,
enclosingCallable,
callId,
i + idxOffset + varargNo,
enclosingStmt
)
}
i += arg.elements.size
} else {
extractExpressionExpr(
arg,
enclosingCallable,
callId,
(i++) + idxOffset,
enclosingStmt
)
}
}
}
}
private fun findFunction(cls: IrClass, name: String): IrFunction? =
cls.declarations.findSubType<IrFunction> { it.name.asString() == name }
val jvmIntrinsicsClass by lazy { referenceExternalClass("kotlin.jvm.internal.Intrinsics") }
private fun findJdkIntrinsicOrWarn(name: String, warnAgainstElement: IrElement): IrFunction? {
val result = jvmIntrinsicsClass?.let { findFunction(it, name) }
if (result == null) {
logger.errorElement("Couldn't find JVM intrinsic function $name", warnAgainstElement)
}
return result
}
private fun findTopLevelFunctionOrWarn(
functionPkg: String,
functionName: String,
type: String,
parameterTypes: Array<String>,
warnAgainstElement: IrElement
): IrFunction? {
val fn =
getFunctionsByFqName(pluginContext, functionPkg, functionName)
.firstOrNull { fnSymbol ->
val owner = fnSymbol.owner
(owner.parentClassOrNull?.fqNameWhenAvailable?.asString() == type ||
(owner.parent is IrExternalPackageFragment &&
getFileClassFqName(owner)?.asString() == type)) &&
owner.valueParameters
.map { it.type.classFqName?.asString() }
.toTypedArray() contentEquals parameterTypes
}
?.owner
if (fn != null) {
if (fn.parentClassOrNull != null) {
extractExternalClassLater(fn.parentAsClass)
}
} else {
logger.errorElement(
"Couldn't find JVM intrinsic function $functionPkg $functionName in $type",
warnAgainstElement
)
}
return fn
}
private fun findTopLevelPropertyOrWarn(
propertyPkg: String,
propertyName: String,
type: String,
warnAgainstElement: IrElement
): IrProperty? {
val prop =
getPropertiesByFqName(pluginContext, propertyPkg, propertyName)
.firstOrNull { it.owner.parentClassOrNull?.fqNameWhenAvailable?.asString() == type }
?.owner
if (prop != null) {
if (prop.parentClassOrNull != null) {
extractExternalClassLater(prop.parentAsClass)
}
} else {
logger.errorElement(
"Couldn't find JVM intrinsic property $propertyPkg $propertyName in $type",
warnAgainstElement
)
}
return prop
}
val javaLangString by lazy { referenceExternalClass("java.lang.String") }
val stringValueOfObjectMethod by lazy {
val result =
javaLangString?.declarations?.findSubType<IrFunction> {
it.name.asString() == "valueOf" &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == pluginContext.irBuiltIns.anyNType
}
if (result == null) {
logger.error("Couldn't find declaration java.lang.String.valueOf(Object)")
}
result
}
val objectCloneMethod by lazy {
val result =
javaLangObject?.declarations?.findSubType<IrFunction> { it.name.asString() == "clone" }
if (result == null) {
logger.error("Couldn't find declaration java.lang.Object.clone(...)")
}
result
}
val kotlinNoWhenBranchMatchedExn by lazy {
referenceExternalClass("kotlin.NoWhenBranchMatchedException")
}
val kotlinNoWhenBranchMatchedConstructor by lazy {
val result =
kotlinNoWhenBranchMatchedExn?.declarations?.findSubType<IrConstructor> {
it.valueParameters.isEmpty()
}
if (result == null) {
logger.error("Couldn't find no-arg constructor for kotlin.NoWhenBranchMatchedException")
}
result
}
val javaUtilArrays by lazy { referenceExternalClass("java.util.Arrays") }
private fun isFunction(
target: IrFunction,
pkgName: String,
classNameLogged: String,
classNamePredicate: (String) -> Boolean,
vararg fNames: String,
isNullable: Boolean? = false
) =
fNames.any {
isFunction(target, pkgName, classNameLogged, classNamePredicate, it, isNullable)
}
private fun isFunction(
target: IrFunction,
pkgName: String,
classNameLogged: String,
classNamePredicate: (String) -> Boolean,
fName: String,
isNullable: Boolean? = false
): Boolean {
val verbose = false
fun verboseln(s: String) {
if (verbose) println(s)
}
verboseln("Attempting match for $pkgName $classNameLogged $fName")
if (target.name.asString() != fName) {
verboseln("No match as function name is ${target.name.asString()} not $fName")
return false
}
val extensionReceiverParameter = target.extensionReceiverParameter
val targetClass =
if (extensionReceiverParameter == null) {
if (isNullable == true) {
verboseln(
"Nullablility of type didn't match (target is not an extension method)"
)
return false
}
target.parent
} else {
val st = extensionReceiverParameter.type as? IrSimpleType
if (isNullable != null && st?.isNullable() != isNullable) {
verboseln("Nullablility of type didn't match")
return false
}
st?.classifier?.owner
}
if (targetClass !is IrClass) {
verboseln("No match as didn't find target class")
return false
}
if (!classNamePredicate(targetClass.name.asString())) {
verboseln(
"No match as class name is ${targetClass.name.asString()} not $classNameLogged"
)
return false
}
val targetPkg = targetClass.parent
if (targetPkg !is IrPackageFragment) {
verboseln("No match as didn't find target package")
return false
}
val targetName = targetPkg.packageFqName.asString()
if (targetName != pkgName) {
verboseln("No match as package name is $targetName not $pkgName")
return false
}
verboseln("Match")
return true
}
private fun isFunction(
target: IrFunction,
pkgName: String,
className: String,
fName: String,
isNullable: Boolean? = false
) = isFunction(target, pkgName, className, { it == className }, fName, isNullable)
private fun isNumericFunction(target: IrFunction, fName: String): Boolean {
return isFunction(target, "kotlin", "Int", fName) ||
isFunction(target, "kotlin", "Byte", fName) ||
isFunction(target, "kotlin", "Short", fName) ||
isFunction(target, "kotlin", "Long", fName) ||
isFunction(target, "kotlin", "Float", fName) ||
isFunction(target, "kotlin", "Double", fName)
}
private fun isNumericFunction(target: IrFunction, vararg fNames: String) =
fNames.any { isNumericFunction(target, it) }
private fun isArrayType(typeName: String) =
when (typeName) {
"Array" -> true
"IntArray" -> true
"ByteArray" -> true
"ShortArray" -> true
"LongArray" -> true
"FloatArray" -> true
"DoubleArray" -> true
"CharArray" -> true
"BooleanArray" -> true
else -> false
}
private fun isGenericArrayType(typeName: String) =
when (typeName) {
"Array" -> true
else -> false
}
private fun extractCall(
c: IrCall,
callable: Label<out DbCallable>,
stmtExprParent: StmtExprParent
) {
with("call", c) {
val owner = getBoundSymbolOwner(c.symbol, c) ?: return
val target = tryReplaceSyntheticFunction(owner)
// The vast majority of types of call want an expr context, so make one available
// lazily:
val exprParent by lazy { stmtExprParent.expr(c, callable) }
val parent by lazy { exprParent.parent }
val idx by lazy { exprParent.idx }
val enclosingStmt by lazy { exprParent.enclosingStmt }
fun extractMethodAccess(
syntacticCallTarget: IrFunction,
extractMethodTypeArguments: Boolean = true,
extractClassTypeArguments: Boolean = false
) {
val typeArgs =
if (extractMethodTypeArguments)
(0 until c.typeArgumentsCount)
.map { c.getTypeArgument(it) }
.requireNoNullsOrNull()
else listOf()
if (typeArgs == null) {
logger.warn("Missing type argument in extractMethodAccess")
return
}
extractRawMethodAccess(
syntacticCallTarget,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
(0 until c.valueArgumentsCount).map { c.getValueArgument(it) },
c.dispatchReceiver,
c.extensionReceiver,
typeArgs,
extractClassTypeArguments,
c.superQualifierSymbol
)
}
fun extractSpecialEnumFunction(fnName: String) {
if (c.typeArgumentsCount != 1) {
logger.errorElement("Expected to find exactly one type argument", c)
return
}
val enumType = (c.getTypeArgument(0) as? IrSimpleType)?.classifier?.owner
if (enumType == null) {
logger.errorElement("Couldn't find type of enum type", c)
return
}
if (enumType is IrClass) {
val func =
enumType.declarations.findSubType<IrFunction> {
it.name.asString() == fnName
}
if (func == null) {
logger.errorElement("Couldn't find function $fnName on enum type", c)
return
}
extractMethodAccess(func, false)
} else if (enumType is IrTypeParameter && enumType.isReified) {
// A call to `enumValues<T>()` is being extracted, where `T` is a reified type
// parameter of an `inline` function.
// We can't generate a valid expression here, because we would need to know the
// type of T on the call site.
// TODO: replace error expression with something that better shows this
// expression is unrepresentable.
val id = tw.getFreshIdLabel<DbErrorexpr>()
val type = useType(c.type)
tw.writeExprs_errorexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, tw.getLocation(c), callable, enclosingStmt)
} else {
logger.errorElement("Unexpected enum type rep ${enumType.javaClass}", c)
}
}
fun binopReceiver(
id: Label<out DbExpr>,
receiver: IrExpression?,
receiverDescription: String
) {
extractExprContext(id, tw.getLocation(c), callable, enclosingStmt)
if (receiver == null) {
logger.errorElement("$receiverDescription not found", c)
} else {
extractExpressionExpr(receiver, callable, id, 0, enclosingStmt)
}
if (c.valueArgumentsCount < 1) {
logger.errorElement("No RHS found", c)
} else {
if (c.valueArgumentsCount > 1) {
logger.errorElement("Extra arguments found", c)
}
val arg = c.getValueArgument(0)
if (arg == null) {
logger.errorElement("RHS null", c)
} else {
extractExpressionExpr(arg, callable, id, 1, enclosingStmt)
}
}
}
fun unaryopReceiver(
id: Label<out DbExpr>,
receiver: IrExpression?,
receiverDescription: String
) {
extractExprContext(id, tw.getLocation(c), callable, enclosingStmt)
if (receiver == null) {
logger.errorElement("$receiverDescription not found", c)
} else {
extractExpressionExpr(receiver, callable, id, 0, enclosingStmt)
}
if (c.valueArgumentsCount > 0) {
logger.errorElement("Extra arguments found", c)
}
}
/**
* Populate the lhs of a binary op from this call's dispatch receiver, and the rhs from
* its sole argument.
*/
fun binopDisp(id: Label<out DbExpr>) {
binopReceiver(id, c.dispatchReceiver, "Dispatch receiver")
}
fun binopExt(id: Label<out DbExpr>) {
binopReceiver(id, c.extensionReceiver, "Extension receiver")
}
fun unaryopDisp(id: Label<out DbExpr>) {
unaryopReceiver(id, c.dispatchReceiver, "Dispatch receiver")
}
fun unaryopExt(id: Label<out DbExpr>) {
unaryopReceiver(id, c.extensionReceiver, "Extension receiver")
}
val dr = c.dispatchReceiver
when {
isFunction(target, "kotlin", "String", "plus", false) -> {
val id = tw.getFreshIdLabel<DbAddexpr>()
val type = useType(c.type)
tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binopDisp(id)
}
isFunction(target, "kotlin", "String", "plus", true) -> {
findJdkIntrinsicOrWarn("stringPlus", c)?.let { stringPlusFn ->
extractRawMethodAccess(
stringPlusFn,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
listOf(c.extensionReceiver, c.getValueArgument(0)),
null,
null
)
}
}
isNumericFunction(
target,
"plus",
"minus",
"times",
"div",
"rem",
"and",
"or",
"xor",
"shl",
"shr",
"ushr"
) -> {
val type = useType(c.type)
val id: Label<out DbExpr> =
when (val targetName = target.name.asString()) {
"plus" -> {
val id = tw.getFreshIdLabel<DbAddexpr>()
tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx)
id
}
"minus" -> {
val id = tw.getFreshIdLabel<DbSubexpr>()
tw.writeExprs_subexpr(id, type.javaResult.id, parent, idx)
id
}
"times" -> {
val id = tw.getFreshIdLabel<DbMulexpr>()
tw.writeExprs_mulexpr(id, type.javaResult.id, parent, idx)
id
}
"div" -> {
val id = tw.getFreshIdLabel<DbDivexpr>()
tw.writeExprs_divexpr(id, type.javaResult.id, parent, idx)
id
}
"rem" -> {
val id = tw.getFreshIdLabel<DbRemexpr>()
tw.writeExprs_remexpr(id, type.javaResult.id, parent, idx)
id
}
"and" -> {
val id = tw.getFreshIdLabel<DbAndbitexpr>()
tw.writeExprs_andbitexpr(id, type.javaResult.id, parent, idx)
id
}
"or" -> {
val id = tw.getFreshIdLabel<DbOrbitexpr>()
tw.writeExprs_orbitexpr(id, type.javaResult.id, parent, idx)
id
}
"xor" -> {
val id = tw.getFreshIdLabel<DbXorbitexpr>()
tw.writeExprs_xorbitexpr(id, type.javaResult.id, parent, idx)
id
}
"shl" -> {
val id = tw.getFreshIdLabel<DbLshiftexpr>()
tw.writeExprs_lshiftexpr(id, type.javaResult.id, parent, idx)
id
}
"shr" -> {
val id = tw.getFreshIdLabel<DbRshiftexpr>()
tw.writeExprs_rshiftexpr(id, type.javaResult.id, parent, idx)
id
}
"ushr" -> {
val id = tw.getFreshIdLabel<DbUrshiftexpr>()
tw.writeExprs_urshiftexpr(id, type.javaResult.id, parent, idx)
id
}
else -> {
logger.errorElement("Unhandled binary target name: $targetName", c)
return
}
}
tw.writeExprsKotlinType(id, type.kotlinResult.id)
if (
isFunction(
target,
"kotlin",
"Byte or Short",
{ it == "Byte" || it == "Short" },
"and",
"or",
"xor"
)
)
binopExt(id)
else binopDisp(id)
}
// != gets desugared into not and ==. Here we resugar it.
c.origin == IrStatementOrigin.EXCLEQ &&
isFunction(target, "kotlin", "Boolean", "not") &&
c.valueArgumentsCount == 0 &&
dr != null &&
dr is IrCall &&
isBuiltinCallInternal(dr, "EQEQ") -> {
val id = tw.getFreshIdLabel<DbValueneexpr>()
val type = useType(c.type)
tw.writeExprs_valueneexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, dr, callable, enclosingStmt)
}
c.origin == IrStatementOrigin.EXCLEQEQ &&
isFunction(target, "kotlin", "Boolean", "not") &&
c.valueArgumentsCount == 0 &&
dr != null &&
dr is IrCall &&
isBuiltinCallInternal(dr, "EQEQEQ") -> {
val id = tw.getFreshIdLabel<DbNeexpr>()
val type = useType(c.type)
tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, dr, callable, enclosingStmt)
}
c.origin == IrStatementOrigin.EXCLEQ &&
isFunction(target, "kotlin", "Boolean", "not") &&
c.valueArgumentsCount == 0 &&
dr != null &&
dr is IrCall &&
isBuiltinCallInternal(dr, "ieee754equals") -> {
val id = tw.getFreshIdLabel<DbNeexpr>()
val type = useType(c.type)
tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, dr, callable, enclosingStmt)
}
isFunction(target, "kotlin", "Boolean", "not") -> {
val id = tw.getFreshIdLabel<DbLognotexpr>()
val type = useType(c.type)
tw.writeExprs_lognotexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
unaryopDisp(id)
}
isNumericFunction(target, "inv", "unaryMinus", "unaryPlus") -> {
val type = useType(c.type)
val id: Label<out DbExpr> =
when (val targetName = target.name.asString()) {
"inv" -> {
val id = tw.getFreshIdLabel<DbBitnotexpr>()
tw.writeExprs_bitnotexpr(id, type.javaResult.id, parent, idx)
id
}
"unaryMinus" -> {
val id = tw.getFreshIdLabel<DbMinusexpr>()
tw.writeExprs_minusexpr(id, type.javaResult.id, parent, idx)
id
}
"unaryPlus" -> {
val id = tw.getFreshIdLabel<DbPlusexpr>()
tw.writeExprs_plusexpr(id, type.javaResult.id, parent, idx)
id
}
else -> {
logger.errorElement("Unhandled unary target name: $targetName", c)
return
}
}
tw.writeExprsKotlinType(id, type.kotlinResult.id)
if (
isFunction(
target,
"kotlin",
"Byte or Short",
{ it == "Byte" || it == "Short" },
"inv"
)
)
unaryopExt(id)
else unaryopDisp(id)
}
// We need to handle all the builtin operators defines in BuiltInOperatorNames in
// compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/IrBuiltIns.kt
// as they can't be extracted as external dependencies.
isBuiltinCallInternal(c, "less") -> {
if (c.origin != IrStatementOrigin.LT) {
logger.warnElement("Unexpected origin for LT: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbLtexpr>()
val type = useType(c.type)
tw.writeExprs_ltexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "lessOrEqual") -> {
if (c.origin != IrStatementOrigin.LTEQ) {
logger.warnElement("Unexpected origin for LTEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbLeexpr>()
val type = useType(c.type)
tw.writeExprs_leexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "greater") -> {
if (c.origin != IrStatementOrigin.GT) {
logger.warnElement("Unexpected origin for GT: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbGtexpr>()
val type = useType(c.type)
tw.writeExprs_gtexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "greaterOrEqual") -> {
if (c.origin != IrStatementOrigin.GTEQ) {
logger.warnElement("Unexpected origin for GTEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbGeexpr>()
val type = useType(c.type)
tw.writeExprs_geexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "EQEQ") -> {
if (c.origin != IrStatementOrigin.EQEQ) {
logger.warnElement("Unexpected origin for EQEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbValueeqexpr>()
val type = useType(c.type)
tw.writeExprs_valueeqexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "EQEQEQ") -> {
if (c.origin != IrStatementOrigin.EQEQEQ) {
logger.warnElement("Unexpected origin for EQEQEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbEqexpr>()
val type = useType(c.type)
tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "ieee754equals") -> {
if (c.origin != IrStatementOrigin.EQEQ) {
logger.warnElement("Unexpected origin for ieee754equals: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbEqexpr>()
val type = useType(c.type)
tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "CHECK_NOT_NULL") -> {
if (c.origin != IrStatementOrigin.EXCLEXCL) {
logger.warnElement("Unexpected origin for CHECK_NOT_NULL: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbNotnullexpr>()
val type = useType(c.type)
tw.writeExprs_notnullexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
unaryOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "THROW_CCE") -> {
// TODO
logger.errorElement("Unhandled builtin", c)
}
isBuiltinCallInternal(c, "THROW_ISE") -> {
// TODO
logger.errorElement("Unhandled builtin", c)
}
isBuiltinCallInternal(c, "noWhenBranchMatchedException") -> {
kotlinNoWhenBranchMatchedConstructor?.let {
val locId = tw.getLocation(c)
val thrownType = useSimpleTypeClass(it.parentAsClass, listOf(), false)
val stmtParent = stmtExprParent.stmt(c, callable)
val throwId = tw.getFreshIdLabel<DbThrowstmt>()
tw.writeStmts_throwstmt(
throwId,
stmtParent.parent,
stmtParent.idx,
callable
)
tw.writeHasLocation(throwId, locId)
val newExprId =
extractNewExpr(
it,
null,
thrownType,
locId,
throwId,
0,
callable,
throwId
)
if (newExprId == null) {
logger.errorElement(
"No ID for newExpr in noWhenBranchMatchedException",
c
)
} else {
extractTypeAccess(thrownType, locId, newExprId, -3, callable, throwId)
}
}
}
isBuiltinCallInternal(c, "illegalArgumentException") -> {
// TODO
logger.errorElement("Unhandled builtin", c)
}
isBuiltinCallInternal(c, "ANDAND") -> {
// TODO
logger.errorElement("Unhandled builtin", c)
}
isBuiltinCallInternal(c, "OROR") -> {
// TODO
logger.errorElement("Unhandled builtin", c)
}
isFunction(target, "kotlin", "Any", "toString", true) -> {
stringValueOfObjectMethod?.let {
extractRawMethodAccess(
it,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
listOf(c.extensionReceiver),
null,
null
)
}
}
isBuiltinCallKotlin(c, "enumValues") -> {
extractSpecialEnumFunction("values")
}
isBuiltinCallKotlin(c, "enumValueOf") -> {
extractSpecialEnumFunction("valueOf")
}
isBuiltinCallKotlin(c, "arrayOfNulls") -> {
val id = tw.getFreshIdLabel<DbArraycreationexpr>()
val type = useType(c.type)
tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
val locId = tw.getLocation(c)
extractExprContext(id, locId, callable, enclosingStmt)
if (c.typeArgumentsCount == 1) {
val typeArgument = c.getTypeArgument(0)
if (typeArgument == null) {
logger.errorElement("Type argument missing in an arrayOfNulls call", c)
} else {
extractTypeAccessRecursive(
typeArgument,
locId,
id,
-1,
callable,
enclosingStmt,
TypeContext.GENERIC_ARGUMENT
)
}
} else {
logger.errorElement(
"Expected to find exactly one type argument in an arrayOfNulls call",
c
)
}
if (c.valueArgumentsCount == 1) {
val dim = c.getValueArgument(0)
if (dim != null) {
extractExpressionExpr(dim, callable, id, 0, enclosingStmt)
} else {
logger.errorElement(
"Expected to find non-null argument in an arrayOfNulls call",
c
)
}
} else {
logger.errorElement(
"Expected to find only one argument in an arrayOfNulls call",
c
)
}
}
isBuiltinCallKotlin(c, "arrayOf") ||
isBuiltinCallKotlin(c, "doubleArrayOf") ||
isBuiltinCallKotlin(c, "floatArrayOf") ||
isBuiltinCallKotlin(c, "longArrayOf") ||
isBuiltinCallKotlin(c, "intArrayOf") ||
isBuiltinCallKotlin(c, "charArrayOf") ||
isBuiltinCallKotlin(c, "shortArrayOf") ||
isBuiltinCallKotlin(c, "byteArrayOf") ||
isBuiltinCallKotlin(c, "booleanArrayOf") -> {
val isPrimitiveArrayCreation = !isBuiltinCallKotlin(c, "arrayOf")
val elementType =
if (isPrimitiveArrayCreation) {
c.type.getArrayElementType(pluginContext.irBuiltIns)
} else {
// TODO: is there any reason not to always use getArrayElementType?
if (c.typeArgumentsCount == 1) {
c.getTypeArgument(0).also {
if (it == null) {
logger.errorElement(
"Type argument missing in an arrayOf call",
c
)
}
}
} else {
logger.errorElement(
"Expected to find one type argument in arrayOf call",
c
)
null
}
}
val arg =
if (c.valueArgumentsCount == 1) c.getValueArgument(0)
else {
logger.errorElement(
"Expected to find only one (vararg) argument in ${c.symbol.owner.name.asString()} call",
c
)
null
}
?.let {
if (it is IrVararg) it
else {
logger.errorElement(
"Expected to find vararg argument in ${c.symbol.owner.name.asString()} call",
c
)
null
}
}
extractArrayCreation(
arg,
c.type,
elementType,
isPrimitiveArrayCreation,
c,
parent,
idx,
callable,
enclosingStmt
)
}
isBuiltinCall(c, "<get-java>", "kotlin.jvm") -> {
// Special case for KClass<*>.java, which is used in the Parcelize plugin. In
// normal cases, this is already rewritten to the property referenced below:
findTopLevelPropertyOrWarn(
"kotlin.jvm",
"java",
"kotlin.jvm.JvmClassMappingKt",
c
)
?.let { javaProp ->
val getter = javaProp.getter
if (getter == null) {
logger.error(
"Couldn't find getter of `kotlin.jvm.JvmClassMappingKt::java`"
)
return
}
val ext = c.extensionReceiver
if (ext == null) {
logger.errorElement(
"No extension receiver found for `KClass::java` call",
c
)
return
}
val argType =
(ext.type as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull
val typeArguments = if (argType == null) listOf() else listOf(argType)
extractRawMethodAccess(
getter,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
listOf(),
null,
ext,
typeArguments
)
}
}
isFunction(
target,
"kotlin",
"(some array type)",
{ isArrayType(it) },
"iterator"
) -> {
val parentClass = target.parent
if (parentClass !is IrClass) {
logger.errorElement("Iterator parent is not a class", c)
return
}
var typeFilter =
if (isGenericArrayType(parentClass.name.asString())) {
"kotlin.jvm.internal.ArrayIteratorKt"
} else {
"kotlin.jvm.internal.ArrayIteratorsKt"
}
findTopLevelFunctionOrWarn(
"kotlin.jvm.internal",
"iterator",
typeFilter,
arrayOf(parentClass.kotlinFqName.asString()),
c
)
?.let { iteratorFn ->
val dispatchReceiver = c.dispatchReceiver
if (dispatchReceiver == null) {
logger.errorElement(
"No dispatch receiver found for array iterator call",
c
)
} else {
val drType = dispatchReceiver.type
if (drType !is IrSimpleType) {
logger.errorElement(
"Dispatch receiver with unexpected type rep found for array iterator call: ${drType.javaClass}",
c
)
} else {
val typeArgs =
drType.arguments.map {
when (it) {
is IrTypeProjection -> it.type
else -> pluginContext.irBuiltIns.anyNType
}
}
extractRawMethodAccess(
iteratorFn,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
listOf(c.dispatchReceiver),
null,
null,
typeArgs
)
}
}
}
}
isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "get") &&
c.origin == IrStatementOrigin.GET_ARRAY_ELEMENT &&
c.dispatchReceiver != null -> {
val id = tw.getFreshIdLabel<DbArrayaccess>()
val type = useType(c.type)
tw.writeExprs_arrayaccess(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binopDisp(id)
}
isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "set") &&
c.origin == IrStatementOrigin.EQ &&
c.dispatchReceiver != null -> {
val array = c.dispatchReceiver
val arrayIdx = c.getValueArgument(0)
val assignedValue = c.getValueArgument(1)
if (array != null && arrayIdx != null && assignedValue != null) {
val locId = tw.getLocation(c)
extractAssignExpr(c.type, locId, parent, idx, callable, enclosingStmt)
.also { assignId ->
tw.getFreshIdLabel<DbArrayaccess>().also { arrayAccessId ->
val arrayType = useType(array.type)
tw.writeExprs_arrayaccess(
arrayAccessId,
arrayType.javaResult.id,
assignId,
0
)
tw.writeExprsKotlinType(
arrayAccessId,
arrayType.kotlinResult.id
)
extractExprContext(
arrayAccessId,
locId,
callable,
enclosingStmt
)
extractExpressionExpr(
array,
callable,
arrayAccessId,
0,
enclosingStmt
)
extractExpressionExpr(
arrayIdx,
callable,
arrayAccessId,
1,
enclosingStmt
)
}
extractExpressionExpr(
assignedValue,
callable,
assignId,
1,
enclosingStmt
)
}
} else {
logger.errorElement("Unexpected Array.set function signature", c)
}
}
isBuiltinCall(c, "<unsafe-coerce>", "kotlin.jvm.internal") -> {
if (c.valueArgumentsCount != 1) {
logger.errorElement(
"Expected to find one argument for a kotlin.jvm.internal.<unsafe-coerce>() call, but found ${c.valueArgumentsCount}",
c
)
return
}
if (c.typeArgumentsCount != 2) {
logger.errorElement(
"Expected to find two type arguments for a kotlin.jvm.internal.<unsafe-coerce>() call, but found ${c.typeArgumentsCount}",
c
)
return
}
val valueArg = c.getValueArgument(0)
if (valueArg == null) {
logger.errorElement(
"Cannot find value argument for a kotlin.jvm.internal.<unsafe-coerce>() call",
c
)
return
}
val typeArg = c.getTypeArgument(1)
if (typeArg == null) {
logger.errorElement(
"Cannot find type argument for a kotlin.jvm.internal.<unsafe-coerce>() call",
c
)
return
}
val id = tw.getFreshIdLabel<DbUnsafecoerceexpr>()
val locId = tw.getLocation(c)
val type = useType(c.type)
tw.writeExprs_unsafecoerceexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(typeArg, locId, id, 0, callable, enclosingStmt)
extractExpressionExpr(valueArg, callable, id, 1, enclosingStmt)
}
isBuiltinCallInternal(c, "dataClassArrayMemberToString") -> {
val arrayArg = c.getValueArgument(0)
val realArrayClass = arrayArg?.type?.classOrNull
if (realArrayClass == null) {
logger.errorElement(
"Argument to dataClassArrayMemberToString not a class",
c
)
return
}
val realCallee =
javaUtilArrays?.declarations?.findSubType<IrFunction> { decl ->
decl.name.asString() == "toString" &&
decl.valueParameters.size == 1 &&
decl.valueParameters[0].type.classOrNull?.let {
it == realArrayClass
} == true
}
if (realCallee == null) {
logger.errorElement(
"Couldn't find a java.lang.Arrays.toString method matching class ${realArrayClass.owner.name}",
c
)
} else {
extractRawMethodAccess(
realCallee,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
listOf(arrayArg),
null,
null
)
}
}
isBuiltinCallInternal(c, "dataClassArrayMemberHashCode") -> {
val arrayArg = c.getValueArgument(0)
val realArrayClass = arrayArg?.type?.classOrNull
if (realArrayClass == null) {
logger.errorElement(
"Argument to dataClassArrayMemberHashCode not a class",
c
)
return
}
val realCallee =
javaUtilArrays?.declarations?.findSubType<IrFunction> { decl ->
decl.name.asString() == "hashCode" &&
decl.valueParameters.size == 1 &&
decl.valueParameters[0].type.classOrNull?.let {
it == realArrayClass
} == true
}
if (realCallee == null) {
logger.errorElement(
"Couldn't find a java.lang.Arrays.hashCode method matching class ${realArrayClass.owner.name}",
c
)
} else {
extractRawMethodAccess(
realCallee,
c,
c.type,
callable,
parent,
idx,
enclosingStmt,
listOf(arrayArg),
null,
null
)
}
}
else -> {
extractMethodAccess(target, true, true)
}
}
}
}
private fun extractArrayCreation(
elementList: IrVararg?,
resultType: IrType,
elementType: IrType?,
allowPrimitiveElementType: Boolean,
locElement: IrElement,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) {
// If this is [someType]ArrayOf(*x), x, otherwise null
val clonedArray =
elementList?.let {
if (it.elements.size == 1) {
val onlyElement = it.elements[0]
if (onlyElement is IrSpreadElement) onlyElement.expression else null
} else null
}
if (clonedArray != null) {
// This is an array clone: extract is as a call to java.lang.Object.clone
objectCloneMethod?.let {
extractRawMethodAccess(
it,
locElement,
resultType,
enclosingCallable,
parent,
idx,
enclosingStmt,
listOf(),
clonedArray,
null
)
}
} else {
// This is array creation: extract it as a call to new ArrayType[] { ... }
val id = tw.getFreshIdLabel<DbArraycreationexpr>()
val type = useType(resultType)
tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
val locId = tw.getLocation(locElement)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
if (elementType != null) {
val typeContext =
if (allowPrimitiveElementType) TypeContext.OTHER
else TypeContext.GENERIC_ARGUMENT
extractTypeAccessRecursive(
elementType,
locId,
id,
-1,
enclosingCallable,
enclosingStmt,
typeContext
)
}
if (elementList != null) {
val initId = tw.getFreshIdLabel<DbArrayinit>()
tw.writeExprs_arrayinit(initId, type.javaResult.id, id, -2)
tw.writeExprsKotlinType(initId, type.kotlinResult.id)
extractExprContext(initId, locId, enclosingCallable, enclosingStmt)
elementList.elements.forEachIndexed { i, arg ->
extractVarargElement(arg, enclosingCallable, initId, i, enclosingStmt)
}
extractConstantInteger(
elementList.elements.size,
locId,
id,
0,
enclosingCallable,
enclosingStmt
)
}
}
}
private fun extractNewExpr(
methodId: Label<out DbConstructor>,
constructedType: TypeResults,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
): Label<DbNewexpr> {
val id = tw.getFreshIdLabel<DbNewexpr>()
tw.writeExprs_newexpr(id, constructedType.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, constructedType.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
tw.writeCallableBinding(id, methodId)
return id
}
private fun extractNewExpr(
calledConstructor: IrFunction,
constructorTypeArgs: List<IrTypeArgument>?,
constructedType: TypeResults,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
): Label<DbNewexpr>? {
val funId = useFunction<DbConstructor>(calledConstructor, constructorTypeArgs)
if (funId == null) {
logger.error("Cannot get ID for newExpr function")
return null
}
return extractNewExpr(funId, constructedType, locId, parent, idx, callable, enclosingStmt)
}
private fun needsObinitFunction(c: IrClass) =
c.primaryConstructor == null && c.constructors.count() > 1
private fun getObinitLabel(c: IrClass, parentId: Label<out DbElement>): String =
getFunctionLabel(
c,
parentId,
"<obinit>",
listOf(),
pluginContext.irBuiltIns.unitType,
null,
functionTypeParameters = listOf(),
classTypeArgsIncludingOuterClasses = listOf(),
overridesCollectionsMethod = false,
javaSignature = null,
addParameterWildcardsByDefault = false
)
private fun extractConstructorCall(
e: IrFunctionAccessExpression,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) {
val eType = e.type
if (eType !is IrSimpleType) {
logger.errorElement("Constructor call has non-simple type ${eType.javaClass}", e)
return
}
val type = useType(eType)
val isAnonymous = eType.isAnonymous
val locId = tw.getLocation(e)
val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) }
val id =
if (
e !is IrEnumConstructorCall && callUsesDefaultArguments(e.symbol.owner, valueArgs)
) {
val defaultsMethodId = getDefaultsMethodLabel(e.symbol.owner)
if (defaultsMethodId == null) {
logger.errorElement("Cannot get defaults method ID", e)
return
}
extractNewExpr(
defaultsMethodId.cast(),
type,
locId,
parent,
idx,
callable,
enclosingStmt
)
.also {
extractDefaultsCallArguments(
it,
e.symbol.owner,
callable,
enclosingStmt,
valueArgs,
null,
null
)
}
} else {
val newExprId =
extractNewExpr(
e.symbol.owner,
eType.arguments,
type,
locId,
parent,
idx,
callable,
enclosingStmt
)
if (newExprId == null) {
logger.errorElement("Cannot get newExpr ID", e)
return
}
val realCallTarget = e.symbol.owner.realOverrideTarget
// Generated constructor calls to kotlin.Enum have no arguments in IR, but the
// constructor takes two parameters.
if (
e is IrEnumConstructorCall &&
realCallTarget is IrConstructor &&
realCallTarget.parentClassOrNull?.fqNameWhenAvailable?.asString() ==
"kotlin.Enum" &&
realCallTarget.valueParameters.size == 2 &&
realCallTarget.valueParameters[0].type ==
pluginContext.irBuiltIns.stringType &&
realCallTarget.valueParameters[1].type == pluginContext.irBuiltIns.intType
) {
val id0 =
extractNull(
pluginContext.irBuiltIns.stringType,
locId,
newExprId,
0,
callable,
enclosingStmt
)
tw.writeCompiler_generated(
id0,
CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind
)
val id1 =
extractConstantInteger(0, locId, newExprId, 1, callable, enclosingStmt)
tw.writeCompiler_generated(
id1,
CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind
)
} else {
extractCallValueArguments(newExprId, e, enclosingStmt, callable, 0)
}
newExprId
}
if (isAnonymous) {
tw.writeIsAnonymClass(type.javaResult.id.cast(), id)
}
val dr = e.dispatchReceiver
if (dr != null) {
extractExpressionExpr(dr, callable, id, -2, enclosingStmt)
}
val typeAccessType =
if (isAnonymous) {
val c = eType.classifier.owner
if (c !is IrClass) {
logger.warnElement("Anonymous type not a class (${c.javaClass})", e)
}
if ((c as? IrClass)?.superTypes?.size == 1) {
useType(c.superTypes.first())
} else {
useType(pluginContext.irBuiltIns.anyType)
}
} else {
type
}
if (e is IrConstructorCall) {
extractConstructorTypeAccess(
eType,
typeAccessType,
e.symbol,
locId,
id,
-3,
callable,
enclosingStmt
)
} else if (e is IrEnumConstructorCall) {
val enumClass = e.symbol.owner.parent as? IrClass
if (enumClass == null) {
logger.warnElement("Couldn't find declaring class of enum constructor call", e)
return
}
val args =
(0 until e.typeArgumentsCount).map { e.getTypeArgument(it) }.requireNoNullsOrNull()
if (args == null) {
logger.warnElement("Found null type argument in enum constructor call", e)
return
}
val enumType = enumClass.typeWith(args)
extractConstructorTypeAccess(
enumType,
useType(enumType),
e.symbol,
locId,
id,
-3,
callable,
enclosingStmt
)
} else {
logger.errorElement("Unexpected constructor call type: ${e.javaClass}", e)
}
}
abstract inner class StmtExprParent {
abstract fun stmt(e: IrExpression, callable: Label<out DbCallable>): StmtParent
abstract fun expr(e: IrExpression, callable: Label<out DbCallable>): ExprParent
}
inner class StmtParent(val parent: Label<out DbStmtparent>, val idx: Int) : StmtExprParent() {
override fun stmt(e: IrExpression, callable: Label<out DbCallable>) = this
override fun expr(e: IrExpression, callable: Label<out DbCallable>) =
extractExpressionStmt(tw.getLocation(e), parent, idx, callable).let { id ->
ExprParent(id, 0, id)
}
}
inner class ExprParent(
val parent: Label<out DbExprparent>,
val idx: Int,
val enclosingStmt: Label<out DbStmt>
) : StmtExprParent() {
override fun stmt(e: IrExpression, callable: Label<out DbCallable>): StmtParent {
val id = tw.getFreshIdLabel<DbStmtexpr>()
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_stmtexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
return StmtParent(id, 0)
}
override fun expr(e: IrExpression, callable: Label<out DbCallable>): ExprParent {
return this
}
}
private fun getStatementOriginOperator(origin: IrStatementOrigin?) =
when (origin) {
IrStatementOrigin.PLUSEQ -> "plus"
IrStatementOrigin.MINUSEQ -> "minus"
IrStatementOrigin.MULTEQ -> "times"
IrStatementOrigin.DIVEQ -> "div"
IrStatementOrigin.PERCEQ -> "rem"
else -> null
}
private fun getUpdateInPlaceRHS(
origin: IrStatementOrigin?,
isExpectedLhs: (IrExpression?) -> Boolean,
updateRhs: IrExpression
): IrExpression? {
// Check for a desugared in-place update operator, such as "v += e":
return getStatementOriginOperator(origin)?.let {
if (updateRhs is IrCall && isNumericFunction(updateRhs.symbol.owner, it)) {
// Check for an expression like x = get(x).op(e):
val opReceiver = updateRhs.dispatchReceiver
if (isExpectedLhs(opReceiver)) {
updateRhs.getValueArgument(0)
} else null
} else null
}
}
private fun writeUpdateInPlaceExpr(
origin: IrStatementOrigin
): ((
tw: TrapWriter,
id: Label<out DbAssignexpr>,
type: Label<out DbType>,
exprParent: Label<out DbExprparent>,
index: Int
) -> Unit)? {
when (origin) {
IrStatementOrigin.PLUSEQ ->
return {
tw: TrapWriter,
id: Label<out DbAssignexpr>,
type: Label<out DbType>,
exprParent: Label<out DbExprparent>,
index: Int ->
tw.writeExprs_assignaddexpr(id.cast<DbAssignaddexpr>(), type, exprParent, index)
}
IrStatementOrigin.MINUSEQ ->
return {
tw: TrapWriter,
id: Label<out DbAssignexpr>,
type: Label<out DbType>,
exprParent: Label<out DbExprparent>,
index: Int ->
tw.writeExprs_assignsubexpr(id.cast<DbAssignsubexpr>(), type, exprParent, index)
}
IrStatementOrigin.MULTEQ ->
return {
tw: TrapWriter,
id: Label<out DbAssignexpr>,
type: Label<out DbType>,
exprParent: Label<out DbExprparent>,
index: Int ->
tw.writeExprs_assignmulexpr(id.cast<DbAssignmulexpr>(), type, exprParent, index)
}
IrStatementOrigin.DIVEQ ->
return {
tw: TrapWriter,
id: Label<out DbAssignexpr>,
type: Label<out DbType>,
exprParent: Label<out DbExprparent>,
index: Int ->
tw.writeExprs_assigndivexpr(id.cast<DbAssigndivexpr>(), type, exprParent, index)
}
IrStatementOrigin.PERCEQ ->
return {
tw: TrapWriter,
id: Label<out DbAssignexpr>,
type: Label<out DbType>,
exprParent: Label<out DbExprparent>,
index: Int ->
tw.writeExprs_assignremexpr(id.cast<DbAssignremexpr>(), type, exprParent, index)
}
else -> return null
}
}
/**
* This method tries to extract a block as an enhanced for loop. It returns true if it succeeds,
* and false otherwise.
*/
private fun tryExtractForLoop(
e: IrContainerExpression,
callable: Label<out DbCallable>,
parent: StmtExprParent
): Boolean {
/*
* We're expecting the pattern
* {
* val iterator = [expr].iterator()
* while (iterator.hasNext()) {
* val [loopVar] = iterator.next()
* [block]
* }
* }
*/
if (e.origin != IrStatementOrigin.FOR_LOOP || e.statements.size != 2) {
return false
}
val iteratorVariable = e.statements[0] as? IrVariable
val innerWhile = e.statements[1] as? IrWhileLoop
if (
iteratorVariable == null ||
iteratorVariable.origin != IrDeclarationOrigin.FOR_LOOP_ITERATOR ||
innerWhile == null ||
innerWhile.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE
) {
return false
}
val initializer = iteratorVariable.initializer as? IrCall
if (
initializer == null ||
initializer.origin != IrStatementOrigin.FOR_LOOP_ITERATOR ||
initializer.symbol.owner.name.asString() != "iterator"
) {
return false
}
val expr = initializer.dispatchReceiver
val cond = innerWhile.condition as? IrCall
val body = innerWhile.body as? IrBlock
if (
expr == null ||
cond == null ||
cond.origin != IrStatementOrigin.FOR_LOOP_HAS_NEXT ||
(cond.dispatchReceiver as? IrGetValue)?.symbol?.owner != iteratorVariable ||
body == null ||
body.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE ||
body.statements.size < 2
) {
return false
}
val loopVar = body.statements[0] as? IrVariable
val nextCall = loopVar?.initializer as? IrCall
if (
loopVar == null ||
!(loopVar.origin == IrDeclarationOrigin.FOR_LOOP_VARIABLE ||
loopVar.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) ||
nextCall == null ||
nextCall.origin != IrStatementOrigin.FOR_LOOP_NEXT ||
(nextCall.dispatchReceiver as? IrGetValue)?.symbol?.owner != iteratorVariable
) {
return false
}
val id =
extractLoop(innerWhile, null, parent, callable) { p, idx ->
tw.getFreshIdLabel<DbEnhancedforstmt>().also {
tw.writeStmts_enhancedforstmt(it, p, idx, callable)
}
}
extractVariableExpr(loopVar, callable, id, 0, id, extractInitializer = false)
extractExpressionExpr(expr, callable, id, 1, id)
val block = body.statements[1] as? IrBlock
if (body.statements.size == 2 && block != null) {
// Extract the body that was given to us by the compiler
extractExpressionStmt(block, callable, id, 2)
} else {
// Extract a block with all but the first (loop variable declaration) statement
extractBlock(body, body.statements.takeLast(body.statements.size - 1), id, 2, callable)
}
return true
}
/**
* This tried to extract a block as an array update. It returns true if it succeeds, and false
* otherwise.
*/
private fun tryExtractArrayUpdate(
e: IrContainerExpression,
callable: Label<out DbCallable>,
parent: StmtExprParent
): Boolean {
/*
* We're expecting the pattern
* {
* val array = e1
* val idx = e2
* array.set(idx, array.get(idx).op(e3))
* }
*
* If we find it, we'll extract e1[e2] op= e3 (op is +, -, ...)
*/
if (e.statements.size != 3) return false
(e.statements[0] as? IrVariable)?.let { arrayVarDecl ->
arrayVarDecl.initializer?.let { arrayVarInitializer ->
(e.statements[1] as? IrVariable)?.let { indexVarDecl ->
indexVarDecl.initializer?.let { indexVarInitializer ->
(e.statements[2] as? IrCall)?.let { arraySetCall ->
if (
isFunction(
arraySetCall.symbol.owner,
"kotlin",
"(some array type)",
{ isArrayType(it) },
"set"
)
) {
val updateRhs0 = arraySetCall.getValueArgument(1)
if (updateRhs0 == null) {
logger.errorElement("Update RHS not found", e)
return false
}
getUpdateInPlaceRHS(
e
.origin, // Using e.origin not arraySetCall.origin here
// distinguishes a compiler-generated block
// from a user manually code that looks the
// same.
{ oldValue ->
oldValue is IrCall &&
isFunction(
oldValue.symbol.owner,
"kotlin",
"(some array type)",
{ typeName -> isArrayType(typeName) },
"get"
) &&
(oldValue.dispatchReceiver as? IrGetValue)?.let {
receiverVal ->
receiverVal.symbol.owner ==
arrayVarDecl.symbol.owner
} ?: false
},
updateRhs0
)
?.let { updateRhs ->
val origin = e.origin
if (origin == null) {
logger.errorElement("No origin found", e)
return false
}
val writeUpdateInPlaceExprFun =
writeUpdateInPlaceExpr(origin)
if (writeUpdateInPlaceExprFun == null) {
logger.errorElement("Unexpected origin", e)
return false
}
// Create an assignment skeleton _ op= _
val exprParent = parent.expr(e, callable)
val assignId = tw.getFreshIdLabel<DbAssignexpr>()
val type = useType(arrayVarInitializer.type)
val locId = tw.getLocation(e)
tw.writeExprsKotlinType(assignId, type.kotlinResult.id)
extractExprContext(
assignId,
locId,
callable,
exprParent.enclosingStmt
)
writeUpdateInPlaceExprFun(
tw,
assignId,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
// Extract e1[e2]
val lhsId = tw.getFreshIdLabel<DbArrayaccess>()
val elementType = useType(updateRhs.type)
tw.writeExprs_arrayaccess(
lhsId,
elementType.javaResult.id,
assignId,
0
)
tw.writeExprsKotlinType(lhsId, elementType.kotlinResult.id)
extractExprContext(
lhsId,
locId,
callable,
exprParent.enclosingStmt
)
extractExpressionExpr(
arrayVarInitializer,
callable,
lhsId,
0,
exprParent.enclosingStmt
)
extractExpressionExpr(
indexVarInitializer,
callable,
lhsId,
1,
exprParent.enclosingStmt
)
// Extract e3
extractExpressionExpr(
updateRhs,
callable,
assignId,
1,
exprParent.enclosingStmt
)
return true
}
}
}
}
}
}
}
return false
}
private fun extractExpressionStmt(
locId: Label<DbLocation>,
parent: Label<out DbStmtparent>,
idx: Int,
callable: Label<out DbCallable>
) =
tw.getFreshIdLabel<DbExprstmt>().also {
tw.writeStmts_exprstmt(it, parent, idx, callable)
tw.writeHasLocation(it, locId)
}
private fun extractExpressionStmt(
e: IrExpression,
callable: Label<out DbCallable>,
parent: Label<out DbStmtparent>,
idx: Int
) {
extractExpression(e, callable, StmtParent(parent, idx))
}
fun extractExpressionExpr(
e: IrExpression,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>
) {
extractExpression(e, callable, ExprParent(parent, idx, enclosingStmt))
}
private fun extractExprContext(
id: Label<out DbExpr>,
locId: Label<DbLocation>,
callable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?
) {
tw.writeHasLocation(id, locId)
callable?.let { tw.writeCallableEnclosingExpr(id, it) }
enclosingStmt?.let { tw.writeStatementEnclosingExpr(id, it) }
}
private fun extractEqualsExpression(
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
tw.getFreshIdLabel<DbEqexpr>().also {
val type = useType(pluginContext.irBuiltIns.booleanType)
tw.writeExprs_eqexpr(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
}
private fun extractAndbitExpression(
type: IrType,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
tw.getFreshIdLabel<DbAndbitexpr>().also {
val typeResults = useType(type)
tw.writeExprs_andbitexpr(it, typeResults.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, typeResults.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
}
private fun extractConstantInteger(
v: Number,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
overrideId: Label<out DbExpr>? = null
) =
exprIdOrFresh<DbIntegerliteral>(overrideId).also {
val type = useType(pluginContext.irBuiltIns.intType)
tw.writeExprs_integerliteral(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
tw.writeNamestrings(v.toString(), v.toString(), it)
extractExprContext(it, locId, callable, enclosingStmt)
}
private fun extractNull(
t: IrType,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
overrideId: Label<out DbExpr>? = null
) =
exprIdOrFresh<DbNullliteral>(overrideId).also {
val type = useType(t)
tw.writeExprs_nullliteral(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
}
private fun extractAssignExpr(
type: IrType,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
tw.getFreshIdLabel<DbAssignexpr>().also {
val typeResults = useType(type)
tw.writeExprs_assignexpr(it, typeResults.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, typeResults.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
}
private fun extractExpression(
e: IrExpression,
callable: Label<out DbCallable>,
parent: StmtExprParent
) {
with("expression", e) {
when (e) {
is IrDelegatingConstructorCall -> {
val stmtParent = parent.stmt(e, callable)
val irCallable = declarationStack.peek().first
val delegatingClass = e.symbol.owner.parent
val currentClass = irCallable.parent
if (delegatingClass !is IrClass) {
logger.warnElement(
"Delegating class isn't a class: " + delegatingClass.javaClass,
e
)
}
if (currentClass !is IrClass) {
logger.warnElement(
"Current class isn't a class: " + currentClass.javaClass,
e
)
}
val id: Label<out DbStmt>
if (delegatingClass != currentClass) {
id = tw.getFreshIdLabel<DbSuperconstructorinvocationstmt>()
tw.writeStmts_superconstructorinvocationstmt(
id,
stmtParent.parent,
stmtParent.idx,
callable
)
} else {
id = tw.getFreshIdLabel<DbConstructorinvocationstmt>()
tw.writeStmts_constructorinvocationstmt(
id,
stmtParent.parent,
stmtParent.idx,
callable
)
}
val locId = tw.getLocation(e)
val methodId = useFunction<DbConstructor>(e.symbol.owner)
if (methodId == null) {
logger.errorElement("Cannot get ID for delegating constructor", e)
} else {
tw.writeCallableBinding(id.cast<DbCaller>(), methodId)
}
tw.writeHasLocation(id, locId)
extractCallValueArguments(id, e, id, callable, 0)
val dr = e.dispatchReceiver
if (dr != null) {
extractExpressionExpr(dr, callable, id, -1, id)
}
// todo: type arguments at index -2, -3, ...
}
is IrThrow -> {
val stmtParent = parent.stmt(e, callable)
val id = tw.getFreshIdLabel<DbThrowstmt>()
val locId = tw.getLocation(e)
tw.writeStmts_throwstmt(id, stmtParent.parent, stmtParent.idx, callable)
tw.writeHasLocation(id, locId)
extractExpressionExpr(e.value, callable, id, 0, id)
}
is IrBreak -> {
val stmtParent = parent.stmt(e, callable)
val id = tw.getFreshIdLabel<DbBreakstmt>()
tw.writeStmts_breakstmt(id, stmtParent.parent, stmtParent.idx, callable)
extractBreakContinue(e, id)
}
is IrContinue -> {
val stmtParent = parent.stmt(e, callable)
val id = tw.getFreshIdLabel<DbContinuestmt>()
tw.writeStmts_continuestmt(id, stmtParent.parent, stmtParent.idx, callable)
extractBreakContinue(e, id)
}
is IrReturn -> {
val stmtParent = parent.stmt(e, callable)
val id = tw.getFreshIdLabel<DbReturnstmt>()
val locId = tw.getLocation(e)
tw.writeStmts_returnstmt(id, stmtParent.parent, stmtParent.idx, callable)
tw.writeHasLocation(id, locId)
extractExpressionExpr(e.value, callable, id, 0, id)
}
is IrTry -> {
val stmtParent = parent.stmt(e, callable)
val id = tw.getFreshIdLabel<DbTrystmt>()
val locId = tw.getLocation(e)
tw.writeStmts_trystmt(id, stmtParent.parent, stmtParent.idx, callable)
tw.writeHasLocation(id, locId)
extractExpressionStmt(e.tryResult, callable, id, -1)
val finallyStmt = e.finallyExpression
if (finallyStmt != null) {
extractExpressionStmt(finallyStmt, callable, id, -2)
}
for ((catchIdx, catchClause) in e.catches.withIndex()) {
val catchId = tw.getFreshIdLabel<DbCatchclause>()
tw.writeStmts_catchclause(catchId, id, catchIdx, callable)
val catchLocId = tw.getLocation(catchClause)
tw.writeHasLocation(catchId, catchLocId)
extractTypeAccessRecursive(
catchClause.catchParameter.type,
tw.getLocation(catchClause.catchParameter),
catchId,
-1,
callable,
catchId
)
extractVariableExpr(
catchClause.catchParameter,
callable,
catchId,
0,
catchId
)
extractExpressionStmt(catchClause.result, callable, catchId, 1)
}
}
is IrContainerExpression -> {
if (
!tryExtractArrayUpdate(e, callable, parent) &&
!tryExtractForLoop(e, callable, parent)
) {
extractBlock(e, e.statements, parent, callable)
}
}
is IrWhileLoop -> {
extractLoopWithCondition(e, parent, callable)
}
is IrDoWhileLoop -> {
extractLoopWithCondition(e, parent, callable)
}
is IrInstanceInitializerCall -> {
val irConstructor = declarationStack.peek().first as? IrConstructor
if (irConstructor == null) {
logger.errorElement("IrInstanceInitializerCall outside constructor", e)
return
}
if (needsObinitFunction(irConstructor.parentAsClass)) {
val exprParent = parent.expr(e, callable)
val id = tw.getFreshIdLabel<DbMethodaccess>()
val type = useType(pluginContext.irBuiltIns.unitType)
val locId = tw.getLocation(e)
val parentClass = irConstructor.parentAsClass
val parentId = useDeclarationParentOf(irConstructor, false, null, true)
if (parentId == null) {
logger.errorElement("Cannot get parent ID for obinit", e)
return
}
val methodLabel = getObinitLabel(parentClass, parentId)
val methodId = tw.getLabelFor<DbMethod>(methodLabel)
tw.writeExprs_methodaccess(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
tw.writeCallableBinding(id, methodId)
} else {
val stmtParent = parent.stmt(e, callable)
extractInstanceInitializerBlock(stmtParent, irConstructor)
}
}
is IrConstructorCall -> {
val exprParent = parent.expr(e, callable)
extractConstructorCall(
e,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
is IrEnumConstructorCall -> {
val exprParent = parent.expr(e, callable)
extractConstructorCall(
e,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
is IrCall -> {
extractCall(e, callable, parent)
}
is IrStringConcatenation -> {
val exprParent = parent.expr(e, callable)
val id = tw.getFreshIdLabel<DbStringtemplateexpr>()
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_stringtemplateexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
e.arguments.forEachIndexed { i, a ->
extractExpressionExpr(a, callable, id, i, exprParent.enclosingStmt)
}
}
is CodeQLIrConst<*> -> {
val exprParent = parent.expr(e, callable)
extractConstant(
e,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
is IrGetValue -> {
val exprParent = parent.expr(e, callable)
val owner = e.symbol.owner
if (
owner is IrValueParameter &&
owner.index == -1 &&
!owner.isExtensionReceiver()
) {
extractThisAccess(e, owner.parent, exprParent, callable)
} else {
val isAnnotationClassParameter =
((owner as? IrValueParameter)?.parent as? IrConstructor)
?.parentClassOrNull
?.kind == ClassKind.ANNOTATION_CLASS
val extractType =
if (isAnnotationClassParameter) kClassToJavaClass(e.type) else e.type
extractVariableAccess(
useValueDeclaration(owner),
extractType,
tw.getLocation(e),
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
}
is IrGetField -> {
val exprParent = parent.expr(e, callable)
val owner = tryReplaceAndroidSyntheticField(e.symbol.owner)
val locId = tw.getLocation(e)
val fieldType =
if (isAnnotationClassField(owner)) kClassToJavaClass(e.type) else e.type
extractVariableAccess(
useField(owner),
fieldType,
locId,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
.also { id ->
val receiver = e.receiver
if (receiver != null) {
extractExpressionExpr(
receiver,
callable,
id,
-1,
exprParent.enclosingStmt
)
} else if (owner.isStatic) {
extractStaticTypeAccessQualifier(
owner,
id,
locId,
callable,
exprParent.enclosingStmt
)
}
}
}
is IrGetEnumValue -> {
val exprParent = parent.expr(e, callable)
extractEnumValue(
e,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
is IrSetValue,
is IrSetField -> {
val exprParent = parent.expr(e, callable)
val id = tw.getFreshIdLabel<DbAssignexpr>()
val type = useType(e.type)
val rhsValue =
when (e) {
is IrSetValue -> e.value
is IrSetField -> e.value
else -> {
logger.errorElement("Unhandled IrSet* element.", e)
return
}
}
// The set operation's location as actually that of its LHS. Hence, the
// assignment spans the
// set op plus its RHS, while the varAccess takes its location from `e`.
val locId = tw.getLocation(e.startOffset, rhsValue.endOffset)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
val lhsId = tw.getFreshIdLabel<DbVaraccess>()
val lhsLocId = tw.getLocation(e)
extractExprContext(lhsId, lhsLocId, callable, exprParent.enclosingStmt)
when (e) {
is IrSetValue -> {
// Check for a desugared in-place update operator, such as "v += e":
val inPlaceUpdateRhs =
getUpdateInPlaceRHS(
e.origin,
{ it is IrGetValue && it.symbol.owner == e.symbol.owner },
rhsValue
)
if (inPlaceUpdateRhs != null) {
val origin = e.origin
if (origin == null) {
logger.errorElement("No origin for set-value", e)
return
} else {
val writeUpdateInPlaceExprFun = writeUpdateInPlaceExpr(origin)
if (writeUpdateInPlaceExprFun == null) {
logger.errorElement("Unexpected origin for set-value", e)
return
}
writeUpdateInPlaceExprFun(
tw,
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
}
} else {
tw.writeExprs_assignexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
}
val lhsType = useType(e.symbol.owner.type)
tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0)
tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
val vId = useValueDeclaration(e.symbol.owner)
if (vId != null) {
tw.writeVariableBinding(lhsId, vId)
}
extractExpressionExpr(
inPlaceUpdateRhs ?: rhsValue,
callable,
id,
1,
exprParent.enclosingStmt
)
}
is IrSetField -> {
tw.writeExprs_assignexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
val realField = tryReplaceAndroidSyntheticField(e.symbol.owner)
val lhsType = useType(realField.type)
tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0)
tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
val vId = useField(realField)
tw.writeVariableBinding(lhsId, vId)
extractExpressionExpr(
e.value,
callable,
id,
1,
exprParent.enclosingStmt
)
val receiver = e.receiver
if (receiver != null) {
extractExpressionExpr(
receiver,
callable,
lhsId,
-1,
exprParent.enclosingStmt
)
} else if (realField.isStatic) {
extractStaticTypeAccessQualifier(
realField,
lhsId,
lhsLocId,
callable,
exprParent.enclosingStmt
)
}
}
else -> {
logger.errorElement("Unhandled IrSet* element.", e)
}
}
}
is IrWhen -> {
val isAndAnd = e.origin == IrStatementOrigin.ANDAND
val isOrOr = e.origin == IrStatementOrigin.OROR
if (
(isAndAnd || isOrOr) &&
e.branches.size == 2 &&
(e.branches[1].condition as? CodeQLIrConst<*>)?.value == true &&
(e.branches[if (e.origin == IrStatementOrigin.ANDAND) 1 else 0].result
as? CodeQLIrConst<*>)
?.value == isOrOr
) {
// resugar binary logical operators:
val exprParent = parent.expr(e, callable)
val type = useType(e.type)
val id =
if (e.origin == IrStatementOrigin.ANDAND) {
val id = tw.getFreshIdLabel<DbAndlogicalexpr>()
tw.writeExprs_andlogicalexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
id
} else {
val id = tw.getFreshIdLabel<DbOrlogicalexpr>()
tw.writeExprs_orlogicalexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
id
}
val locId = tw.getLocation(e)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
extractExpressionExpr(
e.branches[0].condition,
callable,
id,
0,
exprParent.enclosingStmt
)
var rhsIdx = if (e.origin == IrStatementOrigin.ANDAND) 0 else 1
extractExpressionExpr(
e.branches[rhsIdx].result,
callable,
id,
1,
exprParent.enclosingStmt
)
return
}
val exprParent = parent.expr(e, callable)
val id = tw.getFreshIdLabel<DbWhenexpr>()
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_whenexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
if (e.origin == IrStatementOrigin.IF) {
tw.writeWhen_if(id)
}
e.branches.forEachIndexed { i, b ->
val bId = tw.getFreshIdLabel<DbWhenbranch>()
val bLocId = tw.getLocation(b)
tw.writeStmts_whenbranch(bId, id, i, callable)
tw.writeHasLocation(bId, bLocId)
extractExpressionExpr(b.condition, callable, bId, 0, bId)
extractExpressionStmt(b.result, callable, bId, 1)
if (b is IrElseBranch) {
tw.writeWhen_branch_else(bId)
}
}
}
is IrGetClass -> {
val exprParent = parent.expr(e, callable)
val id = tw.getFreshIdLabel<DbGetclassexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_getclassexpr(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 0, exprParent.enclosingStmt)
}
is IrTypeOperatorCall -> {
val exprParent = parent.expr(e, callable)
extractTypeOperatorCall(
e,
callable,
exprParent.parent,
exprParent.idx,
exprParent.enclosingStmt
)
}
is IrVararg -> {
// There are lowered IR cases when the vararg expression is not within a call,
// such as
// val temp0 = [*expr].
// This AST element can also occur as a collection literal in an annotation
// class, such as
// annotation class Ann(val strings: Array<String> = [])
val exprParent = parent.expr(e, callable)
extractArrayCreation(
e,
e.type,
e.varargElementType,
true,
e,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
is IrGetObjectValue -> {
// For `object MyObject { ... }`, the .class has an
// automatically-generated `public static final MyObject INSTANCE`
// field that we are accessing here.
val exprParent = parent.expr(e, callable)
val c = getBoundSymbolOwner(e.symbol, e) ?: return
val instance =
if (c.isCompanion) useCompanionObjectClassInstance(c)
else useObjectClassInstance(c)
if (instance != null) {
val id = tw.getFreshIdLabel<DbVaraccess>()
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_varaccess(
id,
type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, exprParent.enclosingStmt)
tw.writeVariableBinding(id, instance.id)
}
}
is IrFunctionReference -> {
extractFunctionReference(e, parent, callable)
}
is IrFunctionExpression -> {
/*
* Extract generated class:
* ```
* class C : Any, kotlin.FunctionI<T0,T1, ... TI, R> {
* constructor() { super(); }
* fun invoke(a0:T0, a1:T1, ... aI: TI): R { ... }
* }
* ```
* or in case of big arity lambdas
* ```
* class C : Any, kotlin.FunctionN<R> {
* constructor() { super(); }
* fun invoke(a0:T0, a1:T1, ... aI: TI): R { ... }
* fun invoke(vararg args: Any?): R {
* return invoke(args[0] as T0, args[1] as T1, ..., args[I] as TI)
* }
* }
* ```
**/
val ids = getLocallyVisibleFunctionLabels(e.function)
val locId = tw.getLocation(e)
val ext = e.function.extensionReceiverParameter
val parameters =
if (ext != null) {
listOf(ext) + e.function.valueParameters
} else {
e.function.valueParameters
}
var types = parameters.map { it.type }
types += e.function.returnType
val isBigArity = types.size > BuiltInFunctionArity.BIG_ARITY
if (isBigArity) {
implementFunctionNInvoke(e.function, ids, locId, parameters)
} else {
addModifiers(ids.function, "override")
}
val exprParent = parent.expr(e, callable)
val idLambdaExpr = tw.getFreshIdLabel<DbLambdaexpr>()
tw.writeExprs_lambdaexpr(
idLambdaExpr,
ids.type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(idLambdaExpr, ids.type.kotlinResult.id)
extractExprContext(idLambdaExpr, locId, callable, exprParent.enclosingStmt)
tw.writeCallableBinding(idLambdaExpr, ids.constructor)
// todo: fix hard coded block body of lambda
tw.writeLambdaKind(idLambdaExpr, 1)
val fnInterfaceType = getFunctionalInterfaceType(types)
if (fnInterfaceType == null) {
logger.warnElement(
"Cannot find functional interface type for function expression",
e
)
} else {
val id =
extractGeneratedClass(
e
.function, // We're adding this function as a member, and
// changing its name to `invoke` to implement
// `kotlin.FunctionX<,,,>.invoke(,,)`
listOf(pluginContext.irBuiltIns.anyType, fnInterfaceType)
)
extractTypeAccessRecursive(
fnInterfaceType,
locId,
idLambdaExpr,
-3,
callable,
exprParent.enclosingStmt
)
tw.writeIsAnonymClass(id, idLambdaExpr)
}
}
is IrClassReference -> {
val exprParent = parent.expr(e, callable)
extractClassReference(
e,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
}
is IrPropertyReference -> {
extractPropertyReference(
"property reference",
e,
e.getter,
e.setter,
e.field,
parent,
callable
)
}
is IrLocalDelegatedPropertyReference -> {
extractPropertyReference(
"local delegated property reference",
e,
e.getter,
e.setter,
null,
parent,
callable
)
}
else -> {
logger.errorElement("Unrecognised IrExpression: " + e.javaClass, e)
}
}
return
}
}
private fun extractBlock(
e: IrContainerExpression,
statements: List<IrStatement>,
parent: StmtExprParent,
callable: Label<out DbCallable>
) {
val stmtParent = parent.stmt(e, callable)
extractBlock(e, statements, stmtParent.parent, stmtParent.idx, callable)
}
private fun extractBlock(
e: IrElement,
statements: List<IrStatement>,
parent: Label<out DbStmtparent>,
idx: Int,
callable: Label<out DbCallable>
) {
val id = tw.getFreshIdLabel<DbBlock>()
val locId = tw.getLocation(e)
tw.writeStmts_block(id, parent, idx, callable)
tw.writeHasLocation(id, locId)
statements.forEachIndexed { i, s -> extractStatement(s, callable, id, i) }
}
private inline fun <D : DeclarationDescriptor, reified B : IrSymbolOwner> getBoundSymbolOwner(
symbol: IrBindableSymbol<D, B>,
e: IrExpression
): B? {
if (symbol.isBound) {
return symbol.owner
}
logger.errorElement("Unbound symbol found, skipping extraction of expression", e)
return null
}
private fun extractSuperAccess(
irType: IrType,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>,
locId: Label<DbLocation>
) =
tw.getFreshIdLabel<DbSuperaccess>().also {
val type = useType(irType)
tw.writeExprs_superaccess(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
extractTypeAccessRecursive(irType, locId, it, 0)
}
private fun extractThisAccess(
type: TypeResults,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>,
locId: Label<DbLocation>
) =
tw.getFreshIdLabel<DbThisaccess>().also {
tw.writeExprs_thisaccess(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
}
private fun extractThisAccess(
irType: IrType,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>,
locId: Label<DbLocation>
) = extractThisAccess(useType(irType), callable, parent, idx, enclosingStmt, locId)
private fun extractThisAccess(
e: IrGetValue,
thisParamParent: IrDeclarationParent,
exprParent: ExprParent,
callable: Label<out DbCallable>
) {
val containingDeclaration = declarationStack.peek().first
val locId = tw.getLocation(e)
if (
containingDeclaration.shouldExtractAsStatic &&
containingDeclaration.parentClassOrNull?.isNonCompanionObject == true
) {
// Use of `this` in a non-companion object member that will be lowered to a static
// function -- replace with a reference
// to the corresponding static object instance.
val instanceField = useObjectClassInstance(containingDeclaration.parentAsClass)
extractVariableAccess(
instanceField.id,
e.type,
locId,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
.also { varAccessId ->
extractStaticTypeAccessQualifier(
containingDeclaration,
varAccessId,
locId,
callable,
exprParent.enclosingStmt
)
}
} else {
if (thisParamParent is IrFunction) {
val overriddenAttributes =
declarationStack.findOverriddenAttributes(thisParamParent)
val replaceWithParamIdx =
overriddenAttributes?.valueParameters?.indexOf(e.symbol.owner)
if (replaceWithParamIdx != null && replaceWithParamIdx != -1) {
// Use of 'this' in a function where the dispatch receiver is passed like an
// ordinary parameter,
// such as a `$default` static function that substitutes in default arguments as
// needed.
val paramDeclarerId =
overriddenAttributes.id ?: useDeclarationParent(thisParamParent, false)
val replacementParamId =
tw.getLabelFor<DbParam>(
getValueParameterLabel(paramDeclarerId, replaceWithParamIdx)
)
extractVariableAccess(
replacementParamId,
e.type,
locId,
exprParent.parent,
exprParent.idx,
callable,
exprParent.enclosingStmt
)
return
}
}
val id =
extractThisAccess(
e.type,
callable,
exprParent.parent,
exprParent.idx,
exprParent.enclosingStmt,
locId
)
fun extractTypeAccess(parent: IrClass) {
extractTypeAccessRecursive(
parent.typeWith(listOf()),
locId,
id,
0,
callable,
exprParent.enclosingStmt
)
}
val owner = e.symbol.owner
when (val ownerParent = owner.parent) {
is IrFunction -> {
if (
ownerParent.dispatchReceiverParameter == owner &&
ownerParent.extensionReceiverParameter != null
) {
val ownerParent2 = ownerParent.parent
if (ownerParent2 is IrClass) {
extractTypeAccess(ownerParent2)
} else {
logger.errorElement("Unhandled qualifier for this", e)
}
}
}
is IrClass -> {
if (ownerParent.thisReceiver == owner) {
extractTypeAccess(ownerParent)
}
}
else -> {
logger.errorElement(
"Unexpected owner parent for this access: " + ownerParent.javaClass,
e
)
}
}
}
}
private fun extractVariableAccess(
variable: Label<out DbVariable>?,
type: TypeResults,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
tw.getFreshIdLabel<DbVaraccess>().also {
tw.writeExprs_varaccess(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
if (variable != null) {
tw.writeVariableBinding(it, variable)
}
}
private fun extractVariableAccess(
variable: Label<out DbVariable>?,
irType: IrType,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
extractVariableAccess(
variable,
useType(irType),
locId,
parent,
idx,
callable,
enclosingStmt
)
private fun extractLoop(
loop: IrLoop,
bodyIdx: Int?,
stmtExprParent: StmtExprParent,
callable: Label<out DbCallable>,
getId: (Label<out DbStmtparent>, Int) -> Label<out DbStmt>
): Label<out DbStmt> {
val stmtParent = stmtExprParent.stmt(loop, callable)
val locId = tw.getLocation(loop)
val idx: Int
val parent: Label<out DbStmtparent>
val label = loop.label
if (label != null) {
val labeledStmt = tw.getFreshIdLabel<DbLabeledstmt>()
tw.writeStmts_labeledstmt(labeledStmt, stmtParent.parent, stmtParent.idx, callable)
tw.writeHasLocation(labeledStmt, locId)
tw.writeNamestrings(label, "", labeledStmt)
idx = 0
parent = labeledStmt
} else {
idx = stmtParent.idx
parent = stmtParent.parent
}
val id = getId(parent, idx)
tw.writeHasLocation(id, locId)
val body = loop.body
if (body != null && bodyIdx != null) {
extractExpressionStmt(body, callable, id, bodyIdx)
}
return id
}
private fun extractLoopWithCondition(
loop: IrLoop,
stmtExprParent: StmtExprParent,
callable: Label<out DbCallable>
) {
val id =
extractLoop(loop, 1, stmtExprParent, callable) { parent, idx ->
if (loop is IrWhileLoop) {
tw.getFreshIdLabel<DbWhilestmt>().also {
tw.writeStmts_whilestmt(it, parent, idx, callable)
}
} else {
tw.getFreshIdLabel<DbDostmt>().also {
tw.writeStmts_dostmt(it, parent, idx, callable)
}
}
}
extractExpressionExpr(loop.condition, callable, id, 0, id)
}
private fun <T : DbExpr> exprIdOrFresh(id: Label<out DbExpr>?) =
id?.cast<T>() ?: tw.getFreshIdLabel()
private fun extractClassReference(
e: IrClassReference,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
overrideId: Label<out DbExpr>? = null,
typeAccessOverrideId: Label<out DbExpr>? = null,
useJavaLangClassType: Boolean = false
) =
exprIdOrFresh<DbTypeliteral>(overrideId).also { id ->
val locId = tw.getLocation(e)
val jlcType =
if (useJavaLangClassType) this.javaLangClass?.let { it.typeWith() } else null
val type = useType(jlcType ?: e.type)
tw.writeExprs_typeliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
extractTypeAccessRecursive(
e.classType,
locId,
id,
0,
enclosingCallable,
enclosingStmt,
overrideId = typeAccessOverrideId
)
}
private fun extractEnumValue(
e: IrGetEnumValue,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
extractTypeAccess: Boolean = true,
overrideId: Label<out DbExpr>? = null
) =
exprIdOrFresh<DbVaraccess>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_varaccess(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
getBoundSymbolOwner(e.symbol, e)?.let { owner ->
val vId = useEnumEntry(owner)
tw.writeVariableBinding(id, vId)
if (extractTypeAccess)
extractStaticTypeAccessQualifier(
owner,
id,
locId,
enclosingCallable,
enclosingStmt
)
}
}
private fun escapeCharForQuotedLiteral(c: Char) =
when (c) {
'\r' -> "\\r"
'\n' -> "\\n"
'\t' -> "\\t"
'\\' -> "\\\\"
'"' -> "\\\""
else -> c.toString()
}
// Render a string literal as it might occur in Kotlin source. Note this is a reasonable guess;
// the real source
// could use other escape sequences to describe the same String. Importantly, this is the same
// guess the Java
// extractor makes regarding string literals occurring within annotations, which we need to
// coincide with to ensure
// database consistency.
private fun toQuotedLiteral(s: String) =
s.toCharArray().joinToString(separator = "", prefix = "\"", postfix = "\"") { c ->
escapeCharForQuotedLiteral(c)
}
private fun extractConstant(
e: CodeQLIrConst<*>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
overrideId: Label<out DbExpr>? = null
): Label<out DbExpr>? {
val v = e.value
return when {
v is Number && (v is Int || v is Short || v is Byte) -> {
extractConstantInteger(
v,
tw.getLocation(e),
parent,
idx,
enclosingCallable,
enclosingStmt,
overrideId = overrideId
)
}
v is Long -> {
exprIdOrFresh<DbLongliteral>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_longliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
tw.writeNamestrings(v.toString(), v.toString(), id)
}
}
v is Float -> {
exprIdOrFresh<DbFloatingpointliteral>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_floatingpointliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
tw.writeNamestrings(v.toString(), v.toString(), id)
}
}
v is Double -> {
exprIdOrFresh<DbDoubleliteral>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_doubleliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
tw.writeNamestrings(v.toString(), v.toString(), id)
}
}
v is Boolean -> {
exprIdOrFresh<DbBooleanliteral>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_booleanliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
tw.writeNamestrings(v.toString(), v.toString(), id)
}
}
v is Char -> {
exprIdOrFresh<DbCharacterliteral>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_characterliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
tw.writeNamestrings(v.toString(), v.toString(), id)
}
}
v is String -> {
exprIdOrFresh<DbStringliteral>(overrideId).also { id ->
val type = useType(e.type)
val locId = tw.getLocation(e)
tw.writeExprs_stringliteral(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, enclosingCallable, enclosingStmt)
tw.writeNamestrings(toQuotedLiteral(v.toString()), v.toString(), id)
}
}
v == null -> {
extractNull(
e.type,
tw.getLocation(e),
parent,
idx,
enclosingCallable,
enclosingStmt,
overrideId = overrideId
)
}
else -> {
null.also { logger.errorElement("Unrecognised IrConst: " + v.javaClass, e) }
}
}
}
private fun IrValueParameter.isExtensionReceiver(): Boolean {
val parentFun = parent as? IrFunction ?: return false
return parentFun.extensionReceiverParameter == this
}
private open inner class GeneratedClassHelper(
protected val locId: Label<DbLocation>,
protected val ids: GeneratedClassLabels
) {
protected val classId = ids.type.javaResult.id.cast<DbClassorinterface>()
/**
* Extract a parameter to field assignment, such as `this.field = paramName` below:
* ```
* constructor(paramName: type) {
* this.field = paramName
* }
* ```
*/
fun extractParameterToFieldAssignmentInConstructor(
paramName: String,
paramType: IrType,
fieldId: Label<DbField>,
paramIdx: Int,
stmtIdx: Int
) {
val paramId = tw.getFreshIdLabel<DbParam>()
extractValueParameter(
paramId,
paramType,
paramName,
locId,
ids.constructor,
paramIdx,
paramId,
syntheticParameterNames = false,
isVararg = false,
isNoinline = false,
isCrossinline = false
)
extractExpressionStmt(locId, ids.constructorBlock, stmtIdx, ids.constructor).also {
assignmentStmtId ->
extractAssignExpr(
paramType,
locId,
assignmentStmtId,
0,
ids.constructor,
assignmentStmtId
)
.also { assignmentId ->
extractVariableAccess(
fieldId,
paramType,
locId,
assignmentId,
0,
ids.constructor,
assignmentStmtId
)
.also { lhsId ->
extractThisAccess(
ids.type,
ids.constructor,
lhsId,
-1,
assignmentStmtId,
locId
)
}
extractVariableAccess(
paramId,
paramType,
locId,
assignmentId,
1,
ids.constructor,
assignmentStmtId
)
}
}
}
}
data class ReceiverInfo(
val receiver: IrExpression,
val type: IrType,
val field: Label<DbField>,
val indexOffset: Int
)
private fun makeReceiverInfo(receiver: IrExpression?, indexOffset: Int): ReceiverInfo? {
if (receiver == null) {
return null
}
val type = receiver.type
val field: Label<DbField> = tw.getFreshIdLabel()
return ReceiverInfo(receiver, type, field, indexOffset)
}
/**
* This is used when extracting callable references, i.e. `::someCallable` or
* `::someReceiver::someCallable`.
*/
private open inner class CallableReferenceHelper(
protected val callableReferenceExpr: IrCallableReference<out IrSymbol>,
locId: Label<DbLocation>,
ids: GeneratedClassLabels
) : GeneratedClassHelper(locId, ids) {
// Only one of the receivers can be non-null, but we defensively handle the case when both
// are null anyway
private val dispatchReceiverInfo =
makeReceiverInfo(callableReferenceExpr.dispatchReceiver, 0)
private val extensionReceiverInfo =
makeReceiverInfo(
callableReferenceExpr.extensionReceiver,
if (dispatchReceiverInfo == null) 0 else 1
)
fun extractReceiverField() {
val firstAssignmentStmtIdx = 1
if (dispatchReceiverInfo != null) {
extractField(
dispatchReceiverInfo.field,
"<dispatchReceiver>",
dispatchReceiverInfo.type,
classId,
locId,
DescriptorVisibilities.PRIVATE,
callableReferenceExpr,
isExternalDeclaration = false,
isFinal = true,
isStatic = false
)
extractParameterToFieldAssignmentInConstructor(
"<dispatchReceiver>",
dispatchReceiverInfo.type,
dispatchReceiverInfo.field,
0 + dispatchReceiverInfo.indexOffset,
firstAssignmentStmtIdx + dispatchReceiverInfo.indexOffset
)
}
if (extensionReceiverInfo != null) {
extractField(
extensionReceiverInfo.field,
"<extensionReceiver>",
extensionReceiverInfo.type,
classId,
locId,
DescriptorVisibilities.PRIVATE,
callableReferenceExpr,
isExternalDeclaration = false,
isFinal = true,
isStatic = false
)
extractParameterToFieldAssignmentInConstructor(
"<extensionReceiver>",
extensionReceiverInfo.type,
extensionReceiverInfo.field,
0 + extensionReceiverInfo.indexOffset,
firstAssignmentStmtIdx + extensionReceiverInfo.indexOffset
)
}
}
protected fun writeVariableAccessInFunctionBody(
pType: TypeResults,
idx: Int,
variable: Label<out DbVariable>,
parent: Label<out DbExprparent>,
callable: Label<out DbCallable>,
stmt: Label<out DbStmt>
): Label<DbVaraccess> {
val pId = tw.getFreshIdLabel<DbVaraccess>()
tw.writeExprs_varaccess(pId, pType.javaResult.id, parent, idx)
tw.writeExprsKotlinType(pId, pType.kotlinResult.id)
tw.writeVariableBinding(pId, variable)
extractExprContext(pId, locId, callable, stmt)
return pId
}
private fun writeFieldAccessInFunctionBody(
pType: IrType,
idx: Int,
variable: Label<out DbField>,
parent: Label<out DbExprparent>,
callable: Label<out DbCallable>,
stmt: Label<out DbStmt>
) {
val accessId =
writeVariableAccessInFunctionBody(
useType(pType),
idx,
variable,
parent,
callable,
stmt
)
writeThisAccess(accessId, callable, stmt)
}
protected fun writeThisAccess(
parent: Label<out DbExprparent>,
callable: Label<out DbCallable>,
stmt: Label<out DbStmt>
) {
extractThisAccess(ids.type, callable, parent, -1, stmt, locId)
}
fun extractFieldWriteOfReflectionTarget(
labels: FunctionLabels, // labels of the containing function
target: IrFieldSymbol, // the target field being accessed)
) {
val fieldType = useType(target.owner.type)
extractExpressionStmt(locId, labels.blockId, 0, labels.methodId).also { exprStmtId ->
extractAssignExpr(
target.owner.type,
locId,
exprStmtId,
0,
labels.methodId,
exprStmtId
)
.also { assignExprId ->
extractFieldAccess(fieldType, assignExprId, exprStmtId, labels, target)
val p = labels.parameters.first()
writeVariableAccessInFunctionBody(
p.second,
1,
p.first,
assignExprId,
labels.methodId,
exprStmtId
)
}
}
}
fun extractFieldReturnOfReflectionTarget(
labels: FunctionLabels, // labels of the containing function
target: IrFieldSymbol, // the target field being accessed
) {
val retId = tw.getFreshIdLabel<DbReturnstmt>()
tw.writeStmts_returnstmt(retId, labels.blockId, 0, labels.methodId)
tw.writeHasLocation(retId, locId)
val fieldType = useType(target.owner.type)
extractFieldAccess(fieldType, retId, retId, labels, target)
}
private fun extractFieldAccess(
fieldType: TypeResults,
parent: Label<out DbExprparent>,
stmt: Label<out DbStmt>,
labels: FunctionLabels,
target: IrFieldSymbol
) {
val accessId = tw.getFreshIdLabel<DbVaraccess>()
tw.writeExprs_varaccess(accessId, fieldType.javaResult.id, parent, 0)
tw.writeExprsKotlinType(accessId, fieldType.kotlinResult.id)
extractExprContext(accessId, locId, labels.methodId, stmt)
val fieldId = useField(target.owner)
tw.writeVariableBinding(accessId, fieldId)
if (dispatchReceiverInfo != null) {
writeFieldAccessInFunctionBody(
dispatchReceiverInfo.type,
-1,
dispatchReceiverInfo.field,
accessId,
labels.methodId,
stmt
)
}
}
/**
* Extracts a call to `target` inside the function identified by `labels`. Special
* parameters (`dispatch` and `extension`) are also handled.
*
* Examples are:
* ```
* this.<dispatchReceiver>.fn(this.<extensionReceiver>, param1, param2, param3, ...)
* param1.fn(this.<extensionReceiver>, param2, ...)
* param1.fn(param2, param3, ...)
* fn(this.<extensionReceiver>, param1, param2, ...)
* fn(param1, param2, ...)
* new MyType(param1, param2, ...)
* ```
*
* The parameters with default argument values cover special cases:
* - dispatchReceiverIdx is usually -1, except if a constructor is referenced
* - big arity function references need to call `invoke` with arguments received in an
* object array: `fn(param1[0] as T0, param1[1] as T1, ...)`
*/
fun extractCallToReflectionTarget(
labels: FunctionLabels, // labels of the containing function
target: IrFunctionSymbol, // the target function/constructor being called
returnType:
IrType, // the return type of the called function. Note that
// `target.owner.returnType` and `returnType` doesn't match for generic
// functions
expressionTypeArgs: List<IrType>, // type arguments of the extracted expression
classTypeArgsIncludingOuterClasses:
List<
IrTypeArgument
>?, // type arguments of the class containing the callable reference
dispatchReceiverIdx: Int =
-1, // dispatch receiver index: -1 in case of functions, -2 for constructors
bigArityParameterTypes: List<IrType>? =
null // parameter types used for the cast expressions in a big arity `invoke`
// invocation. null if not a big arity invocation.
) {
// Return statement of generated function:
val retId = tw.getFreshIdLabel<DbReturnstmt>()
tw.writeStmts_returnstmt(retId, labels.blockId, 0, labels.methodId)
tw.writeHasLocation(retId, locId)
// Call to target function:
val callType = useType(returnType)
val callId: Label<out DbExpr> =
if (target is IrConstructorSymbol) {
val callId = tw.getFreshIdLabel<DbNewexpr>()
tw.writeExprs_newexpr(callId, callType.javaResult.id, retId, 0)
tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
extractConstructorTypeAccess(
returnType,
callType,
target,
locId,
callId,
-3,
labels.methodId,
retId
)
callId
} else {
val callId = tw.getFreshIdLabel<DbMethodaccess>()
tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0)
tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
extractTypeArguments(
expressionTypeArgs,
locId,
callId,
labels.methodId,
retId,
-2,
true
)
callId
}
extractExprContext(callId, locId, labels.methodId, retId)
val callableId =
useFunction<DbCallable>(
target.owner.realOverrideTarget,
classTypeArgsIncludingOuterClasses
)
if (callableId == null) {
logger.error("Cannot get ID for reflection target")
} else {
tw.writeCallableBinding(callId.cast<DbCaller>(), callableId)
}
val useFirstArgAsDispatch: Boolean
if (dispatchReceiverInfo != null) {
writeFieldAccessInFunctionBody(
dispatchReceiverInfo.type,
dispatchReceiverIdx,
dispatchReceiverInfo.field,
callId,
labels.methodId,
retId
)
useFirstArgAsDispatch = false
} else {
if (target.owner.isLocalFunction()) {
val ids = getLocallyVisibleFunctionLabels(target.owner)
extractNewExprForLocalFunction(ids, callId, locId, labels.methodId, retId)
useFirstArgAsDispatch = false
} else {
useFirstArgAsDispatch = target.owner.dispatchReceiverParameter != null
if (isStaticFunction(target.owner)) {
extractStaticTypeAccessQualifier(
target.owner,
callId,
locId,
labels.methodId,
retId
)
}
}
}
val extensionIdxOffset: Int
if (extensionReceiverInfo != null) {
writeFieldAccessInFunctionBody(
extensionReceiverInfo.type,
0,
extensionReceiverInfo.field,
callId,
labels.methodId,
retId
)
extensionIdxOffset = 1
} else {
extensionIdxOffset = 0
}
if (bigArityParameterTypes != null) {
// In case we're extracting a big arity function reference:
addArgumentsToInvocationInInvokeNBody(
bigArityParameterTypes,
labels,
retId,
callId,
locId,
extensionIdxOffset,
useFirstArgAsDispatch,
dispatchReceiverIdx
)
} else {
val dispatchIdxOffset = if (useFirstArgAsDispatch) 1 else 0
for ((pIdx, p) in labels.parameters.withIndex()) {
val childIdx =
if (pIdx == 0 && useFirstArgAsDispatch) {
dispatchReceiverIdx
} else {
pIdx + extensionIdxOffset - dispatchIdxOffset
}
writeVariableAccessInFunctionBody(
p.second,
childIdx,
p.first,
callId,
labels.methodId,
retId
)
}
}
}
fun extractConstructorArguments(
callable: Label<out DbCallable>,
idCtorRef: Label<out DbClassinstancexpr>,
enclosingStmt: Label<out DbStmt>
) {
if (dispatchReceiverInfo != null) {
extractExpressionExpr(
dispatchReceiverInfo.receiver,
callable,
idCtorRef,
0 + dispatchReceiverInfo.indexOffset,
enclosingStmt
)
}
if (extensionReceiverInfo != null) {
extractExpressionExpr(
extensionReceiverInfo.receiver,
callable,
idCtorRef,
0 + extensionReceiverInfo.indexOffset,
enclosingStmt
)
}
}
}
private inner class PropertyReferenceHelper(
callableReferenceExpr: IrCallableReference<out IrSymbol>,
locId: Label<DbLocation>,
ids: GeneratedClassLabels
) : CallableReferenceHelper(callableReferenceExpr, locId, ids) {
fun extractPropertyReferenceInvoke(
getId: Label<DbMethod>,
getterParameterTypes: List<IrType>,
getterReturnType: IrType
) {
// Extracting this method is not (strictly) needed for interface member implementation.
// `[Mutable]PropertyReferenceX` already implements it, but its signature doesn't match
// the
// generic one, because it's a raw method implementation. Also, by adding the `invoke`
// explicitly,
// we have better data flow analysis support.
val invokeLabels =
addFunctionManual(
tw.getFreshIdLabel(),
OperatorNameConventions.INVOKE.asString(),
getterParameterTypes,
getterReturnType,
classId,
locId
)
// return this.get(a0, a1, ...)
val retId = tw.getFreshIdLabel<DbReturnstmt>()
tw.writeStmts_returnstmt(retId, invokeLabels.blockId, 0, invokeLabels.methodId)
tw.writeHasLocation(retId, locId)
// Call to target function:
val callType = useType(getterReturnType)
val callId = tw.getFreshIdLabel<DbMethodaccess>()
tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0)
tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
extractExprContext(callId, locId, invokeLabels.methodId, retId)
tw.writeCallableBinding(callId, getId)
this.writeThisAccess(callId, invokeLabels.methodId, retId)
for ((pIdx, p) in invokeLabels.parameters.withIndex()) {
this.writeVariableAccessInFunctionBody(
p.second,
pIdx,
p.first,
callId,
invokeLabels.methodId,
retId
)
}
}
}
private val propertyRefType by lazy {
referenceExternalClass("kotlin.jvm.internal.PropertyReference")?.typeWith()
}
private fun extractPropertyReference(
exprKind: String,
propertyReferenceExpr: IrCallableReference<out IrSymbol>,
getter: IrSimpleFunctionSymbol?,
setter: IrSimpleFunctionSymbol?,
backingField: IrFieldSymbol?,
parent: StmtExprParent,
callable: Label<out DbCallable>
) {
with(exprKind, propertyReferenceExpr) {
/*
* Extract generated class:
* ```
* class C : kotlin.jvm.internal.PropertyReference, kotlin.reflect.KMutableProperty0<R> {
* private dispatchReceiver: TD
* constructor(dispatchReceiver: TD) {
* super()
* this.dispatchReceiver = dispatchReceiver
* }
*
* override fun get(): R { return this.dispatchReceiver.FN1() }
*
* override fun set(a0: R): Unit { return this.dispatchReceiver.FN2(a0) }
*
* override fun invoke(): R { return this.get() }
* }
* ```
*
* Variations:
* - KProperty vs KMutableProperty
* - KProperty0<> vs KProperty1<,>
* - no receiver vs dispatchReceiver vs extensionReceiver
**/
val kPropertyType = propertyReferenceExpr.type
if (kPropertyType !is IrSimpleType) {
logger.errorElement(
"Unexpected: property reference with non simple type. ${kPropertyType.classFqName?.asString()}",
propertyReferenceExpr
)
return
}
val kPropertyClass = kPropertyType.classOrNull
if (kPropertyClass == null) {
logger.errorElement(
"Cannot find class for kPropertyType. ${kPropertyType.classFqName?.asString()}",
propertyReferenceExpr
)
return
}
val parameterTypes: List<IrType>? =
kPropertyType.arguments
.map {
if (it is IrType) {
it
} else {
logger.errorElement(
"Unexpected: Non-IrType (${it.javaClass}) property reference parameter.",
propertyReferenceExpr
)
null
}
}
.requireNoNullsOrNull()
if (parameterTypes == null) {
logger.errorElement(
"Unexpected: One or more non-IrType property reference parameters.",
propertyReferenceExpr
)
return
}
val locId = tw.getLocation(propertyReferenceExpr)
val javaResult = TypeResult(tw.getFreshIdLabel<DbClassorinterface>(), "", "")
val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
val ids =
GeneratedClassLabels(
TypeResults(javaResult, kotlinResult),
constructor = tw.getFreshIdLabel(),
constructorBlock = tw.getFreshIdLabel()
)
val declarationParent =
peekDeclStackAsDeclarationParent(propertyReferenceExpr) ?: return
// The base class could be `Any`. `PropertyReference` is used to keep symmetry with
// function references.
val baseClass = propertyRefType ?: pluginContext.irBuiltIns.anyType
val classId =
extractGeneratedClass(
ids,
listOf(baseClass, kPropertyType),
locId,
propertyReferenceExpr,
declarationParent
)
val helper = PropertyReferenceHelper(propertyReferenceExpr, locId, ids)
helper.extractReceiverField()
val classTypeArguments =
(propertyReferenceExpr.dispatchReceiver?.type as? IrSimpleType)?.arguments
?: if (
(getter?.owner?.dispatchReceiverParameter
?: setter?.owner?.dispatchReceiverParameter) != null
) {
(kPropertyType.arguments.first() as? IrSimpleType)?.arguments
} else {
null
}
val expressionTypeArguments =
(0 until propertyReferenceExpr.typeArgumentsCount).mapNotNull {
propertyReferenceExpr.getTypeArgument(it)
}
val idPropertyRef = tw.getFreshIdLabel<DbPropertyref>()
val getterParameterTypes = parameterTypes.dropLast(1)
val getterReturnType = parameterTypes.last()
if (getter != null) {
val getterCallableId =
useFunction<DbCallable>(getter.owner.realOverrideTarget, classTypeArguments)
if (getterCallableId == null) {
logger.errorElement("Cannot get ID for getter", propertyReferenceExpr)
} else {
val getLabels =
addFunctionManual(
tw.getFreshIdLabel(),
OperatorNameConventions.GET.asString(),
getterParameterTypes,
getterReturnType,
classId,
locId
)
helper.extractCallToReflectionTarget(
getLabels,
getter,
getterReturnType,
expressionTypeArguments,
classTypeArguments
)
tw.writePropertyRefGetBinding(idPropertyRef, getterCallableId)
helper.extractPropertyReferenceInvoke(
getLabels.methodId,
getterParameterTypes,
getterReturnType
)
}
} else {
// Property without a getter.
if (backingField == null) {
logger.errorElement(
"Expected to find getter or backing field for property reference.",
propertyReferenceExpr
)
return
}
val getLabels =
addFunctionManual(
tw.getFreshIdLabel(),
OperatorNameConventions.GET.asString(),
getterParameterTypes,
getterReturnType,
classId,
locId
)
val fieldId = useField(backingField.owner)
helper.extractFieldReturnOfReflectionTarget(getLabels, backingField)
tw.writePropertyRefFieldBinding(idPropertyRef, fieldId)
helper.extractPropertyReferenceInvoke(
getLabels.methodId,
getterParameterTypes,
getterReturnType
)
}
if (setter != null) {
val setterCallableId =
useFunction<DbCallable>(setter.owner.realOverrideTarget, classTypeArguments)
if (setterCallableId == null) {
logger.errorElement("Cannot get ID for setter", propertyReferenceExpr)
} else {
val setLabels =
addFunctionManual(
tw.getFreshIdLabel(),
OperatorNameConventions.SET.asString(),
parameterTypes,
pluginContext.irBuiltIns.unitType,
classId,
locId
)
helper.extractCallToReflectionTarget(
setLabels,
setter,
pluginContext.irBuiltIns.unitType,
expressionTypeArguments,
classTypeArguments
)
tw.writePropertyRefSetBinding(idPropertyRef, setterCallableId)
}
} else {
if (backingField != null && !backingField.owner.isFinal) {
val setLabels =
addFunctionManual(
tw.getFreshIdLabel(),
OperatorNameConventions.SET.asString(),
parameterTypes,
pluginContext.irBuiltIns.unitType,
classId,
locId
)
val fieldId = useField(backingField.owner)
helper.extractFieldWriteOfReflectionTarget(setLabels, backingField)
tw.writePropertyRefFieldBinding(idPropertyRef, fieldId)
}
}
// Add constructor (property ref) call:
val exprParent = parent.expr(propertyReferenceExpr, callable)
tw.writeExprs_propertyref(
idPropertyRef,
ids.type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(idPropertyRef, ids.type.kotlinResult.id)
extractExprContext(idPropertyRef, locId, callable, exprParent.enclosingStmt)
tw.writeCallableBinding(idPropertyRef, ids.constructor)
extractTypeAccessRecursive(
kPropertyType,
locId,
idPropertyRef,
-3,
callable,
exprParent.enclosingStmt
)
helper.extractConstructorArguments(callable, idPropertyRef, exprParent.enclosingStmt)
tw.writeIsAnonymClass(classId, idPropertyRef)
}
}
private val functionRefType by lazy {
referenceExternalClass("kotlin.jvm.internal.FunctionReference")?.typeWith()
}
private fun extractFunctionReference(
functionReferenceExpr: IrFunctionReference,
parent: StmtExprParent,
callable: Label<out DbCallable>
) {
with("function reference", functionReferenceExpr) {
val target =
if (functionReferenceExpr.origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE)
// For an adaptation (e.g. to adjust the number or type of arguments or results),
// the symbol field points at the adapter while `.reflectionTarget` points at the
// source-level target.
functionReferenceExpr.symbol
else
// TODO: Consider whether we could always target the symbol
functionReferenceExpr.reflectionTarget
?: run {
logger.warnElement(
"Expected to find reflection target for function reference. Using underlying symbol instead.",
functionReferenceExpr
)
functionReferenceExpr.symbol
}
/*
* Extract generated class:
* ```
* class C : kotlin.jvm.internal.FunctionReference, kotlin.FunctionI<T0,T1, ... TI, R> {
* private dispatchReceiver: TD
* private extensionReceiver: TE
* constructor(dispatchReceiver: TD, extensionReceiver: TE) {
* super()
* this.dispatchReceiver = dispatchReceiver
* this.extensionReceiver = extensionReceiver
* }
* fun invoke(a0:T0, a1:T1, ... aI: TI): R { return this.dispatchReceiver.FN(a0,a1,...,aI) } OR
* fun invoke( a1:T1, ... aI: TI): R { return this.dispatchReceiver.FN(this.dispatchReceiver,a1,...,aI) } OR
* fun invoke(a0:T0, a1:T1, ... aI: TI): R { return Ctor(a0,a1,...,aI) }
* }
* ```
* or in case of big arity lambdas ????
* ```
* class C : kotlin.jvm.internal.FunctionReference, kotlin.FunctionN<R> {
* private receiver: TD
* constructor(receiver: TD) { super(); this.receiver = receiver; }
* fun invoke(vararg args: Any?): R {
* return this.receiver.FN(args[0] as T0, args[1] as T1, ..., args[I] as TI)
* }
* }
* ```
**/
if (
functionReferenceExpr.dispatchReceiver != null &&
functionReferenceExpr.extensionReceiver != null
) {
logger.errorElement(
"Unexpected: dispatchReceiver and extensionReceiver are both non-null",
functionReferenceExpr
)
return
}
if (
target.owner.dispatchReceiverParameter != null &&
target.owner.extensionReceiverParameter != null
) {
logger.errorElement(
"Unexpected: dispatch and extension parameters are both non-null",
functionReferenceExpr
)
return
}
val type = functionReferenceExpr.type
if (type !is IrSimpleType) {
logger.errorElement(
"Unexpected: function reference with non simple type. ${type.classFqName?.asString()}",
functionReferenceExpr
)
return
}
val parameterTypes: List<IrType>? =
type.arguments
.map {
if (it is IrType) {
it
} else {
logger.errorElement(
"Unexpected: Non-IrType (${it.javaClass}) function reference parameter.",
functionReferenceExpr
)
null
}
}
.requireNoNullsOrNull()
if (parameterTypes == null) {
logger.errorElement(
"Unexpected: One or more non-IrType function reference parameters.",
functionReferenceExpr
)
return
}
val dispatchReceiverIdx: Int
val expressionTypeArguments: List<IrType>
val classTypeArguments: List<IrTypeArgument>?
if (target is IrConstructorSymbol) {
// In case a constructor is referenced, the return type of the `KFunctionX<,,,>` is
// the type if the constructed type.
classTypeArguments = (type.arguments.last() as? IrSimpleType)?.arguments
expressionTypeArguments = listOf(parameterTypes.last())
dispatchReceiverIdx = -2
} else {
classTypeArguments =
(functionReferenceExpr.dispatchReceiver?.type as? IrSimpleType)?.arguments
?: if (target.owner.dispatchReceiverParameter != null) {
(type.arguments.first() as? IrSimpleType)?.arguments
} else {
null
}
expressionTypeArguments =
(0 until functionReferenceExpr.typeArgumentsCount).mapNotNull {
functionReferenceExpr.getTypeArgument(it)
}
dispatchReceiverIdx = -1
}
val locId = tw.getLocation(functionReferenceExpr)
val javaResult = TypeResult(tw.getFreshIdLabel<DbClassorinterface>(), "", "")
val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
val ids =
LocallyVisibleFunctionLabels(
TypeResults(javaResult, kotlinResult),
constructor = tw.getFreshIdLabel(),
function = tw.getFreshIdLabel(),
constructorBlock = tw.getFreshIdLabel()
)
// Add constructor (member ref) call:
val exprParent = parent.expr(functionReferenceExpr, callable)
val idMemberRef = tw.getFreshIdLabel<DbMemberref>()
tw.writeExprs_memberref(
idMemberRef,
ids.type.javaResult.id,
exprParent.parent,
exprParent.idx
)
tw.writeExprsKotlinType(idMemberRef, ids.type.kotlinResult.id)
extractExprContext(idMemberRef, locId, callable, exprParent.enclosingStmt)
tw.writeCallableBinding(idMemberRef, ids.constructor)
val targetCallableId =
useFunction<DbCallable>(target.owner.realOverrideTarget, classTypeArguments)
if (targetCallableId == null) {
logger.errorElement(
"Cannot get ID for function reference callable",
functionReferenceExpr
)
} else {
tw.writeMemberRefBinding(idMemberRef, targetCallableId)
}
val helper = CallableReferenceHelper(functionReferenceExpr, locId, ids)
val fnInterfaceType = getFunctionalInterfaceTypeWithTypeArgs(type.arguments)
if (fnInterfaceType == null) {
logger.warnElement(
"Cannot find functional interface type for function reference",
functionReferenceExpr
)
} else {
val declarationParent =
peekDeclStackAsDeclarationParent(functionReferenceExpr) ?: return
// `FunctionReference` base class is required, because that's implementing
// `KFunction`.
val baseClass = functionRefType ?: pluginContext.irBuiltIns.anyType
val classId =
extractGeneratedClass(
ids,
listOf(baseClass, fnInterfaceType),
locId,
functionReferenceExpr,
declarationParent,
null,
{ it.valueParameters.size == 1 }
) {
// The argument to FunctionReference's constructor is the function arity.
extractConstantInteger(
type.arguments.size - 1,
locId,
it,
0,
ids.constructor,
it
)
}
helper.extractReceiverField()
val isBigArity = type.arguments.size > BuiltInFunctionArity.BIG_ARITY
val funLabels =
if (isBigArity) {
addFunctionNInvoke(ids.function, parameterTypes.last(), classId, locId)
} else {
addFunctionInvoke(
ids.function,
parameterTypes.dropLast(1),
parameterTypes.last(),
classId,
locId
)
}
helper.extractCallToReflectionTarget(
funLabels,
target,
parameterTypes.last(),
expressionTypeArguments,
classTypeArguments,
dispatchReceiverIdx,
if (isBigArity) parameterTypes.dropLast(1) else null
)
val typeAccessArguments =
if (isBigArity) listOf(parameterTypes.last()) else parameterTypes
if (target is IrConstructorSymbol) {
val returnType = typeAccessArguments.last()
val typeAccessId =
extractTypeAccess(
useType(fnInterfaceType, TypeContext.OTHER),
locId,
idMemberRef,
-3,
callable,
exprParent.enclosingStmt
)
typeAccessArguments.dropLast(1).forEachIndexed { argIdx, arg ->
extractTypeAccessRecursive(
arg,
locId,
typeAccessId,
argIdx,
callable,
exprParent.enclosingStmt,
TypeContext.GENERIC_ARGUMENT
)
}
extractConstructorTypeAccess(
returnType,
useType(returnType),
target,
locId,
typeAccessId,
typeAccessArguments.count() - 1,
callable,
exprParent.enclosingStmt
)
} else {
extractTypeAccessRecursive(
fnInterfaceType,
locId,
idMemberRef,
-3,
callable,
exprParent.enclosingStmt
)
}
helper.extractConstructorArguments(callable, idMemberRef, exprParent.enclosingStmt)
tw.writeIsAnonymClass(classId, idMemberRef)
}
}
}
private fun getFunctionalInterfaceType(functionNTypeArguments: List<IrType>) =
getFunctionalInterfaceTypeWithTypeArgs(
functionNTypeArguments.map { makeTypeProjection(it, Variance.INVARIANT) }
)
private fun getFunctionalInterfaceTypeWithTypeArgs(
functionNTypeArguments: List<IrTypeArgument>
) =
if (functionNTypeArguments.size > BuiltInFunctionArity.BIG_ARITY)
referenceExternalClass("kotlin.jvm.functions.FunctionN")
?.symbol
?.typeWithArguments(listOf(functionNTypeArguments.last()))
else
functionN(pluginContext)(functionNTypeArguments.size - 1)
.symbol
.typeWithArguments(functionNTypeArguments)
private data class FunctionLabels(
val methodId: Label<DbMethod>,
val blockId: Label<DbBlock>,
val parameters: List<Pair<Label<DbParam>, TypeResults>>
)
/**
* Adds a function `invoke(a: Any[])` with the specified return type to the class identified by
* `parentId`.
*/
private fun addFunctionNInvoke(
methodId: Label<DbMethod>,
returnType: IrType,
parentId: Label<out DbReftype>,
locId: Label<DbLocation>
): FunctionLabels {
return addFunctionInvoke(
methodId,
listOf(pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)),
returnType,
parentId,
locId
)
}
/**
* Adds a function named `invoke` with the specified parameter types and return type to the
* class identified by `parentId`.
*/
private fun addFunctionInvoke(
methodId: Label<DbMethod>,
parameterTypes: List<IrType>,
returnType: IrType,
parentId: Label<out DbReftype>,
locId: Label<DbLocation>
): FunctionLabels {
return addFunctionManual(
methodId,
OperatorNameConventions.INVOKE.asString(),
parameterTypes,
returnType,
parentId,
locId
)
}
/**
* Extracts a function with the given name, parameter types, return type, containing type, and
* location.
*/
private fun addFunctionManual(
methodId: Label<DbMethod>,
name: String,
parameterTypes: List<IrType>,
returnType: IrType,
parentId: Label<out DbReftype>,
locId: Label<DbLocation>
): FunctionLabels {
val parameters =
parameterTypes.mapIndexed { idx, p ->
val paramId = tw.getFreshIdLabel<DbParam>()
val paramType =
extractValueParameter(
paramId,
p,
"a$idx",
locId,
methodId,
idx,
paramId,
syntheticParameterNames = false,
isVararg = false,
isNoinline = false,
isCrossinline = false
)
Pair(paramId, paramType)
}
val paramsSignature =
parameters.joinToString(separator = ",", prefix = "(", postfix = ")") {
signatureOrWarn(it.second.javaResult, declarationStack.tryPeek()?.first)
}
val rt = useType(returnType, TypeContext.RETURN)
tw.writeMethods(
methodId,
name,
"$name$paramsSignature",
rt.javaResult.id,
parentId,
methodId
)
tw.writeMethodsKotlinType(methodId, rt.kotlinResult.id)
tw.writeHasLocation(methodId, locId)
addModifiers(methodId, "public")
addModifiers(methodId, "override")
return FunctionLabels(methodId, extractBlockBody(methodId, locId), parameters)
}
/*
* This function generates an implementation for `fun kotlin.FunctionN<R>.invoke(vararg args: Any?): R`
*
* The following body is added:
* ```
* fun invoke(vararg a0: Any?): R {
* return invoke(a0[0] as T0, a0[1] as T1, ..., a0[I] as TI)
* }
* ```
* */
private fun implementFunctionNInvoke(
lambda: IrFunction,
ids: LocallyVisibleFunctionLabels,
locId: Label<DbLocation>,
parameters: List<IrValueParameter>
) {
val funLabels =
addFunctionNInvoke(
tw.getFreshIdLabel(),
lambda.returnType,
ids.type.javaResult.id.cast<DbReftype>(),
locId
)
// Return
val retId = tw.getFreshIdLabel<DbReturnstmt>()
tw.writeStmts_returnstmt(retId, funLabels.blockId, 0, funLabels.methodId)
tw.writeHasLocation(retId, locId)
// Call to original `invoke`:
val callId = tw.getFreshIdLabel<DbMethodaccess>()
val callType = useType(lambda.returnType)
tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0)
tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
extractExprContext(callId, locId, funLabels.methodId, retId)
val calledMethodId = useFunction<DbMethod>(lambda)
if (calledMethodId == null) {
logger.errorElement("Cannot get ID for called lambda", lambda)
} else {
tw.writeCallableBinding(callId, calledMethodId)
}
// this access
extractThisAccess(ids.type, funLabels.methodId, callId, -1, retId, locId)
addArgumentsToInvocationInInvokeNBody(
parameters.map { it.type },
funLabels,
retId,
callId,
locId
)
}
/**
* Adds the arguments to the method call inside `invoke(a0: Any[])`. Each argument is an array
* access with a cast:
* ```
* fun invoke(a0: Any[]) : T {
* return fn(a0[0] as T0, a0[1] as T1, ...)
* }
* ```
*/
private fun addArgumentsToInvocationInInvokeNBody(
parameterTypes: List<IrType>, // list of parameter types
funLabels: FunctionLabels, // already generated labels for the function definition
enclosingStmtId: Label<out DbStmt>, // label for the enclosing statement (return)
exprParentId: Label<out DbExprparent>, // label for the expression parent (call)
locId: Label<DbLocation>, // label for the location of all generated items
firstArgumentOffset: Int =
0, // 0 or 1, the index used for the first argument. 1 in case an extension parameter is
// already accessed at index 0
useFirstArgAsDispatch: Boolean =
false, // true if the first argument should be used as the dispatch receiver
dispatchReceiverIdx: Int =
-1 // index of the dispatch receiver. -1 in case of functions, -2 in case of
// constructors
) {
val argsParamType =
pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)
val argsType = useType(argsParamType)
val anyNType = useType(pluginContext.irBuiltIns.anyNType)
val dispatchIdxOffset = if (useFirstArgAsDispatch) 1 else 0
for ((pIdx, pType) in parameterTypes.withIndex()) {
// `a0[i] as Ti` is generated below for each parameter
val childIdx =
if (pIdx == 0 && useFirstArgAsDispatch) {
dispatchReceiverIdx
} else {
pIdx + firstArgumentOffset - dispatchIdxOffset
}
// cast: `(Ti)a0[i]`
val castId = tw.getFreshIdLabel<DbCastexpr>()
val type = useType(pType)
tw.writeExprs_castexpr(castId, type.javaResult.id, exprParentId, childIdx)
tw.writeExprsKotlinType(castId, type.kotlinResult.id)
extractExprContext(castId, locId, funLabels.methodId, enclosingStmtId)
// type access `Ti`
extractTypeAccessRecursive(pType, locId, castId, 0, funLabels.methodId, enclosingStmtId)
// element access: `a0[i]`
val arrayAccessId = tw.getFreshIdLabel<DbArrayaccess>()
tw.writeExprs_arrayaccess(arrayAccessId, anyNType.javaResult.id, castId, 1)
tw.writeExprsKotlinType(arrayAccessId, anyNType.kotlinResult.id)
extractExprContext(arrayAccessId, locId, funLabels.methodId, enclosingStmtId)
// parameter access: `a0`
val argsAccessId = tw.getFreshIdLabel<DbVaraccess>()
tw.writeExprs_varaccess(argsAccessId, argsType.javaResult.id, arrayAccessId, 0)
tw.writeExprsKotlinType(argsAccessId, argsType.kotlinResult.id)
extractExprContext(argsAccessId, locId, funLabels.methodId, enclosingStmtId)
tw.writeVariableBinding(argsAccessId, funLabels.parameters.first().first)
// index access: `i`
extractConstantInteger(
pIdx,
locId,
arrayAccessId,
1,
funLabels.methodId,
enclosingStmtId
)
}
}
private fun extractVarargElement(
e: IrVarargElement,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>
) {
with("vararg element", e) {
val argExpr =
when (e) {
is IrExpression -> e
is IrSpreadElement -> e.expression
else -> {
logger.errorElement("Unrecognised IrVarargElement: " + e.javaClass, e)
null
}
}
argExpr?.let { extractExpressionExpr(it, callable, parent, idx, enclosingStmt) }
}
}
/**
* Extracts a type access expression and its generic arguments for a constructor call. It only
* extracts type arguments relating to the constructed type, not the constructor itself, which
* makes a difference in case of nested generics.
*/
private fun extractConstructorTypeAccess(
irType: IrType,
type: TypeResults,
target: IrFunctionSymbol,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) {
val typeAccessId =
extractTypeAccess(type, locId, parent, idx, enclosingCallable, enclosingStmt)
if (irType is IrSimpleType) {
extractTypeArguments(
irType.arguments
.take(target.owner.parentAsClass.typeParameters.size)
.filterIsInstance<IrType>(),
locId,
typeAccessId,
enclosingCallable,
enclosingStmt
)
}
}
/**
* Extracts a single wildcard type access expression with no enclosing callable and statement.
*/
private fun extractWildcardTypeAccess(
type: TypeResultsWithoutSignatures,
location: Label<out DbLocation>,
parent: Label<out DbExprparent>,
idx: Int
): Label<out DbExpr> {
val id = tw.getFreshIdLabel<DbWildcardtypeaccess>()
tw.writeExprs_wildcardtypeaccess(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
tw.writeHasLocation(id, location)
return id
}
/** Extracts a single type access expression with no enclosing callable and statement. */
private fun extractTypeAccess(
type: TypeResults,
location: Label<out DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
overrideId: Label<out DbExpr>? = null
): Label<out DbExpr> {
// TODO: elementForLocation allows us to give some sort of
// location, but a proper location for the type access will
// require upstream changes
val id = exprIdOrFresh<DbUnannotatedtypeaccess>(overrideId)
tw.writeExprs_unannotatedtypeaccess(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
tw.writeHasLocation(id, location)
return id
}
/** Extracts a single type access expression with enclosing callable and statement. */
private fun extractTypeAccess(
type: TypeResults,
location: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
overrideId: Label<out DbExpr>? = null
): Label<out DbExpr> {
val id = extractTypeAccess(type, location, parent, idx, overrideId = overrideId)
if (enclosingCallable != null) {
tw.writeCallableEnclosingExpr(id, enclosingCallable)
}
if (enclosingStmt != null) {
tw.writeStatementEnclosingExpr(id, enclosingStmt)
}
return id
}
/**
* Extracts a type argument type access, introducing a wildcard type access if appropriate, or
* directly calling `extractTypeAccessRecursive` if the argument is invariant. No enclosing
* callable and statement is extracted, this is useful for type access extraction in field
* declarations.
*/
private fun extractWildcardTypeAccessRecursive(
t: IrTypeArgument,
location: Label<out DbLocation>,
parent: Label<out DbExprparent>,
idx: Int
) {
val typeLabels by lazy {
TypeResultsWithoutSignatures(
getTypeArgumentLabel(t),
TypeResultWithoutSignature(fakeKotlinType(), Unit, "TODO")
)
}
when (t) {
is IrStarProjection -> extractWildcardTypeAccess(typeLabels, location, parent, idx)
is IrTypeProjection ->
when (t.variance) {
Variance.INVARIANT ->
extractTypeAccessRecursive(
t.type,
location,
parent,
idx,
TypeContext.GENERIC_ARGUMENT
)
else -> {
val wildcardLabel =
extractWildcardTypeAccess(typeLabels, location, parent, idx)
// Mimic a Java extractor oddity, that it uses the child index to indicate
// what kind of wildcard this is
val boundChildIdx = if (t.variance == Variance.OUT_VARIANCE) 0 else 1
extractTypeAccessRecursive(
t.type,
location,
wildcardLabel,
boundChildIdx,
TypeContext.GENERIC_ARGUMENT
)
}
}
}
}
/**
* Extracts a type access expression and its child type access expressions in case of a generic
* type. Nested generics are also handled. No enclosing callable and statement is extracted,
* this is useful for type access extraction in field declarations.
*/
private fun extractTypeAccessRecursive(
t: IrType,
location: Label<out DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
typeContext: TypeContext = TypeContext.OTHER
): Label<out DbExpr> {
val typeAccessId = extractTypeAccess(useType(t, typeContext), location, parent, idx)
if (t is IrSimpleType) {
// From 1.9, the list might change when we call erase,
// so we make a copy that it is safe to iterate over.
val argumentsCopy = t.arguments.toList()
argumentsCopy.forEachIndexed { argIdx, arg ->
extractWildcardTypeAccessRecursive(arg, location, typeAccessId, argIdx)
}
}
return typeAccessId
}
/**
* Extracts a type access expression and its child type access expressions in case of a generic
* type. Nested generics are also handled.
*/
private fun extractTypeAccessRecursive(
t: IrType,
location: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
typeContext: TypeContext = TypeContext.OTHER,
overrideId: Label<out DbExpr>? = null
): Label<out DbExpr> {
// TODO: `useType` substitutes types to their java equivalent, and sometimes that also means
// changing the number of type arguments. The below logic doesn't take this into account.
// For example `KFunction2<Int,Double,String>` becomes `KFunction<String>` with three child
// type access expressions: `Int`, `Double`, `String`.
val typeAccessId =
extractTypeAccess(
useType(t, typeContext),
location,
parent,
idx,
enclosingCallable,
enclosingStmt,
overrideId = overrideId
)
if (t is IrSimpleType) {
if (t.arguments.isNotEmpty() && overrideId != null) {
logger.error(
"Unexpected parameterized type with an overridden expression ID; children will be assigned fresh IDs"
)
}
extractTypeArguments(
t.arguments.filterIsInstance<IrType>(),
location,
typeAccessId,
enclosingCallable,
enclosingStmt
)
}
return typeAccessId
}
/**
* Extracts a list of types as type access expressions. Nested generics are also handled. Used
* for extracting nested type access expressions, and type arguments of constructor or function
* calls.
*/
private fun extractTypeArguments(
typeArgs: List<IrType>,
location: Label<DbLocation>,
parentExpr: Label<out DbExprparent>,
enclosingCallable: Label<out DbCallable>?,
enclosingStmt: Label<out DbStmt>?,
startIndex: Int = 0,
reverse: Boolean = false
) {
typeArgs.forEachIndexed { argIdx, arg ->
val mul = if (reverse) -1 else 1
extractTypeAccessRecursive(
arg,
location,
parentExpr,
argIdx * mul + startIndex,
enclosingCallable,
enclosingStmt,
TypeContext.GENERIC_ARGUMENT
)
}
}
/**
* Extracts type arguments of a member access expression as type access expressions. Nested
* generics are also handled. Used for extracting nested type access expressions, and type
* arguments of constructor or function calls.
*/
private fun <T : IrSymbol> extractTypeArguments(
c: IrMemberAccessExpression<T>,
parentExpr: Label<out DbExprparent>,
enclosingCallable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>,
startIndex: Int = 0,
reverse: Boolean = false
) {
val typeArguments =
(0 until c.typeArgumentsCount).map { c.getTypeArgument(it) }.requireNoNullsOrNull()
if (typeArguments == null) {
logger.errorElement("Found a null type argument for a member access expression", c)
} else {
extractTypeArguments(
typeArguments,
tw.getLocation(c),
parentExpr,
enclosingCallable,
enclosingStmt,
startIndex,
reverse
)
}
}
private fun extractArrayCreationWithInitializer(
parent: Label<out DbExprparent>,
arraySize: Int,
locId: Label<DbLocation>,
enclosingCallable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
): Label<DbArrayinit> {
val arrayCreationId = tw.getFreshIdLabel<DbArraycreationexpr>()
val arrayType =
pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)
val at = useType(arrayType)
tw.writeExprs_arraycreationexpr(arrayCreationId, at.javaResult.id, parent, 0)
tw.writeExprsKotlinType(arrayCreationId, at.kotlinResult.id)
extractExprContext(arrayCreationId, locId, enclosingCallable, enclosingStmt)
extractTypeAccessRecursive(
pluginContext.irBuiltIns.anyNType,
locId,
arrayCreationId,
-1,
enclosingCallable,
enclosingStmt
)
val initId = tw.getFreshIdLabel<DbArrayinit>()
tw.writeExprs_arrayinit(initId, at.javaResult.id, arrayCreationId, -2)
tw.writeExprsKotlinType(initId, at.kotlinResult.id)
extractExprContext(initId, locId, enclosingCallable, enclosingStmt)
extractConstantInteger(
arraySize,
locId,
arrayCreationId,
0,
enclosingCallable,
enclosingStmt
)
return initId
}
private fun extractTypeOperatorCall(
e: IrTypeOperatorCall,
callable: Label<out DbCallable>,
parent: Label<out DbExprparent>,
idx: Int,
enclosingStmt: Label<out DbStmt>
) {
with("type operator call", e) {
when (e.operator) {
IrTypeOperator.CAST -> {
val id = tw.getFreshIdLabel<DbCastexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_castexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
}
IrTypeOperator.IMPLICIT_CAST -> {
val id = tw.getFreshIdLabel<DbImplicitcastexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_implicitcastexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
}
IrTypeOperator.IMPLICIT_NOTNULL -> {
val id = tw.getFreshIdLabel<DbImplicitnotnullexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_implicitnotnullexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
}
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> {
val id = tw.getFreshIdLabel<DbImplicitcoerciontounitexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_implicitcoerciontounitexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
}
IrTypeOperator.SAFE_CAST -> {
val id = tw.getFreshIdLabel<DbSafecastexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_safecastexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
}
IrTypeOperator.INSTANCEOF -> {
val id = tw.getFreshIdLabel<DbInstanceofexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_instanceofexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 0, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 1, callable, enclosingStmt)
}
IrTypeOperator.NOT_INSTANCEOF -> {
val id = tw.getFreshIdLabel<DbNotinstanceofexpr>()
val locId = tw.getLocation(e)
val type = useType(e.type)
tw.writeExprs_notinstanceofexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractExpressionExpr(e.argument, callable, id, 0, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 1, callable, enclosingStmt)
}
IrTypeOperator.SAM_CONVERSION -> {
/*
The following Kotlin code
```
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val x = IntPredicate { it % 2 == 0 }
```
is extracted as
```
interface IntPredicate {
Boolean accept(Integer i);
}
class <Anon> extends Object implements IntPredicate {
Function1<Integer, Boolean> <fn>;
public <Anon>(Function1<Integer, Boolean> <fn>) { this.<fn> = <fn>; }
public override Boolean accept(Integer i) { return <fn>.invoke(i); }
}
IntPredicate x = (IntPredicate)new <Anon>(...);
```
*/
val st = e.argument.type as? IrSimpleType
if (st == null) {
logger.errorElement("Expected to find a simple type in SAM conversion.", e)
return
}
fun IrSimpleType.isKProperty() =
classFqName?.asString()?.startsWith("kotlin.reflect.KProperty") == true
if (
!st.isFunctionOrKFunction() &&
!st.isSuspendFunctionOrKFunction() &&
!st.isKProperty()
) {
logger.errorElement(
"Expected to find expression with function type in SAM conversion.",
e
)
return
}
// Either Function1, ... Function22 or FunctionN type, but not Function23 or
// above.
val functionType = getFunctionalInterfaceTypeWithTypeArgs(st.arguments)
if (functionType == null) {
logger.errorElement("Cannot find functional interface.", e)
return
}
val invokeMethod =
functionType.classOrNull?.owner?.declarations?.findSubType<IrFunction> {
it.name.asString() == OperatorNameConventions.INVOKE.asString()
}
if (invokeMethod == null) {
logger.errorElement(
"Couldn't find `invoke` method on functional interface.",
e
)
return
}
val typeOwner = e.typeOperand.classifierOrFail.owner
if (typeOwner !is IrClass) {
logger.errorElement(
"Expected to find SAM conversion to IrClass. Found '${typeOwner.javaClass}' instead. Can't implement SAM interface.",
e
)
return
}
val samMember =
typeOwner.declarations.findSubType<IrFunction> {
it is IrOverridableMember && it.modality == Modality.ABSTRACT
}
if (samMember == null) {
logger.errorElement(
"Couldn't find SAM member in type '${typeOwner.kotlinFqName.asString()}'. Can't implement SAM interface.",
e
)
return
}
val javaResult = TypeResult(tw.getFreshIdLabel<DbClassorinterface>(), "", "")
val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
val ids =
LocallyVisibleFunctionLabels(
TypeResults(javaResult, kotlinResult),
constructor = tw.getFreshIdLabel(),
constructorBlock = tw.getFreshIdLabel(),
function = tw.getFreshIdLabel()
)
val locId = tw.getLocation(e)
val helper = GeneratedClassHelper(locId, ids)
val declarationParent = peekDeclStackAsDeclarationParent(e) ?: return
val classId =
extractGeneratedClass(
ids,
listOf(pluginContext.irBuiltIns.anyType, e.typeOperand),
locId,
e,
declarationParent
)
// add field
val fieldId = tw.getFreshIdLabel<DbField>()
extractField(
fieldId,
"<fn>",
functionType,
classId,
locId,
DescriptorVisibilities.PRIVATE,
e,
isExternalDeclaration = false,
isFinal = true,
isStatic = false
)
// adjust constructor
helper.extractParameterToFieldAssignmentInConstructor(
"<fn>",
functionType,
fieldId,
0,
1
)
// add implementation function
val classTypeArgs = (e.type as? IrSimpleType)?.arguments
val typeSub =
classTypeArgs?.let { makeGenericSubstitutionFunction(typeOwner, it) }
fun trySub(t: IrType, context: TypeContext) =
if (typeSub == null) t else typeSub(t, context, pluginContext)
// Force extraction of this function even if this is a fake override --
// This happens in the case where a functional interface inherits its only
// abstract member,
// which usually we wouldn't extract, but in this case we're effectively using
// it as a template
// for the real function we're extracting that will implement this interface,
// and it serves fine
// for that purpose. By contrast if we looked through the fake to the underlying
// abstract method
// we would need to compose generic type substitutions -- for example, if we're
// implementing
// T UnaryOperator<T>.apply(T t) here, we would need to compose substitutions so
// we can implement
// the real underlying R Function<T, R>.apply(T t).
forceExtractFunction(
samMember,
classId,
extractBody = false,
extractMethodAndParameterTypeAccesses = true,
extractAnnotations = false,
typeSub,
classTypeArgs,
overriddenAttributes =
OverriddenFunctionAttributes(
id = ids.function,
sourceLoc = tw.getLocation(e),
modality = Modality.FINAL
)
)
addModifiers(ids.function, "override")
if (st.isSuspendFunctionOrKFunction()) {
addModifiers(ids.function, "suspend")
}
// body
val blockId = extractBlockBody(ids.function, locId)
// return stmt
val returnId = tw.getFreshIdLabel<DbReturnstmt>()
tw.writeStmts_returnstmt(returnId, blockId, 0, ids.function)
tw.writeHasLocation(returnId, locId)
// <fn>.invoke(vp0, cp1, vp2, vp3, ...) or
// <fn>.invoke(new Object[x]{vp0, vp1, vp2, ...})
// Call to original `invoke`:
val callId = tw.getFreshIdLabel<DbMethodaccess>()
val callType = useType(trySub(samMember.returnType, TypeContext.RETURN))
tw.writeExprs_methodaccess(callId, callType.javaResult.id, returnId, 0)
tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
extractExprContext(callId, locId, ids.function, returnId)
val calledMethodId = useFunction<DbMethod>(invokeMethod, functionType.arguments)
if (calledMethodId == null) {
logger.errorElement("Cannot get ID for called method", invokeMethod)
} else {
tw.writeCallableBinding(callId, calledMethodId)
}
// <fn> access
val lhsId = tw.getFreshIdLabel<DbVaraccess>()
val lhsType = useType(functionType)
tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, callId, -1)
tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
extractExprContext(lhsId, locId, ids.function, returnId)
tw.writeVariableBinding(lhsId, fieldId)
val parameters = mutableListOf<IrValueParameter>()
val extParam = samMember.extensionReceiverParameter
if (extParam != null) {
parameters.add(extParam)
}
parameters.addAll(samMember.valueParameters)
fun extractArgument(
p: IrValueParameter,
idx: Int,
parent: Label<out DbExprparent>
) {
val argsAccessId = tw.getFreshIdLabel<DbVaraccess>()
val paramType = useType(trySub(p.type, TypeContext.OTHER))
tw.writeExprs_varaccess(argsAccessId, paramType.javaResult.id, parent, idx)
tw.writeExprsKotlinType(argsAccessId, paramType.kotlinResult.id)
extractExprContext(argsAccessId, locId, ids.function, returnId)
tw.writeVariableBinding(argsAccessId, useValueParameter(p, ids.function))
}
val isBigArity = st.arguments.size > BuiltInFunctionArity.BIG_ARITY
val argParent =
if (isBigArity) {
// <fn>.invoke(new Object[x]{vp0, vp1, vp2, ...})
extractArrayCreationWithInitializer(
callId,
parameters.size,
locId,
ids.function,
returnId
)
} else {
// <fn>.invoke(vp0, cp1, vp2, vp3, ...) or
callId
}
for ((parameterIdx, vp) in parameters.withIndex()) {
extractArgument(vp, parameterIdx, argParent)
}
val id = tw.getFreshIdLabel<DbCastexpr>()
val type = useType(e.typeOperand)
tw.writeExprs_castexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
extractExprContext(id, locId, callable, enclosingStmt)
extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
val idNewexpr =
extractNewExpr(
ids.constructor,
ids.type,
locId,
id,
1,
callable,
enclosingStmt
)
tw.writeIsAnonymClass(
ids.type.javaResult.id.cast<DbClassorinterface>(),
idNewexpr
)
extractTypeAccessRecursive(
e.typeOperand,
locId,
idNewexpr,
-3,
callable,
enclosingStmt
)
extractExpressionExpr(e.argument, callable, idNewexpr, 0, enclosingStmt)
}
else -> {
logger.errorElement(
"Unrecognised IrTypeOperatorCall for ${e.operator}: " + e.render(),
e
)
}
}
}
}
private fun extractBreakContinue(e: IrBreakContinue, id: Label<out DbNamedexprorstmt>) {
with("break/continue", e) {
val locId = tw.getLocation(e)
tw.writeHasLocation(id, locId)
val label = e.label
if (label != null) {
tw.writeNamestrings(label, "", id)
}
}
}
private val IrType.isAnonymous: Boolean
get() = ((this as? IrSimpleType)?.classifier?.owner as? IrClass)?.isAnonymousObject ?: false
private fun addVisibilityModifierToLocalOrAnonymousClass(id: Label<out DbModifiable>) {
addModifiers(id, "private")
}
/** Extracts the class around a local function, a lambda, or a function reference. */
private fun extractGeneratedClass(
ids: GeneratedClassLabels,
superTypes: List<IrType>,
locId: Label<DbLocation>,
elementToReportOn: IrElement,
declarationParent: IrDeclarationParent,
compilerGeneratedKindOverride: CompilerGeneratedKinds? = null,
superConstructorSelector: (IrFunction) -> Boolean = { it.valueParameters.isEmpty() },
extractSuperconstructorArgs: (Label<DbSuperconstructorinvocationstmt>) -> Unit = {},
): Label<out DbClassorinterface> {
// Write class
val id = ids.type.javaResult.id.cast<DbClassorinterface>()
val pkgId = extractPackage("")
tw.writeClasses_or_interfaces(id, "", pkgId, id)
tw.writeCompiler_generated(
id,
(compilerGeneratedKindOverride ?: CompilerGeneratedKinds.CALLABLE_CLASS).kind
)
tw.writeHasLocation(id, locId)
// Extract constructor
val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
tw.writeConstrs(ids.constructor, "", "", unitType.javaResult.id, id, ids.constructor)
tw.writeConstrsKotlinType(ids.constructor, unitType.kotlinResult.id)
tw.writeHasLocation(ids.constructor, locId)
addModifiers(ids.constructor, "public")
// Constructor body
val constructorBlockId = ids.constructorBlock
tw.writeStmts_block(constructorBlockId, ids.constructor, 0, ids.constructor)
tw.writeHasLocation(constructorBlockId, locId)
// Super call
val baseClass = superTypes.first().classOrNull
if (baseClass == null) {
logger.warnElement("Cannot find base class", elementToReportOn)
} else {
val baseConstructor =
baseClass.owner.declarations.findSubType<IrFunction> {
it.symbol is IrConstructorSymbol && superConstructorSelector(it)
}
if (baseConstructor == null) {
logger.warnElement("Cannot find base constructor", elementToReportOn)
} else {
val baseConstructorId = useFunction<DbConstructor>(baseConstructor)
if (baseConstructorId == null) {
logger.errorElement("Cannot find base constructor ID", elementToReportOn)
} else {
val superCallId = tw.getFreshIdLabel<DbSuperconstructorinvocationstmt>()
tw.writeStmts_superconstructorinvocationstmt(
superCallId,
constructorBlockId,
0,
ids.constructor
)
tw.writeHasLocation(superCallId, locId)
tw.writeCallableBinding(superCallId.cast<DbCaller>(), baseConstructorId)
extractSuperconstructorArgs(superCallId)
}
}
}
addModifiers(id, "final")
addVisibilityModifierToLocalOrAnonymousClass(id)
extractClassSupertypes(
superTypes,
listOf(),
id,
isInterface = false,
inReceiverContext = true
)
extractEnclosingClass(declarationParent, id, null, locId, listOf())
return id
}
/**
* Extracts the class around a local function or a lambda. The superclass must have a no-arg
* constructor.
*/
private fun extractGeneratedClass(
localFunction: IrFunction,
superTypes: List<IrType>,
compilerGeneratedKindOverride: CompilerGeneratedKinds? = null
): Label<out DbClassorinterface> {
with("generated class", localFunction) {
val ids = getLocallyVisibleFunctionLabels(localFunction)
val id =
extractGeneratedClass(
ids,
superTypes,
tw.getLocation(localFunction),
localFunction,
localFunction.parent,
compilerGeneratedKindOverride = compilerGeneratedKindOverride
)
// Extract local function as a member
extractFunction(
localFunction,
id,
extractBody = true,
extractMethodAndParameterTypeAccesses = true,
extractAnnotations = false,
null,
listOf()
)
return id
}
}
private inner class DeclarationStackAdjuster(
val declaration: IrDeclaration,
val overriddenAttributes: OverriddenFunctionAttributes? = null
) : Closeable {
init {
declarationStack.push(declaration, overriddenAttributes)
}
override fun close() {
declarationStack.pop()
}
}
class DeclarationStack {
private val stack: Stack<Pair<IrDeclaration, OverriddenFunctionAttributes?>> = Stack()
fun push(item: IrDeclaration, overriddenAttributes: OverriddenFunctionAttributes?) =
stack.push(Pair(item, overriddenAttributes))
fun pop() = stack.pop()
fun isEmpty() = stack.isEmpty()
fun peek() = stack.peek()
fun tryPeek() = if (stack.isEmpty()) null else stack.peek()
fun findOverriddenAttributes(f: IrFunction) = stack.lastOrNull { it.first == f }?.second
}
data class OverriddenFunctionAttributes(
val id: Label<out DbCallable>? = null,
val sourceDeclarationId: Label<out DbCallable>? = null,
val sourceLoc: Label<DbLocation>? = null,
val valueParameters: List<IrValueParameter>? = null,
val typeParameters: List<IrTypeParameter>? = null,
val isStatic: Boolean? = null,
val visibility: DescriptorVisibility? = null,
val modality: Modality? = null,
)
private fun peekDeclStackAsDeclarationParent(
elementToReportOn: IrElement
): IrDeclarationParent? {
val trapWriter = tw
if (declarationStack.isEmpty() && trapWriter is SourceFileTrapWriter) {
// If the current declaration is used as a parent, we might end up with an empty stack.
// In this case, the source file is the parent.
return trapWriter.irFile
}
val dp = declarationStack.peek().first as? IrDeclarationParent
if (dp == null)
logger.errorElement("Couldn't find current declaration parent", elementToReportOn)
return dp
}
private enum class CompilerGeneratedKinds(val kind: Int) {
DECLARING_CLASSES_OF_ADAPTER_FUNCTIONS(1),
GENERATED_DATA_CLASS_MEMBER(2),
DEFAULT_PROPERTY_ACCESSOR(3),
CLASS_INITIALISATION_METHOD(4),
ENUM_CLASS_SPECIAL_MEMBER(5),
DELEGATED_PROPERTY_GETTER(6),
DELEGATED_PROPERTY_SETTER(7),
JVMSTATIC_PROXY_METHOD(8),
JVMOVERLOADS_METHOD(9),
DEFAULT_ARGUMENTS_METHOD(10),
INTERFACE_FORWARDER(11),
ENUM_CONSTRUCTOR_ARGUMENT(12),
CALLABLE_CLASS(13),
}
}