kotlinx.atomicfu compiler plugin (JS): delegated properties supported (#4692)
* kotlinx.atomicfu compiler plugin (JS): delegated properties supported * NPE handling fixed * Scoped delegated properties supported
This commit is contained in:
+104
-17
@@ -29,6 +29,8 @@ private const val TRACE_BASE_TYPE = "TraceBase"
|
|||||||
private const val GETTER = "atomicfu\$getter"
|
private const val GETTER = "atomicfu\$getter"
|
||||||
private const val SETTER = "atomicfu\$setter"
|
private const val SETTER = "atomicfu\$setter"
|
||||||
private const val GET = "get"
|
private const val GET = "get"
|
||||||
|
private const val GET_VALUE = "getValue"
|
||||||
|
private const val SET_VALUE = "setValue"
|
||||||
private const val ATOMIC_VALUE_FACTORY = "atomic"
|
private const val ATOMIC_VALUE_FACTORY = "atomic"
|
||||||
private const val TRACE = "Trace"
|
private const val TRACE = "Trace"
|
||||||
private const val INVOKE = "invoke"
|
private const val INVOKE = "invoke"
|
||||||
@@ -101,6 +103,39 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
|
|
||||||
private inner class AtomicTransformer : IrElementTransformer<IrFunction?> {
|
private inner class AtomicTransformer : IrElementTransformer<IrFunction?> {
|
||||||
|
|
||||||
|
override fun visitProperty(declaration: IrProperty, data: IrFunction?): IrStatement {
|
||||||
|
// Support transformation for delegated properties:
|
||||||
|
if (declaration.isDelegated && declaration.backingField?.type?.isAtomicValueType() == true) {
|
||||||
|
declaration.backingField?.let { delegateBackingField ->
|
||||||
|
delegateBackingField.initializer?.let {
|
||||||
|
val initializer = it.expression as IrCall
|
||||||
|
when {
|
||||||
|
initializer.isAtomicFieldGetter() -> {
|
||||||
|
// val _a = atomic(0)
|
||||||
|
// var a: Int by _a
|
||||||
|
// Accessors of the delegated property `a` are implemented via the generated property `a$delegate`,
|
||||||
|
// that is the copy of the original `_a`.
|
||||||
|
// They should be delegated to the value of the original field `_a` instead of `a$delegate`.
|
||||||
|
|
||||||
|
// fun <get-a>() = a$delegate.value -> _a.value
|
||||||
|
// fun <set-a>(value: Int) = { a$delegate.value = value } -> { _a.value = value }
|
||||||
|
val originalField = initializer.getBackingField()
|
||||||
|
declaration.transform(DelegatePropertyTransformer(originalField, initializer.dispatchReceiver, false), null)
|
||||||
|
}
|
||||||
|
initializer.isAtomicFactory() -> {
|
||||||
|
// var a by atomic(77) -> var a: Int = 77
|
||||||
|
it.expression = initializer.eraseAtomicFactory()
|
||||||
|
?: error("Atomic factory was expected but found ${initializer.render()}")
|
||||||
|
declaration.transform(DelegatePropertyTransformer(delegateBackingField, initializer.dispatchReceiver, true), null)
|
||||||
|
}
|
||||||
|
else -> error("Unexpected initializer of the delegated property: $initializer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visitProperty(declaration, data)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitFunction(declaration: IrFunction, data: IrFunction?): IrStatement {
|
override fun visitFunction(declaration: IrFunction, data: IrFunction?): IrStatement {
|
||||||
return super.visitFunction(declaration, declaration)
|
return super.visitFunction(declaration, declaration)
|
||||||
}
|
}
|
||||||
@@ -233,6 +268,46 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
return super.visitConstructorCall(expression, data)
|
return super.visitConstructorCall(expression, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class DelegatePropertyTransformer(
|
||||||
|
val originalField: IrField,
|
||||||
|
val receiver: IrExpression?,
|
||||||
|
val isInitializedWithAtomicFactory: Boolean
|
||||||
|
): IrElementTransformerVoid() {
|
||||||
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
|
// Accessors of the delegated property have following signatures:
|
||||||
|
|
||||||
|
// public inline operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
|
||||||
|
// public inline operator fun getValue(thisRef: Any?, property: KProperty<*>): T
|
||||||
|
|
||||||
|
// getValue/setValue should get and set the value of the originalField
|
||||||
|
val name = expression.symbol.owner.name.asString()
|
||||||
|
if (expression.symbol.isKotlinxAtomicfuPackage() && (name == GET_VALUE || name == SET_VALUE)) {
|
||||||
|
val type = originalField.type.atomicToValueType()
|
||||||
|
val isSetter = name == SET_VALUE
|
||||||
|
val runtimeFunction = getRuntimeFunctionSymbol(name, type)
|
||||||
|
val dispatchReceiver = if (isInitializedWithAtomicFactory) {
|
||||||
|
val thisRef = expression.getValueArgument(0)!!
|
||||||
|
if (thisRef.isConstNull()) null else thisRef
|
||||||
|
} else {
|
||||||
|
receiver
|
||||||
|
}
|
||||||
|
val fieldAccessors = listOf(
|
||||||
|
context.buildFieldAccessor(originalField, dispatchReceiver, false),
|
||||||
|
context.buildFieldAccessor(originalField, dispatchReceiver, true)
|
||||||
|
)
|
||||||
|
return buildCall(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
target = runtimeFunction,
|
||||||
|
type = type,
|
||||||
|
typeArguments = if (runtimeFunction.owner.typeParameters.size == 1) listOf(type) else emptyList(),
|
||||||
|
valueArguments = if (isSetter) listOf(expression.getValueArgument(2)!!, fieldAccessors[0], fieldAccessors[1]) else
|
||||||
|
fieldAccessors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return super.visitCall(expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun IrExpression.getReceiverAccessors(parent: IrFunction?): List<IrExpression>? =
|
private fun IrExpression.getReceiverAccessors(parent: IrFunction?): List<IrExpression>? =
|
||||||
when {
|
when {
|
||||||
this is IrCall -> getAccessors()
|
this is IrCall -> getAccessors()
|
||||||
@@ -297,27 +372,36 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
it.type.isAtomicArrayType() && symbol.owner.name.asString() == GET
|
it.type.isAtomicArrayType() && symbol.owner.name.asString() == GET
|
||||||
} ?: false
|
} ?: false
|
||||||
|
|
||||||
|
private fun IrCall.getAccessors(): List<IrExpression> =
|
||||||
|
if (!isArrayElementGetter()) {
|
||||||
|
val field = getBackingField()
|
||||||
|
listOf(
|
||||||
|
context.buildFieldAccessor(field, dispatchReceiver, false),
|
||||||
|
context.buildFieldAccessor(field, dispatchReceiver, true)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val index = getValueArgument(0)!!
|
||||||
|
val arrayGetter = dispatchReceiver as IrCall
|
||||||
|
val arrayField = arrayGetter.getBackingField()
|
||||||
|
listOf(
|
||||||
|
context.buildArrayElementAccessor(arrayField, arrayGetter, index, false),
|
||||||
|
context.buildArrayElementAccessor(arrayField, arrayGetter, index, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun IrStatement.isTrace() =
|
private fun IrStatement.isTrace() =
|
||||||
this is IrCall && (isTraceInvoke() || isTraceAppend())
|
this is IrCall && (isTraceInvoke() || isTraceAppend())
|
||||||
|
|
||||||
private fun IrCall.isTraceInvoke(): Boolean =
|
private fun IrCall.isTraceInvoke(): Boolean =
|
||||||
symbol.isKotlinxAtomicfuPackage() &&
|
symbol.isKotlinxAtomicfuPackage() &&
|
||||||
symbol.owner.name.asString() == INVOKE &&
|
symbol.owner.name.asString() == INVOKE &&
|
||||||
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
|
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
|
||||||
|
|
||||||
private fun IrCall.isTraceAppend(): Boolean =
|
private fun IrCall.isTraceAppend(): Boolean =
|
||||||
symbol.isKotlinxAtomicfuPackage() &&
|
symbol.isKotlinxAtomicfuPackage() &&
|
||||||
symbol.owner.name.asString() == APPEND &&
|
symbol.owner.name.asString() == APPEND &&
|
||||||
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
|
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
|
||||||
|
|
||||||
private fun IrCall.getAccessors(): List<IrExpression> {
|
|
||||||
val isArrayElement = isArrayElementGetter()
|
|
||||||
val valueType = type.atomicToValueType()
|
|
||||||
return listOf(
|
|
||||||
context.buildAccessorLambda(this, valueType, false, isArrayElement),
|
|
||||||
context.buildAccessorLambda(this, valueType, true, isArrayElement)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRuntimeFunctionSymbol(name: String, type: IrType): IrSimpleFunctionSymbol {
|
private fun getRuntimeFunctionSymbol(name: String, type: IrType): IrSimpleFunctionSymbol {
|
||||||
val functionName = when (name) {
|
val functionName = when (name) {
|
||||||
@@ -340,11 +424,11 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
|
|
||||||
private fun IrCall.getAtomicFunctionName(): String =
|
private fun IrCall.getAtomicFunctionName(): String =
|
||||||
symbol.signature?.let { signature ->
|
symbol.signature?.let { signature ->
|
||||||
if (signature is IdSignature.AccessorSignature) signature.accessorSignature else signature.asPublic()
|
signature.getDeclarationNameBySignature()?.let { name ->
|
||||||
}?.declarationFqName?.let { name ->
|
if (name.substringBefore('.') in ATOMIC_VALUE_TYPES) {
|
||||||
if (name.substringBefore('.') in ATOMIC_VALUE_TYPES) {
|
name.substringAfter('.')
|
||||||
name.substringAfter('.')
|
} else name
|
||||||
} else name
|
}
|
||||||
} ?: error("Incorrect pattern of the atomic function name: ${symbol.owner.render()}")
|
} ?: error("Incorrect pattern of the atomic function name: ${symbol.owner.render()}")
|
||||||
|
|
||||||
private fun IrCall.eraseAtomicFactory() =
|
private fun IrCall.eraseAtomicFactory() =
|
||||||
@@ -415,6 +499,9 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_ARRAY_OF_NULLS_FACTORY &&
|
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_ARRAY_OF_NULLS_FACTORY &&
|
||||||
type.isAtomicArrayType()
|
type.isAtomicArrayType()
|
||||||
|
|
||||||
|
private fun IrCall.isAtomicFieldGetter(): Boolean =
|
||||||
|
type.isAtomicValueType() && symbol.owner.name.asString().startsWith("<get-")
|
||||||
|
|
||||||
private fun IrConstructorCall.isAtomicArrayConstructor(): Boolean = type.isAtomicArrayType()
|
private fun IrConstructorCall.isAtomicArrayConstructor(): Boolean = type.isAtomicArrayType()
|
||||||
|
|
||||||
private fun IrCall.isReentrantLockFactory(): Boolean =
|
private fun IrCall.isReentrantLockFactory(): Boolean =
|
||||||
|
|||||||
+116
-68
@@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlinx.atomicfu.compiler.extensions
|
package org.jetbrains.kotlinx.atomicfu.compiler.extensions
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.deepCopyWithVariables
|
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder.buildValueParameter
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
@@ -18,6 +17,8 @@ import org.jetbrains.kotlin.ir.expressions.impl.*
|
|||||||
import org.jetbrains.kotlin.ir.symbols.*
|
import org.jetbrains.kotlin.ir.symbols.*
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.impl.*
|
import org.jetbrains.kotlin.ir.types.impl.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.types.Variance
|
import org.jetbrains.kotlin.types.Variance
|
||||||
@@ -120,11 +121,13 @@ internal fun buildGetValue(
|
|||||||
|
|
||||||
internal fun IrPluginContext.buildConstNull() = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irBuiltIns.anyNType)
|
internal fun IrPluginContext.buildConstNull() = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irBuiltIns.anyNType)
|
||||||
|
|
||||||
internal fun getterName(getterCall: IrCall) = "<get-${getterCall.symbol.owner.name.getFieldName()}>"
|
internal fun IrExpression.isConstNull() = this is IrConst<*> && this.kind.asString == "Null"
|
||||||
internal fun setterName(getterCall: IrCall) = "<set-${getterCall.symbol.owner.name.getFieldName()}>"
|
|
||||||
|
|
||||||
private fun Name.getFieldName() = "<get-(\\w+)>".toRegex().find(asString())?.groupValues?.get(1)
|
internal fun IrField.getterName() = "<get-${name.asString()}>"
|
||||||
?: error("Getter name ${this.asString()} does not match special name pattern <get-fieldName>")
|
internal fun IrField.setterName() = "<set-${name.asString()}>"
|
||||||
|
|
||||||
|
internal fun String.getFieldName() = "<get-(\\w+)>".toRegex().find(this)?.groupValues?.get(1)
|
||||||
|
?: error("Getter name $this does not match special name pattern <get-fieldName>")
|
||||||
|
|
||||||
internal fun IrFunctionAccessExpression.getValueArguments() =
|
internal fun IrFunctionAccessExpression.getValueArguments() =
|
||||||
(0 until valueArgumentsCount).map { i ->
|
(0 until valueArgumentsCount).map { i ->
|
||||||
@@ -162,82 +165,108 @@ private fun buildGetField(backingField: IrField, ownerClass: IrExpression?): IrG
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun IrPluginContext.buildAccessorLambda(
|
private fun IrPluginContext.buildDefaultPropertyAccessor(name: String): IrSimpleFunction =
|
||||||
getter: IrCall,
|
irFactory.buildFun {
|
||||||
valueType: IrType,
|
|
||||||
isSetter: Boolean,
|
|
||||||
isArrayElement: Boolean
|
|
||||||
): IrExpression {
|
|
||||||
val getterCall = if (isArrayElement) getter.dispatchReceiver as IrCall else getter
|
|
||||||
val type = if (isSetter) buildSetterType(valueType) else buildGetterType(valueType)
|
|
||||||
val name = if (isSetter) setterName(getterCall) else getterName(getterCall)
|
|
||||||
val returnType = if (isSetter) irBuiltIns.unitType else valueType
|
|
||||||
val accessorFunction = irFactory.buildFun {
|
|
||||||
startOffset = UNDEFINED_OFFSET
|
startOffset = UNDEFINED_OFFSET
|
||||||
endOffset = UNDEFINED_OFFSET
|
endOffset = UNDEFINED_OFFSET
|
||||||
this.origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
|
this.origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
|
||||||
this.name = Name.identifier(name)
|
|
||||||
this.visibility = DescriptorVisibilities.LOCAL
|
this.visibility = DescriptorVisibilities.LOCAL
|
||||||
this.isInline = true
|
this.isInline = true
|
||||||
this.returnType = returnType
|
this.name = Name.identifier(name)
|
||||||
}.apply {
|
}
|
||||||
val valueParameter = JsIrBuilder.buildValueParameter(this, name, 0, valueType)
|
|
||||||
|
internal fun IrPluginContext.buildArrayElementAccessor(
|
||||||
|
arrayField: IrField,
|
||||||
|
arrayGetter: IrCall,
|
||||||
|
index: IrExpression,
|
||||||
|
isSetter: Boolean
|
||||||
|
): IrExpression {
|
||||||
|
val valueType = arrayField.type
|
||||||
|
val functionType = if (isSetter) buildSetterType(valueType) else buildGetterType(valueType)
|
||||||
|
val returnType = if (isSetter) irBuiltIns.unitType else valueType
|
||||||
|
val name = if (isSetter) arrayField.setterName() else arrayField.getterName()
|
||||||
|
val accessorFunction = buildDefaultPropertyAccessor(name).apply {
|
||||||
|
val valueParameter = buildValueParameter(this, name, 0, valueType)
|
||||||
this.valueParameters = if (isSetter) listOf(valueParameter) else emptyList()
|
this.valueParameters = if (isSetter) listOf(valueParameter) else emptyList()
|
||||||
val body = if (isSetter) {
|
body = irFactory.buildBlockBody(
|
||||||
if (isArrayElement) {
|
listOf(
|
||||||
val setSymbol = referenceFunction(referenceArrayClass(getterCall.type as IrSimpleType), SET)
|
if (isSetter) {
|
||||||
val elementIndex = getter.getValueArgument(0)!!.deepCopyWithVariables()
|
val setSymbol = referenceFunction(referenceArrayClass(arrayField.type as IrSimpleType), SET)
|
||||||
buildCall(
|
buildCall(
|
||||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
target = setSymbol,
|
target = setSymbol,
|
||||||
type = irBuiltIns.unitType,
|
type = irBuiltIns.unitType,
|
||||||
origin = IrStatementOrigin.LAMBDA,
|
origin = IrStatementOrigin.LAMBDA,
|
||||||
valueArguments = listOf(elementIndex, valueParameter.capture())
|
valueArguments = listOf(index, valueParameter.capture())
|
||||||
).apply {
|
).apply {
|
||||||
dispatchReceiver = getterCall
|
this.dispatchReceiver = arrayGetter
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val getField = buildGetField(arrayField, arrayGetter.dispatchReceiver)
|
||||||
|
val getSymbol = referenceFunction(referenceArrayClass(arrayField.type as IrSimpleType), GET)
|
||||||
|
buildCall(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
target = getSymbol,
|
||||||
|
type = valueType,
|
||||||
|
origin = IrStatementOrigin.LAMBDA,
|
||||||
|
valueArguments = listOf(index)
|
||||||
|
).apply {
|
||||||
|
dispatchReceiver = getField
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
)
|
||||||
buildSetField(getterCall.getBackingField(), getterCall.dispatchReceiver, valueParameter.capture())
|
)
|
||||||
}
|
this.returnType = returnType
|
||||||
} else {
|
|
||||||
val getField = buildGetField(getterCall.getBackingField(), getterCall.dispatchReceiver)
|
|
||||||
if (isArrayElement) {
|
|
||||||
val getSymbol = referenceFunction(referenceArrayClass(getterCall.type as IrSimpleType), GET)
|
|
||||||
val elementIndex = getter.getValueArgument(0)!!.deepCopyWithVariables()
|
|
||||||
buildCall(
|
|
||||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
|
||||||
target = getSymbol,
|
|
||||||
type = valueType,
|
|
||||||
origin = IrStatementOrigin.LAMBDA,
|
|
||||||
valueArguments = listOf(elementIndex)
|
|
||||||
).apply {
|
|
||||||
dispatchReceiver = getField.deepCopyWithVariables()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
getField.deepCopyWithVariables()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.body = irFactory.buildBlockBody(listOf(body))
|
|
||||||
origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
|
|
||||||
}
|
}
|
||||||
return IrFunctionExpressionImpl(
|
return IrFunctionExpressionImpl(
|
||||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
type,
|
functionType,
|
||||||
accessorFunction,
|
accessorFunction,
|
||||||
IrStatementOrigin.LAMBDA
|
IrStatementOrigin.LAMBDA
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrCall.getBackingField(): IrField {
|
internal fun IrPluginContext.buildFieldAccessor(
|
||||||
val correspondingPropertySymbol = symbol.owner.correspondingPropertySymbol!!
|
field: IrField,
|
||||||
return correspondingPropertySymbol.owner.backingField!!
|
dispatchReceiver: IrExpression?,
|
||||||
|
isSetter: Boolean
|
||||||
|
): IrExpression {
|
||||||
|
val valueType = field.type
|
||||||
|
val functionType = if (isSetter) buildSetterType(valueType) else buildGetterType(valueType)
|
||||||
|
val returnType = if (isSetter) irBuiltIns.unitType else valueType
|
||||||
|
val name = if (isSetter) field.setterName() else field.getterName()
|
||||||
|
val accessorFunction = buildDefaultPropertyAccessor(name).apply {
|
||||||
|
val valueParameter = buildValueParameter(this, name, 0, valueType)
|
||||||
|
valueParameters = if (isSetter) listOf(valueParameter) else emptyList()
|
||||||
|
body = irFactory.buildBlockBody(
|
||||||
|
listOf(
|
||||||
|
if (isSetter) {
|
||||||
|
buildSetField(field, dispatchReceiver, valueParameter.capture())
|
||||||
|
} else {
|
||||||
|
buildGetField(field, dispatchReceiver)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this.returnType = returnType
|
||||||
|
}
|
||||||
|
return IrFunctionExpressionImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
functionType,
|
||||||
|
accessorFunction,
|
||||||
|
IrStatementOrigin.LAMBDA
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun IrCall.getBackingField(): IrField =
|
||||||
|
symbol.owner.correspondingPropertySymbol?.let { propertySymbol ->
|
||||||
|
propertySymbol.owner.backingField ?: error("Property expected to have backing field")
|
||||||
|
} ?: error("Atomic property accessor ${this.render()} expected to have non-null correspondingPropertySymbol")
|
||||||
|
|
||||||
internal fun IrPluginContext.referencePackageFunction(
|
internal fun IrPluginContext.referencePackageFunction(
|
||||||
packageName: String,
|
packageName: String,
|
||||||
name: String,
|
name: String,
|
||||||
predicate: (IrFunctionSymbol) -> Boolean = { true }
|
predicate: (IrFunctionSymbol) -> Boolean = { true }
|
||||||
) = try {
|
): IrSimpleFunctionSymbol = try {
|
||||||
referenceFunctions(FqName("$packageName.$name")).single(predicate)
|
referenceFunctions(FqName("$packageName.$name")).single(predicate)
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
error("Exception while looking for the function `$name` in package `$packageName`: ${e.message}")
|
error("Exception while looking for the function `$name` in package `$packageName`: ${e.message}")
|
||||||
@@ -253,13 +282,32 @@ internal fun IrPluginContext.referenceFunction(classSymbol: IrClassSymbol, funct
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun IrPluginContext.referenceArrayClass(irType: IrSimpleType): IrClassSymbol {
|
private fun IrPluginContext.referenceArrayClass(irType: IrSimpleType): IrClassSymbol {
|
||||||
val afuClassId = (irType.classifier.signature!!.asPublic())!!.declarationFqName
|
val jsArrayName = irType.getArrayClassFqName()
|
||||||
val classId = FqName("$KOTLIN.${AFU_ARRAY_CLASSES[afuClassId]!!}")
|
return referenceClass(jsArrayName) ?: error("Array class $jsArrayName was not found in the context")
|
||||||
return referenceClass(classId)!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun IrPluginContext.getArrayConstructorSymbol(irType: IrSimpleType, predicate: (IrConstructorSymbol) -> Boolean = { true }): IrConstructorSymbol {
|
internal fun IrPluginContext.getArrayConstructorSymbol(
|
||||||
val afuClassId = (irType.classifier.signature!!.asPublic())!!.declarationFqName
|
irType: IrSimpleType,
|
||||||
val classId = FqName("$KOTLIN.${AFU_ARRAY_CLASSES[afuClassId]!!}")
|
predicate: (IrConstructorSymbol) -> Boolean = { true }
|
||||||
return referenceConstructors(classId).single(predicate)
|
): IrConstructorSymbol {
|
||||||
|
val jsArrayName = irType.getArrayClassFqName()
|
||||||
|
return try {
|
||||||
|
referenceConstructors(jsArrayName).single(predicate)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
error("Array constructor $jsArrayName matching the predicate was not found in the context")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrSimpleType.getArrayClassFqName(): FqName =
|
||||||
|
classifier.signature?.let { signature ->
|
||||||
|
signature.getDeclarationNameBySignature().let { name ->
|
||||||
|
AFU_ARRAY_CLASSES[name]?.let { jsArrayName ->
|
||||||
|
FqName("$KOTLIN.$jsArrayName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: error("Unexpected array type ${this.render()}")
|
||||||
|
|
||||||
|
internal fun IdSignature.getDeclarationNameBySignature(): String? {
|
||||||
|
val commonSignature = if (this is IdSignature.AccessorSignature) accessorSignature else asPublic()
|
||||||
|
return commonSignature?.declarationFqName
|
||||||
|
}
|
||||||
+6
@@ -43,6 +43,12 @@ public class AtomicfuJsIrTestGenerated extends AbstractAtomicfuJsIrTest {
|
|||||||
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/AtomicArrayTest.kt");
|
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/AtomicArrayTest.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("DelegatedPropertiesTest.kt")
|
||||||
|
public void testDelegatedPropertiesTest() throws Exception {
|
||||||
|
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/DelegatedPropertiesTest.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("ExtensionsTest.kt")
|
@TestMetadata("ExtensionsTest.kt")
|
||||||
public void testExtensionsTest() throws Exception {
|
public void testExtensionsTest() throws Exception {
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import kotlinx.atomicfu.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
private val _topLevelInt = atomic(42)
|
||||||
|
var topLevelInt: Int by _topLevelInt
|
||||||
|
|
||||||
|
var vIntTopLevel by atomic(55)
|
||||||
|
|
||||||
|
class DelegatedProperties {
|
||||||
|
private val _a = atomic(42)
|
||||||
|
var a: Int by _a
|
||||||
|
|
||||||
|
private val _l = atomic(55555555555)
|
||||||
|
var l: Long by _l
|
||||||
|
|
||||||
|
private val _b = atomic(false)
|
||||||
|
var b: Boolean by _b
|
||||||
|
|
||||||
|
private val _ref = atomic(A(B(77)))
|
||||||
|
var ref: A by _ref
|
||||||
|
|
||||||
|
var vInt by atomic(77)
|
||||||
|
|
||||||
|
var vLong by atomic(777777777)
|
||||||
|
|
||||||
|
var vBoolean by atomic(false)
|
||||||
|
|
||||||
|
var vRef by atomic(A(B(77)))
|
||||||
|
|
||||||
|
class A (val b: B)
|
||||||
|
class B (val n: Int)
|
||||||
|
|
||||||
|
fun testDelegatedAtomicInt() {
|
||||||
|
assertEquals(42, a)
|
||||||
|
_a.compareAndSet(42, 56)
|
||||||
|
assertEquals(56, a)
|
||||||
|
a = 77
|
||||||
|
_a.compareAndSet(77, 66)
|
||||||
|
assertEquals(66, _a.value)
|
||||||
|
assertEquals(66, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testDelegatedAtomicLong() {
|
||||||
|
assertEquals(55555555555, l)
|
||||||
|
_l.getAndIncrement()
|
||||||
|
assertEquals(55555555556, l)
|
||||||
|
l = 7777777777777
|
||||||
|
assertTrue(_l.compareAndSet(7777777777777, 66666666666))
|
||||||
|
assertEquals(66666666666, _l.value)
|
||||||
|
assertEquals(66666666666, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testDelegatedAtomicBoolean() {
|
||||||
|
assertEquals(false, b)
|
||||||
|
_b.lazySet(true)
|
||||||
|
assertEquals(true, b)
|
||||||
|
b = false
|
||||||
|
assertTrue(_b.compareAndSet(false, true))
|
||||||
|
assertEquals(true, _b.value)
|
||||||
|
assertEquals(true, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testDelegatedAtomicRef() {
|
||||||
|
assertEquals(77, ref.b.n)
|
||||||
|
_ref.lazySet(A(B(66)))
|
||||||
|
assertEquals(66, ref.b.n)
|
||||||
|
assertTrue(_ref.compareAndSet(_ref.value, A(B(56))))
|
||||||
|
assertEquals(56, ref.b.n)
|
||||||
|
ref = A(B(99))
|
||||||
|
assertEquals(99, _ref.value.b.n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testVolatileInt() {
|
||||||
|
assertEquals(77, vInt)
|
||||||
|
vInt = 55
|
||||||
|
assertEquals(110, vInt * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testVolatileLong() {
|
||||||
|
assertEquals(777777777, vLong)
|
||||||
|
vLong = 55
|
||||||
|
assertEquals(55, vLong)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testVolatileBoolean() {
|
||||||
|
assertEquals(false, vBoolean)
|
||||||
|
vBoolean = true
|
||||||
|
assertEquals(true, vBoolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testVolatileRef() {
|
||||||
|
assertEquals(77, vRef.b.n)
|
||||||
|
vRef = A(B(99))
|
||||||
|
assertEquals(99, vRef.b.n)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class D {
|
||||||
|
var b: Int by _a
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testScopedDelegatedProperties() {
|
||||||
|
val clazz = D()
|
||||||
|
clazz.b = 42
|
||||||
|
_a.compareAndSet(42, 56)
|
||||||
|
assertEquals(56, clazz.b)
|
||||||
|
clazz.b = 77
|
||||||
|
_a.compareAndSet(77, 66)
|
||||||
|
assertEquals(66, _a.value)
|
||||||
|
assertEquals(66, clazz.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
testDelegatedAtomicInt()
|
||||||
|
testDelegatedAtomicLong()
|
||||||
|
testDelegatedAtomicBoolean()
|
||||||
|
testDelegatedAtomicRef()
|
||||||
|
testVolatileInt()
|
||||||
|
testVolatileBoolean()
|
||||||
|
testVolatileLong()
|
||||||
|
testVolatileRef()
|
||||||
|
testScopedDelegatedProperties()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testTopLevelDelegatedProperties() {
|
||||||
|
assertEquals(42, topLevelInt)
|
||||||
|
_topLevelInt.compareAndSet(42, 56)
|
||||||
|
assertEquals(56, topLevelInt)
|
||||||
|
topLevelInt = 77
|
||||||
|
_topLevelInt.compareAndSet(77, 66)
|
||||||
|
assertEquals(66, _topLevelInt.value)
|
||||||
|
assertEquals(66, topLevelInt)
|
||||||
|
|
||||||
|
assertEquals(55, vIntTopLevel)
|
||||||
|
vIntTopLevel = 70
|
||||||
|
assertEquals(140, vIntTopLevel * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val testClass = DelegatedProperties()
|
||||||
|
testClass.test()
|
||||||
|
//testTopLevelDelegatedProperties()
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user