org.jetbrains.kotlin.ir.IrAttribute.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir
import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer
import org.jetbrains.kotlin.ir.declarations.copyAttributes
import org.jetbrains.kotlin.utils.DummyDelegate
import java.lang.ref.WeakReference
import java.util.function.Function
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* Creates new [IrAttribute] which can be used to store additional data of type [T] inside of [E].
*
* See [IrAttribute] for details.
*
* @param followAttributeOwner If true, every read and write of this attribute will be performed not
* on the given element directly, but on its `attributeOwnerId`.
* I.e. `element.foo = 123` will effectively transform to `element.attributeOwnerId.foo = 123`.
*
* This is a temporary mechanism, while the goal is to drop `attributeOwnerId`.
* When that happens, this parameter will likely be replaced with something like `copyByDefault`, which would
* have similar behavior w.r.t. [copyAttributes], but with an actual copy, instead of following the link.
* For details, see [unwrapAttributeOwner].
*/
fun irAttribute(followAttributeOwner: Boolean): IrAttribute.Delegate =
IrAttribute.Delegate(followAttributeOwner)
/**
* Creates new [IrAttribute] which can be used to put an additional mark
* on an element of type [E].
*
* This is similar to using `irAttribute()`, except:
* - Boolean attribute has 3 states: `false`, `true` and `null`,
* while flag has 2: set or not set.
* - It is possible to store a flag a bit more efficiently,
* by not allocating a slot for a value - not implemented yet.
*
* See [irAttribute] for details.
*/
fun irFlag(followAttributeOwner: Boolean): IrAttribute.Flag.Delegate =
IrAttribute.Flag.Delegate(IrAttribute.Delegate(followAttributeOwner))
/**
* Returns a value of [attribute], or null if the value is missing.
*/
operator fun E.get(attribute: IrAttribute): T? {
return (unwrapAttributeOwner(attribute) as IrElementBase).getAttributeInternal(attribute)
}
/**
* Stores a [value] associated with [attribute] in this IrElement, or removes an association if [value] is null.
*
* @return The previous value associated with the attribute, or null if the attribute was not present.
*/
operator fun E.set(attribute: IrAttribute, value: T?): T? {
return (unwrapAttributeOwner(attribute) as IrElementBase).setAttributeInternal(attribute, value)
}
private fun E.unwrapAttributeOwner(attribute: IrAttribute): IrElement {
// This mechanism is intended to aid with future migration off of attributeOwnerId.
// The main change would be that [copyAttributes] wouldn't just set attributeOwnerId to the copied element,
// but do a proper clone of an attribute array.
// Details:
// This place allows us to record if/when a given property is accessed via attributeOwnerId or not.
// If it always is, it means that this particular property should be copied in [copyAttributes] by default.
// Otherwise, we should probably still seek for a possibility to copy it by default. For that, we can also add tracking
// of properties' gets/sets, and check,during compiler execution, whether a value returned from a copy always
// matches the one returned via `attributeOwnerId.get`. If it does, it is probably safe to always copy it as well.
// There is one more peculiarity in the current implementation of [copyAttributes] via attributeOwnerId - it is
// essentially not a copy, but a link between two objects which share the same attributes - if
// those are accessed via attributeOwnerId, this is. However, attributes will employ a hard copy.
// To ensure the new behavior is the-same-or-better, the tracking and comparison can be used as well.
return if (attribute.followAttributeOwner)
(this as IrAttributeContainer).attributeOwnerId
else this
}
/**
* A key for storing additional data inside [IrElement].
*
* Example usage:
* ```kotlin
* var IrFunction.binaryName: String by irAttribute()
*
* fun computeBinaryName(function: IrFunction) {
* function.binaryName = function.findBinaryNameAnnotation() ?: function.name.mangle()
* }
* ```
*
* ##### Migration note:
* There is also a [MutableMap] wrapper around [IrAttribute] to ease migration from maps in
* the form of `MutableMap` to attributes - you can replace such maps with
* `irAttribute().asMap()` in-place, and the usual map-based syntax will continue to work.
* Example:
* ```kotlin
* val binaryNames = mutableMapOf()
* // can be replaced with:
* val binaryNames by irAttribute().asMap()
* ```
* `binaryNames[element]` will then behave the same way as `element.binaryNames`.
*
* Similarly, `MutableSet` may be implemented by `irFlag()`:
* ```kotlin
* val functionsWithSpecialBridges = hashSet()
* // can be replaced with:
* val functionsWithSpecialBridges by irFlag().asSet()
* ```
* This is useful for marker sets, e.g. where later you would check `someFunction in functionsWithSpecialBridges`.
*
* However, a proper migration to the extension property syntax is eventually expected, and this
* functionality is to be removed.
* Note that collection operations, like iterating over all entries of the map, are not supported
* and will throw at runtime. If there is a need to use them, such a map should not be converted to
* an attribute.
*
* @param E restricts the type of [IrElement] on which this attribute can be stored.
* @param T the type of the data stored in the attribute.
*/
class IrAttribute internal constructor(
val name: String?,
owner: Any?,
val followAttributeOwner: Boolean,
) {
/**
* Used solely for debug, to help distinguish between multiple instances of attribute keys.
* This may happen if the key is defined inside some class, instead of on top level.
*/
val ownerForDebug = owner?.let(::WeakReference)
@Suppress("NOTHING_TO_INLINE")
inline operator fun getValue(thisRef: E, property: KProperty<*>): T? {
return thisRef[this]
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun setValue(thisRef: E, property: KProperty<*>, value: T?) {
thisRef[this] = value
}
override fun toString(): String {
return when {
name != null && ownerForDebug?.get() != null -> "$name (inside of ${ownerForDebug.get()})"
name != null -> name
else -> super.toString()
}
}
/**
* See [irFlag]
*/
class Flag internal constructor(
private val attribute: IrAttribute,
) {
@Suppress("NOTHING_TO_INLINE")
inline operator fun getValue(thisRef: E, property: KProperty<*>): Boolean = get(thisRef)
@Suppress("NOTHING_TO_INLINE")
inline operator fun setValue(thisRef: E, property: KProperty<*>, value: Boolean) = set(thisRef, value)
fun get(element: E): Boolean {
return element[attribute] == true
}
fun set(element: E, value: Boolean) {
element[attribute] = if (value) true else null
}
class Delegate internal constructor(
private val attributeDelegate: IrAttribute.Delegate,
) {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): Flag {
val attribute = attributeDelegate.provideDelegate(thisRef, property)
return Flag(attribute)
}
fun asSet() = PropertyDelegateProvider { thisRef: Any?, property: KProperty<*> ->
val attribute = [email protected](thisRef, property)
DummyDelegate(IrAttributeMapWrapper.FlagSetWrapper(attribute))
}
}
}
class Delegate internal constructor(
private val followAttributeOwner: Boolean,
) {
fun create(owner: Any?, name: String?): IrAttribute {
return IrAttribute(
name = name,
owner = owner,
followAttributeOwner = followAttributeOwner,
)
}
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): IrAttribute =
create(thisRef, property.name)
fun asMap() = PropertyDelegateProvider { thisRef: Any?, property: KProperty<*> ->
val attribute = [email protected](thisRef, property)
DummyDelegate(IrAttributeMapWrapper(attribute))
}
}
}
/**
* A helper for migration from `MutableMap` to `IrAttribute`.
* See [IrAttribute]
*/
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
class IrAttributeMapWrapper internal constructor(
val attribute: IrAttribute,
) : AbstractMutableMap() {
override operator fun get(element: E): T? {
return element[attribute]
}
override fun containsKey(element: E): Boolean {
return element[attribute] != null
}
override fun put(element: E, value: T): T? {
return element.set(attribute, value)
}
override fun remove(element: E): T? {
return element.set(attribute, null)
}
override fun computeIfAbsent(element: E, mappingFunction: Function): T {
element[attribute]?.let {
return it
}
val newValue = mappingFunction.apply(element)
element[attribute] = newValue
return newValue
}
override val keys: MutableSet = KeyCollection()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override val entries: MutableSet> get() = unsupportedMapOperation()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override val size: Int get() = unsupportedMapOperation()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override fun clear() = unsupportedMapOperation()
override fun equals(other: Any?): Boolean = other is IrAttributeMapWrapper<*, *> && attribute == other.attribute
override fun hashCode(): Int = attribute.hashCode()
override fun toString(): String = attribute.toString()
private inner class KeyCollection : AbstractMutableSet() {
override fun contains(element: E): Boolean {
return element[attribute] != null
}
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override val size: Int
get() = unsupportedMapOperation()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override fun add(element: E): Boolean = unsupportedMapOperation()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override fun iterator(): MutableIterator = unsupportedMapOperation()
}
/**
* A helper for migration from `MutableSet` to `IrAttribute`.
* See [IrAttribute]
*/
class FlagSetWrapper internal constructor(
private val flag: IrAttribute.Flag,
) : AbstractMutableSet() {
override fun contains(element: E): Boolean {
return flag.get(element)
}
override fun add(element: E): Boolean {
val wasSet = flag.get(element)
flag.set(element, true)
return !wasSet
}
override fun remove(element: E): Boolean {
val wasSet = flag.get(element)
flag.set(element, false)
return wasSet
}
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override val size: Int get() = unsupportedMapOperation()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override fun iterator(): MutableIterator = unsupportedMapOperation()
@Deprecated(
"Not implemented in IrAttribute, will throw at runtime." +
"If you need this Map functionality, please use regular MutableMap.",
level = DeprecationLevel.ERROR
)
override fun clear() = unsupportedMapOperation()
}
companion object {
private fun unsupportedMapOperation(): Nothing =
throw UnsupportedOperationException("This map-based operation is unsupported by IR attribute")
}
}