diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/AssignmentGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/AssignmentGenerator.kt index cc2816be11d..fb0dd04fe6b 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/AssignmentGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/AssignmentGenerator.kt @@ -16,16 +16,18 @@ package org.jetbrains.kotlin.psi2ir.generators +import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.ir.builders.irBlock import org.jetbrains.kotlin.ir.builders.irGet import org.jetbrains.kotlin.ir.builders.irTemporary -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrDynamicOperatorExpressionImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl import org.jetbrains.kotlin.ir.util.referenceFunction +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffsetSkippingComments @@ -35,6 +37,7 @@ import org.jetbrains.kotlin.psi2ir.unwrappedSetMethod import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver import org.jetbrains.kotlin.types.KotlinType @@ -53,35 +56,74 @@ class AssignmentGenerator(statementGenerator: StatementGenerator) : StatementGen val ktLeft = ktExpression.left!! val ktRight = ktExpression.right!! val irAssignmentReceiver = generateAssignmentReceiver(ktLeft, origin) + val isDynamicCall = opResolvedCall.resultingDescriptor.isDynamic() return irAssignmentReceiver.assign { irLValue -> - val opCall = statementGenerator.pregenerateCallReceivers(opResolvedCall) - opCall.setExplicitReceiverValue(irLValue) - opCall.irValueArgumentsByIndex[0] = ktRight.genExpr() - val irOpCall = CallGenerator(statementGenerator).generateCall(ktExpression, opCall, origin) - - if (isSimpleAssignment) { - // Set( Op( Get(), RHS ) ) - irLValue.store(irOpCall) + if (isDynamicCall) { + IrDynamicOperatorExpressionImpl( + ktExpression.startOffsetSkippingComments, ktExpression.endOffset, + context.irBuiltIns.unitType, + getDynamicAugmentedAssignmentOperator(ktExpression.operationToken) + ).apply { + left = irLValue.load() + right = ktRight.genExpr() + } } else { - // Op( Get(), RHS ) - irOpCall + val opCall = statementGenerator.pregenerateCallReceivers(opResolvedCall) + opCall.setExplicitReceiverValue(irLValue) + opCall.irValueArgumentsByIndex[0] = ktRight.genExpr() + val irOpCall = CallGenerator(statementGenerator).generateCall(ktExpression, opCall, origin) + + if (isSimpleAssignment) { + // Set( Op( Get(), RHS ) ) + irLValue.store(irOpCall) + } else { + // Op( Get(), RHS ) + irOpCall + } } } } + private fun getDynamicAugmentedAssignmentOperator(operatorToken: IElementType): IrDynamicOperator = + when (operatorToken) { + KtTokens.PLUSEQ -> IrDynamicOperator.PLUSEQ + KtTokens.MINUSEQ -> IrDynamicOperator.MINUSEQ + KtTokens.MULTEQ -> IrDynamicOperator.MULEQ + KtTokens.DIVEQ -> IrDynamicOperator.DIVEQ + KtTokens.PERCEQ -> IrDynamicOperator.MODEQ + else -> throw AssertionError("Unexpected operator token: $operatorToken") + } + fun generatePrefixIncrementDecrement(ktExpression: KtPrefixExpression, origin: IrStatementOrigin): IrExpression { val opResolvedCall = getResolvedCall(ktExpression)!! val ktBaseExpression = ktExpression.baseExpression!! - val irAssignmentReceiver = generateAssignmentReceiver(ktBaseExpression, origin) + val irAssignmentReceiver = generateAssignmentReceiver(ktBaseExpression, origin, isAssignmentStatement = false) + val isDynamicCall = opResolvedCall.resultingDescriptor.isDynamic() return irAssignmentReceiver.assign { irLValue -> - irBlock(ktExpression.startOffsetSkippingComments, ktExpression.endOffset, origin, irLValue.type) { - val opCall = statementGenerator.pregenerateCall(opResolvedCall) - opCall.setExplicitReceiverValue(irLValue) - val irOpCall = CallGenerator(statementGenerator).generateCall(ktExpression, opCall, origin) - +irLValue.store(irOpCall) - +irLValue.load() + val startOffset = ktExpression.startOffsetSkippingComments + val endOffset = ktExpression.endOffset + + if (isDynamicCall) { + IrDynamicOperatorExpressionImpl( + startOffset, endOffset, + irLValue.type, + if (ktExpression.operationToken == KtTokens.PLUSPLUS) + IrDynamicOperator.PREFIX_INCREMENT + else + IrDynamicOperator.PREFIX_DECREMENT + ).apply { + receiver = irLValue.load() + } + } else { + irBlock(startOffset, endOffset, origin, irLValue.type) { + val opCall = statementGenerator.pregenerateCall(opResolvedCall) + opCall.setExplicitReceiverValue(irLValue) + val irOpCall = CallGenerator(statementGenerator).generateCall(ktExpression, opCall, origin) + +irLValue.store(irOpCall) + +irLValue.load() + } } } } @@ -89,23 +131,43 @@ class AssignmentGenerator(statementGenerator: StatementGenerator) : StatementGen fun generatePostfixIncrementDecrement(ktExpression: KtPostfixExpression, origin: IrStatementOrigin): IrExpression { val opResolvedCall = getResolvedCall(ktExpression)!! val ktBaseExpression = ktExpression.baseExpression!! - val irAssignmentReceiver = generateAssignmentReceiver(ktBaseExpression, origin) + val irAssignmentReceiver = generateAssignmentReceiver(ktBaseExpression, origin, isAssignmentStatement = false) + val isDynamicCall = opResolvedCall.resultingDescriptor.isDynamic() + val startOffset = ktExpression.startOffsetSkippingComments + val endOffset = ktExpression.endOffset return irAssignmentReceiver.assign { irLValue -> - irBlock(ktExpression.startOffsetSkippingComments, ktExpression.endOffset, origin, irLValue.type) { - val temporary = irTemporary(irLValue.load()) - val opCall = statementGenerator.pregenerateCall(opResolvedCall) - opCall.setExplicitReceiverValue( - VariableLValue(context, startOffset, endOffset, temporary.symbol, temporary.type) - ) - val irOpCall = CallGenerator(statementGenerator).generateCall(ktExpression, opCall, origin) - +irLValue.store(irOpCall) - +irGet(temporary.type, temporary.symbol) + if (isDynamicCall) { + IrDynamicOperatorExpressionImpl( + startOffset, endOffset, + irLValue.type, + if (ktExpression.operationToken == KtTokens.PLUSPLUS) + IrDynamicOperator.POSTFIX_INCREMENT + else + IrDynamicOperator.POSTFIX_DECREMENT + ).apply { + receiver = irLValue.load() + } + } else { + irBlock(startOffset, endOffset, origin, irLValue.type) { + val temporary = irTemporary(irLValue.load()) + val opCall = statementGenerator.pregenerateCall(opResolvedCall) + opCall.setExplicitReceiverValue( + VariableLValue(context, startOffset, endOffset, temporary.symbol, temporary.type) + ) + val irOpCall = CallGenerator(statementGenerator).generateCall(ktExpression, opCall, origin) + +irLValue.store(irOpCall) + +irGet(temporary.type, temporary.symbol) + } } } } - private fun generateAssignmentReceiver(ktLeft: KtExpression, origin: IrStatementOrigin): AssignmentReceiver { + private fun generateAssignmentReceiver( + ktLeft: KtExpression, + origin: IrStatementOrigin, + isAssignmentStatement: Boolean = true + ): AssignmentReceiver { if (ktLeft is KtArrayAccessExpression) { return generateArrayAccessAssignmentReceiver(ktLeft, origin) } @@ -116,7 +178,12 @@ class AssignmentGenerator(statementGenerator: StatementGenerator) : StatementGen return when (descriptor) { is SyntheticFieldDescriptor -> { val receiverValue = - statementGenerator.generateBackingFieldReceiver(ktLeft.startOffsetSkippingComments, ktLeft.endOffset, resolvedCall, descriptor) + statementGenerator.generateBackingFieldReceiver( + ktLeft.startOffsetSkippingComments, + ktLeft.endOffset, + resolvedCall, + descriptor + ) createBackingFieldLValue(ktLeft, descriptor.propertyDescriptor, receiverValue, origin) } is LocalVariableDescriptor -> @@ -133,7 +200,7 @@ class AssignmentGenerator(statementGenerator: StatementGenerator) : StatementGen else createVariableValue(ktLeft, descriptor, origin) is PropertyDescriptor -> - generateAssignmentReceiverForProperty(descriptor, origin, ktLeft, resolvedCall) + generateAssignmentReceiverForProperty(descriptor, origin, ktLeft, resolvedCall, isAssignmentStatement) is ValueDescriptor -> createVariableValue(ktLeft, descriptor, origin) else -> @@ -172,29 +239,45 @@ class AssignmentGenerator(statementGenerator: StatementGenerator) : StatementGen descriptor: PropertyDescriptor, origin: IrStatementOrigin, ktLeft: KtExpression, - resolvedCall: ResolvedCall<*> + resolvedCall: ResolvedCall<*>, + isAssignmentStatement: Boolean ): AssignmentReceiver = - if (isValInitializationInConstructor(descriptor, resolvedCall)) { - val thisClass = getThisClass() - val thisAsReceiverParameter = thisClass.thisAsReceiverParameter - val thisType = thisAsReceiverParameter.type.toIrType() - val irThis = IrGetValueImpl( - ktLeft.startOffsetSkippingComments, ktLeft.endOffset, - thisType, - context.symbolTable.referenceValueParameter(thisAsReceiverParameter) - ) - createBackingFieldLValue(ktLeft, descriptor, RematerializableValue(irThis), null) - } else { - val propertyReceiver = statementGenerator.generateCallReceiver( - ktLeft, descriptor, resolvedCall.dispatchReceiver, resolvedCall.extensionReceiver, - isSafe = resolvedCall.call.isSafeCall(), - isAssignmentReceiver = true - ) + when { + descriptor.isDynamic() -> + DynamicMemberLValue( + context, + ktLeft.startOffsetSkippingComments, ktLeft.endOffset, + descriptor.type.toIrType(), + descriptor.name.asString(), + statementGenerator.generateCallReceiver( + ktLeft, descriptor, resolvedCall.dispatchReceiver, resolvedCall.extensionReceiver, + isSafe = resolvedCall.call.isSafeCall(), + isAssignmentReceiver = isAssignmentStatement + ) + ) + isValInitializationInConstructor(descriptor, resolvedCall) -> { + val thisClass = getThisClass() + val thisAsReceiverParameter = thisClass.thisAsReceiverParameter + val thisType = thisAsReceiverParameter.type.toIrType() + val irThis = IrGetValueImpl( + ktLeft.startOffsetSkippingComments, ktLeft.endOffset, + thisType, + context.symbolTable.referenceValueParameter(thisAsReceiverParameter) + ) + createBackingFieldLValue(ktLeft, descriptor, RematerializableValue(irThis), null) + } + else -> { + val propertyReceiver = statementGenerator.generateCallReceiver( + ktLeft, descriptor, resolvedCall.dispatchReceiver, resolvedCall.extensionReceiver, + isSafe = resolvedCall.call.isSafeCall(), + isAssignmentReceiver = isAssignmentStatement + ) - val superQualifier = getSuperQualifier(resolvedCall) + val superQualifier = getSuperQualifier(resolvedCall) - // TODO property imported from an object - createPropertyLValue(ktLeft, descriptor, propertyReceiver, getTypeArguments(resolvedCall), origin, superQualifier) + // TODO property imported from an object + createPropertyLValue(ktLeft, descriptor, propertyReceiver, getTypeArguments(resolvedCall), origin, superQualifier) + } } private fun createPropertyLValue( diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/DynamicMemberLValue.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/DynamicMemberLValue.kt new file mode 100644 index 00000000000..43b0e0f51a6 --- /dev/null +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/DynamicMemberLValue.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.psi2ir.intermediate + +import org.jetbrains.kotlin.ir.builders.IrGeneratorContext +import org.jetbrains.kotlin.ir.expressions.IrDynamicOperator +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrDynamicMemberExpressionImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrDynamicOperatorExpressionImpl +import org.jetbrains.kotlin.ir.expressions.left +import org.jetbrains.kotlin.ir.expressions.right +import org.jetbrains.kotlin.ir.types.IrType + +class DynamicMemberLValue( + private val context: IrGeneratorContext, + private val startOffset: Int, + private val endOffset: Int, + override val type: IrType, + private val memberName: String, + private val receiver: CallReceiver +) : LValue, AssignmentReceiver { + + override fun load(): IrExpression = + receiver.call { dispatchReceiverValue, extensionReceiverValue -> + val dynamicReceiver = getDynamicReceiver(dispatchReceiverValue, extensionReceiverValue) + + IrDynamicMemberExpressionImpl( + startOffset, endOffset, + type, + memberName, + dynamicReceiver + ) + } + + override fun store(irExpression: IrExpression): IrExpression = + receiver.call { dispatchReceiverValue, extensionReceiverValue -> + val dynamicReceiver = getDynamicReceiver(dispatchReceiverValue, extensionReceiverValue) + + IrDynamicOperatorExpressionImpl( + startOffset, endOffset, + context.irBuiltIns.unitType, + IrDynamicOperator.EQ + ).apply { + left = dynamicReceiver + right = irExpression + } + } + + override fun assign(withLValue: (LValue) -> IrExpression): IrExpression = + receiver.call { dispatchReceiverValue, extensionReceiverValue -> + withLValue( + DynamicMemberLValue( + context, startOffset, endOffset, type, memberName, + SimpleCallReceiver(dispatchReceiverValue, extensionReceiverValue) + ) + ) + } + + private fun getDynamicReceiver(dispatchReceiverValue: IntermediateValue?, extensionReceiverValue: IntermediateValue?): IrExpression { + if (dispatchReceiverValue == null) throw AssertionError("Dynamic call $memberName should have a dispatch receiver") + if (extensionReceiverValue != null) throw AssertionError("Dynamic call $memberName should have no extension receiver") + return dispatchReceiverValue.load() + } +} \ No newline at end of file diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SafeCallReceiver.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SafeCallReceiver.kt index 0fbf150a273..7e16d273b16 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SafeCallReceiver.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SafeCallReceiver.kt @@ -16,15 +16,14 @@ package org.jetbrains.kotlin.psi2ir.intermediate -import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.buildStatement +import org.jetbrains.kotlin.ir.builders.irIfNull +import org.jetbrains.kotlin.ir.builders.irNull import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrIfThenElseImpl import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.psi2ir.generators.GeneratorWithScope -import org.jetbrains.kotlin.types.typeUtil.builtIns -import org.jetbrains.kotlin.types.typeUtil.makeNullable class SafeCallReceiver( @@ -33,7 +32,7 @@ class SafeCallReceiver( val endOffset: Int, val extensionReceiver: IntermediateValue?, val dispatchReceiver: IntermediateValue?, - val isAssignmentReceiver: Boolean + val isStatement: Boolean ) : CallReceiver { override fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression { @@ -52,7 +51,7 @@ class SafeCallReceiver( val irResult = withDispatchAndExtensionReceivers(dispatchReceiverValue, extensionReceiverValue) - val resultType = if (isAssignmentReceiver) generator.context.irBuiltIns.unitType else irResult.type.makeNullable() + val resultType = if (isStatement) generator.context.irBuiltIns.unitType else irResult.type.makeNullable() val irBlock = IrBlockImpl(startOffset, endOffset, resultType, IrStatementOrigin.SAFE_CALL) @@ -79,7 +78,7 @@ fun IrExpression.safeCallOnDispatchReceiver( generator, startOffset, endOffset, extensionReceiver = null, dispatchReceiver = OnceExpressionValue(this), - isAssignmentReceiver = false + isStatement = false ).call { dispatchReceiverValue, _ -> ifNotNull(dispatchReceiverValue!!.load()) } \ No newline at end of file diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrDynamicExpression.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrDynamicExpression.kt index 609619bf49f..3443960fd59 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrDynamicExpression.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrDynamicExpression.kt @@ -24,7 +24,10 @@ var IrDynamicOperatorExpression.left: IrExpression var IrDynamicOperatorExpression.right: IrExpression get() = arguments[0] set(value) { - arguments[0] = value + if (arguments.isEmpty()) + arguments.add(value) + else + arguments[0] = value } interface IrDynamicMemberExpression : IrDynamicExpression { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrDynamicOperatorExpressionImpl.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrDynamicOperatorExpressionImpl.kt index da7d2d9b78e..88c35956fee 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrDynamicOperatorExpressionImpl.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrDynamicOperatorExpressionImpl.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.visitors.IrElementTransformer import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.utils.SmartList class IrDynamicOperatorExpressionImpl( startOffset: Int, @@ -23,7 +24,7 @@ class IrDynamicOperatorExpressionImpl( override lateinit var receiver: IrExpression - override val arguments: MutableList = ArrayList() + override val arguments: MutableList = SmartList() override fun accept(visitor: IrElementVisitor, data: D): R = visitor.visitDynamicOperatorExpression(this, data) diff --git a/compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.kt b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.kt new file mode 100644 index 00000000000..c4e3823992a --- /dev/null +++ b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.kt @@ -0,0 +1,7 @@ +fun testMemberAssignment(d: dynamic) { + d.m = 1 +} + +fun testSafeMemberAssignment(d: dynamic) { + d?.m = 1 +} \ No newline at end of file diff --git a/compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.txt b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.txt new file mode 100644 index 00000000000..d61ac48203a --- /dev/null +++ b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.txt @@ -0,0 +1,26 @@ +FILE fqName: fileName:/dynamicMemberAssignment.kt + FUN name:testMemberAssignment visibility:public modality:FINAL <> (d:dynamic) returnType:kotlin.Unit flags: + VALUE_PARAMETER name:d index:0 type:dynamic flags: + BLOCK_BODY + DYN_OP operator=EQ type=kotlin.Unit + receiver: GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + 0: CONST Int type=kotlin.Int value=1 + FUN name:testSafeMemberAssignment visibility:public modality:FINAL <> (d:dynamic) returnType:kotlin.Unit flags: + VALUE_PARAMETER name:d index:0 type:dynamic flags: + BLOCK_BODY + BLOCK type=kotlin.Unit origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp0_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp0_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=EQ type=kotlin.Unit + receiver: GET_VAR 'tmp0_safe_receiver: dynamic' type=dynamic origin=null + 0: CONST Int type=kotlin.Int value=1 diff --git a/compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.kt b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.kt new file mode 100644 index 00000000000..7458ff873ac --- /dev/null +++ b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.kt @@ -0,0 +1,16 @@ +fun testAugmentedMemberAssignment(d: dynamic) { + d.m += "+=" + d.m -= "-=" + d.m *= "*=" + d.m /= "/=" + d.m %= "%=" +} + +// see KT-29768 +fun testSafeAugmentedMemberAssignment(d: dynamic) { + d?.m += "+=" + d?.m -= "-=" + d?.m *= "*=" + d?.m /= "/=" + d?.m %= "%=" +} diff --git a/compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.txt b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.txt new file mode 100644 index 00000000000..0d629feff6e --- /dev/null +++ b/compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.txt @@ -0,0 +1,112 @@ +FILE fqName: fileName:/dynamicMemberAugmentedAssignment.kt + FUN name:testAugmentedMemberAssignment visibility:public modality:FINAL <> (d:dynamic) returnType:kotlin.Unit flags: + VALUE_PARAMETER name:d index:0 type:dynamic flags: + BLOCK_BODY + DYN_OP operator=PLUSEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="+=" + DYN_OP operator=MINUSEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="-=" + DYN_OP operator=MULEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="*=" + DYN_OP operator=DIVEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="/=" + DYN_OP operator=MODEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="%=" + FUN name:testSafeAugmentedMemberAssignment visibility:public modality:FINAL <> (d:dynamic) returnType:kotlin.Unit flags: + VALUE_PARAMETER name:d index:0 type:dynamic flags: + BLOCK_BODY + BLOCK type=kotlin.Unit origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp0_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp0_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=PLUSEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'tmp0_safe_receiver: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="+=" + BLOCK type=kotlin.Unit origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp1_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp1_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=MINUSEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'tmp1_safe_receiver: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="-=" + BLOCK type=kotlin.Unit origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp2_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp2_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=MULEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'tmp2_safe_receiver: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="*=" + BLOCK type=kotlin.Unit origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp3_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp3_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=DIVEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'tmp3_safe_receiver: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="/=" + BLOCK type=kotlin.Unit origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp4_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=kotlin.Unit origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp4_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=MODEQ type=kotlin.Unit + receiver: DYN_MEMBER memberName='m' type=dynamic + GET_VAR 'tmp4_safe_receiver: dynamic' type=dynamic origin=null + 0: CONST String type=kotlin.String value="%=" diff --git a/compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.kt b/compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.kt new file mode 100644 index 00000000000..9f8ce1dd8ac --- /dev/null +++ b/compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.kt @@ -0,0 +1,14 @@ + +fun testMemberIncrementDecrement(d: dynamic) { + val t1 = ++d.prefixIncr + val t2 = --d.prefixDecr + val t3 = d.postfixIncr++ + val t4 = d.postfixDecr-- +} + +fun testSafeMemberIncrementDecrement(d: dynamic) { + val t1 = ++d?.prefixIncr + val t2 = --d?.prefixDecr + val t3 = d?.postfixIncr++ + val t4 = d?.postfixDecr-- +} \ No newline at end of file diff --git a/compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.txt b/compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.txt new file mode 100644 index 00000000000..26d0b69dda3 --- /dev/null +++ b/compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.txt @@ -0,0 +1,83 @@ +FILE fqName: fileName:/dynamicMemberIncrementDecrement.kt + FUN name:testMemberIncrementDecrement visibility:public modality:FINAL <> (d:dynamic) returnType:kotlin.Unit flags: + VALUE_PARAMETER name:d index:0 type:dynamic flags: + BLOCK_BODY + VAR name:t1 type:dynamic flags:val + DYN_OP operator=PREFIX_INCREMENT type=dynamic + receiver: DYN_MEMBER memberName='prefixIncr' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + VAR name:t2 type:dynamic flags:val + DYN_OP operator=PREFIX_DECREMENT type=dynamic + receiver: DYN_MEMBER memberName='prefixDecr' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + VAR name:t3 type:dynamic flags:val + DYN_OP operator=POSTFIX_INCREMENT type=dynamic + receiver: DYN_MEMBER memberName='postfixIncr' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + VAR name:t4 type:dynamic flags:val + DYN_OP operator=POSTFIX_DECREMENT type=dynamic + receiver: DYN_MEMBER memberName='postfixDecr' type=dynamic + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + FUN name:testSafeMemberIncrementDecrement visibility:public modality:FINAL <> (d:dynamic) returnType:kotlin.Unit flags: + VALUE_PARAMETER name:d index:0 type:dynamic flags: + BLOCK_BODY + VAR name:t1 type:dynamic flags:val + BLOCK type=dynamic origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp0_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=dynamic origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp0_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=PREFIX_INCREMENT type=dynamic + receiver: DYN_MEMBER memberName='prefixIncr' type=dynamic + GET_VAR 'tmp0_safe_receiver: dynamic' type=dynamic origin=null + VAR name:t2 type:dynamic flags:val + BLOCK type=dynamic origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp1_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=dynamic origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp1_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=PREFIX_DECREMENT type=dynamic + receiver: DYN_MEMBER memberName='prefixDecr' type=dynamic + GET_VAR 'tmp1_safe_receiver: dynamic' type=dynamic origin=null + VAR name:t3 type:dynamic flags:val + BLOCK type=dynamic origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp2_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=dynamic origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp2_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=POSTFIX_INCREMENT type=dynamic + receiver: DYN_MEMBER memberName='postfixIncr' type=dynamic + GET_VAR 'tmp2_safe_receiver: dynamic' type=dynamic origin=null + VAR name:t4 type:dynamic flags:val + BLOCK type=dynamic origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp3_safe_receiver type:dynamic flags:val + GET_VAR 'value-parameter d: dynamic' type=dynamic origin=null + WHEN type=dynamic origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp3_safe_receiver: dynamic' type=dynamic origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: DYN_OP operator=POSTFIX_DECREMENT type=dynamic + receiver: DYN_MEMBER memberName='postfixDecr' type=dynamic + GET_VAR 'tmp3_safe_receiver: dynamic' type=dynamic origin=null diff --git a/compiler/testData/ir/irText/expressions/safeCallWithIncrementDecrement.txt b/compiler/testData/ir/irText/expressions/safeCallWithIncrementDecrement.txt index cedd02d5885..fad10b22f72 100644 --- a/compiler/testData/ir/irText/expressions/safeCallWithIncrementDecrement.txt +++ b/compiler/testData/ir/irText/expressions/safeCallWithIncrementDecrement.txt @@ -61,22 +61,20 @@ FILE fqName:test fileName:/safeCallWithIncrementDecrement.kt FUN name:testProperty visibility:public modality:FINAL <> (nc:test.C?) returnType:kotlin.Unit flags: VALUE_PARAMETER name:nc index:0 type:test.C? flags: BLOCK_BODY - BLOCK type=kotlin.Unit origin=SAFE_CALL - VAR IR_TEMPORARY_VARIABLE name:tmp0_safe_receiver type:test.C? flags:val - GET_VAR 'value-parameter nc: C?' type=test.C? origin=null - WHEN type=kotlin.Unit origin=null - BRANCH - if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ - arg0: GET_VAR 'tmp0_safe_receiver: C?' type=test.C? origin=null - arg1: CONST Null type=kotlin.Nothing? value=null - then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit - typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] - CONST Null type=kotlin.Nothing? value=null - BRANCH - if: CONST Boolean type=kotlin.Boolean value=true - then: TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit - typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] - BLOCK type=kotlin.Int origin=POSTFIX_INCR + TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public flags: superTypes:[kotlin.Any] + BLOCK type=kotlin.Int? origin=SAFE_CALL + VAR IR_TEMPORARY_VARIABLE name:tmp0_safe_receiver type:test.C? flags:val + GET_VAR 'value-parameter nc: C?' type=test.C? origin=null + WHEN type=kotlin.Int? origin=null + BRANCH + if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ + arg0: GET_VAR 'tmp0_safe_receiver: C?' type=test.C? origin=null + arg1: CONST Null type=kotlin.Nothing? value=null + then: CONST Null type=kotlin.Nothing? value=null + BRANCH + if: CONST Boolean type=kotlin.Boolean value=true + then: BLOCK type=kotlin.Int origin=POSTFIX_INCR VAR IR_TEMPORARY_VARIABLE name:tmp1_receiver type:test.C? flags:val GET_VAR 'tmp0_safe_receiver: C?' type=test.C? origin=null BLOCK type=kotlin.Int origin=POSTFIX_INCR diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrJsTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrJsTextTestCaseGenerated.java index 761b0f34948..94a585f5b8f 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrJsTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrJsTextTestCaseGenerated.java @@ -56,6 +56,21 @@ public class IrJsTextTestCaseGenerated extends AbstractIrJsTextTestCase { runTest("compiler/testData/ir/irJsText/dynamic/dynamicMemberAccess.kt"); } + @TestMetadata("dynamicMemberAssignment.kt") + public void testDynamicMemberAssignment() throws Exception { + runTest("compiler/testData/ir/irJsText/dynamic/dynamicMemberAssignment.kt"); + } + + @TestMetadata("dynamicMemberAugmentedAssignment.kt") + public void testDynamicMemberAugmentedAssignment() throws Exception { + runTest("compiler/testData/ir/irJsText/dynamic/dynamicMemberAugmentedAssignment.kt"); + } + + @TestMetadata("dynamicMemberIncrementDecrement.kt") + public void testDynamicMemberIncrementDecrement() throws Exception { + runTest("compiler/testData/ir/irJsText/dynamic/dynamicMemberIncrementDecrement.kt"); + } + @TestMetadata("dynamicWithImplicitCast.kt") public void testDynamicWithImplicitCast() throws Exception { runTest("compiler/testData/ir/irJsText/dynamic/dynamicWithImplicitCast.kt");