From dc4bb3015c9988500d1dee7f9231935aeee49927 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 25 Aug 2016 11:06:46 +0300 Subject: [PATCH] Inline safe call chains. --- .../kotlin/psi2ir/Psi2IrTranslator.kt | 13 +-- .../BranchingExpressionGenerator.kt | 2 +- .../psi2ir/generators/CallGeneration.kt | 4 +- .../kotlin/psi2ir/generators/CallGenerator.kt | 4 +- .../generators/OperatorExpressionGenerator.kt | 6 +- .../kotlin/psi2ir/generators/Primitives.kt | 23 ++--- .../psi2ir/generators/StatementGenerator.kt | 2 +- .../ArrayAccessAssignmentReceiver.kt | 2 +- .../kotlin/psi2ir/intermediate/CallBuilder.kt | 4 +- .../psi2ir/intermediate/OnceCallValue.kt | 2 +- .../intermediate/OnceExpressionValue.kt | 2 +- .../intermediate/RematerializableValue.kt | 8 +- .../psi2ir/intermediate/SafeCallReceiver.kt | 12 +-- .../psi2ir/intermediate/SimpleCallReceiver.kt | 6 +- .../intermediate/TransientReceiverValue.kt | 2 +- .../kotlin/psi2ir/intermediate/Values.kt | 6 +- .../transformations/InlineSafeCallChains.kt | 83 +++++++++++++++++++ .../ReplaceTemporaryVariable.kt | 36 ++++++++ .../testData/ir/irText/chainOfSafeCalls.kt | 7 ++ .../testData/ir/irText/chainOfSafeCalls.txt | 27 ++++++ .../ir/irText/variableAsFunctionCall.txt | 18 ++-- .../kotlin/ir/IrTextTestCaseGenerated.java | 6 ++ 22 files changed, 215 insertions(+), 60 deletions(-) create mode 100644 compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InlineSafeCallChains.kt create mode 100644 compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/ReplaceTemporaryVariable.kt create mode 100644 compiler/testData/ir/irText/chainOfSafeCalls.kt create mode 100644 compiler/testData/ir/irText/chainOfSafeCalls.txt diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/Psi2IrTranslator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/Psi2IrTranslator.kt index 59936c48830..e2b4f6ae714 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/Psi2IrTranslator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/Psi2IrTranslator.kt @@ -23,25 +23,28 @@ import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext import org.jetbrains.kotlin.psi2ir.generators.ModuleGenerator import org.jetbrains.kotlin.psi2ir.transformations.foldStringConcatenation import org.jetbrains.kotlin.psi2ir.transformations.inlineDesugaredBlocks +import org.jetbrains.kotlin.psi2ir.transformations.inlineSafeCallChains import org.jetbrains.kotlin.psi2ir.transformations.insertImplicitCasts import org.jetbrains.kotlin.resolve.BindingContext class Psi2IrTranslator(val configuration: Configuration = Configuration()) { class Configuration( val shouldInlineDesugaredBlocks: Boolean = false, - val shouldFoldStringConcatenation: Boolean = true + val shouldFoldStringConcatenation: Boolean = true, + val shouldInlineSafeCallChains: Boolean = true ) fun generateModule(moduleDescriptor: ModuleDescriptor, ktFiles: List, bindingContext: BindingContext): IrModule { - val irGeneratorContext = GeneratorContext(moduleDescriptor, bindingContext) - val irModule = ModuleGenerator(irGeneratorContext).generateModule(ktFiles) - postprocess(irModule) + val context = GeneratorContext(moduleDescriptor, bindingContext) + val irModule = ModuleGenerator(context).generateModule(ktFiles) + postprocess(irModule, context) return irModule } - private fun postprocess(irModule: IrModule) { + private fun postprocess(irModule: IrModule, context: GeneratorContext) { insertImplicitCasts(irModule.irBuiltins.builtIns, irModule) if (configuration.shouldInlineDesugaredBlocks) inlineDesugaredBlocks(irModule) if (configuration.shouldFoldStringConcatenation) foldStringConcatenation(irModule) + if (configuration.shouldInlineSafeCallChains) inlineSafeCallChains(context, irModule) } } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/BranchingExpressionGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/BranchingExpressionGenerator.kt index f5d63e96d30..8124dfaa0aa 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/BranchingExpressionGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/BranchingExpressionGenerator.kt @@ -93,7 +93,7 @@ class BranchingExpressionGenerator(val statementGenerator: StatementGenerator) : generateWhenConditionWithSubject(ktCondition, irSubject) else generateWhenConditionNoSubject(ktCondition) - irBranchCondition = irBranchCondition?.let { whenComma(it, irCondition) } ?: irCondition + irBranchCondition = irBranchCondition?.let { context.whenComma(it, irCondition) } ?: irCondition } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGeneration.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGeneration.kt index 4fcaf305c46..d992d84e290 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGeneration.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGeneration.kt @@ -25,10 +25,10 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall import org.jetbrains.kotlin.resolve.calls.model.* import org.jetbrains.kotlin.resolve.scopes.receivers.* -fun StatementGenerator.generateReceiverOrNull(ktDefaultElement: KtElement, receiver: ReceiverValue?): Value? = +fun StatementGenerator.generateReceiverOrNull(ktDefaultElement: KtElement, receiver: ReceiverValue?): IntermediateValue? = receiver?.let { generateReceiver(ktDefaultElement, receiver) } -fun StatementGenerator.generateReceiver(ktDefaultElement: KtElement, receiver: ReceiverValue): Value { +fun StatementGenerator.generateReceiver(ktDefaultElement: KtElement, receiver: ReceiverValue): IntermediateValue { if (receiver is TransientReceiver) { return TransientReceiverValue(ktDefaultElement.text, receiver.type) } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGenerator.kt index 2de32662b53..613418d14c7 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/CallGenerator.kt @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi2ir.intermediate.CallBuilder import org.jetbrains.kotlin.psi2ir.intermediate.getValueArgumentsInParameterOrder import org.jetbrains.kotlin.psi2ir.intermediate.isValueArgumentReorderingRequired -import org.jetbrains.kotlin.psi2ir.intermediate.Value +import org.jetbrains.kotlin.psi2ir.intermediate.IntermediateValue import org.jetbrains.kotlin.psi2ir.intermediate.createRematerializableOrTemporary import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument import org.jetbrains.kotlin.types.KotlinType @@ -126,7 +126,7 @@ class CallGenerator( valueArgumentsToValueParameters[valueArgument] = valueParameter } - val irArgumentValues = HashMap() + val irArgumentValues = HashMap() for (valueArgument in valueArgumentsInEvaluationOrder) { val valueParameter = valueArgumentsToValueParameters[valueArgument]!! diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/OperatorExpressionGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/OperatorExpressionGenerator.kt index faec6fca417..21a03435d4d 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/OperatorExpressionGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/OperatorExpressionGenerator.kt @@ -121,7 +121,7 @@ class OperatorExpressionGenerator( val irArgument0Value = createRematerializableOrTemporary(scope, irArgument0, irBlock, "elvis_lhs") irBlock.addStatement(IrIfThenElseImpl( expression.startOffset, expression.endOffset, returnType, - equalsNull(expression.startOffset, expression.endOffset, irArgument0Value.load()), + context.equalsNull(expression.startOffset, expression.endOffset, irArgument0Value.load()), irArgument1, irArgument0Value.load() )) @@ -133,9 +133,9 @@ class OperatorExpressionGenerator( val irArgument1 = statementGenerator.generateExpression(expression.right!!) return when (irOperator) { IrOperator.OROR -> - oror(expression.startOffset, expression.endOffset, irArgument0, irArgument1) + context.oror(expression.startOffset, expression.endOffset, irArgument0, irArgument1) IrOperator.ANDAND -> - andand(expression.startOffset, expression.endOffset, irArgument0, irArgument1) + context.andand(expression.startOffset, expression.endOffset, irArgument0, irArgument1) else -> throw AssertionError("Unexpected binary boolean operator $irOperator") } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/Primitives.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/Primitives.kt index e4723e5c9f9..5a5d088acac 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/Primitives.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/Primitives.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.psi2ir.generators import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.expressions.* fun primitiveOp1(startOffset: Int, endOffset: Int, primitiveOpDescriptor: CallableDescriptor, irOperator: IrOperator, @@ -27,30 +28,30 @@ fun primitiveOp2(startOffset: Int, endOffset: Int, primitiveOpDescriptor: Callab argument1: IrExpression, argument2: IrExpression): IrExpression = IrBinaryPrimitiveImpl(startOffset, endOffset, irOperator, primitiveOpDescriptor, argument1, argument2) -fun Generator.constNull(startOffset: Int, endOffset: Int): IrExpression = - IrConstImpl.constNull(startOffset, endOffset, context.builtIns.nullableNothingType) +fun GeneratorContext.constNull(startOffset: Int, endOffset: Int): IrExpression = + IrConstImpl.constNull(startOffset, endOffset, builtIns.nullableNothingType) -fun Generator.equalsNull(startOffset: Int, endOffset: Int, argument: IrExpression): IrExpression = - primitiveOp2(startOffset, endOffset, context.irBuiltIns.eqeq, IrOperator.EQEQ, +fun GeneratorContext.equalsNull(startOffset: Int, endOffset: Int, argument: IrExpression): IrExpression = + primitiveOp2(startOffset, endOffset, irBuiltIns.eqeq, IrOperator.EQEQ, argument, constNull(startOffset, endOffset)) // a || b == if (a) true else b -fun Generator.oror(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.OROR): IrWhen = - IrIfThenElseImpl(startOffset, endOffset, context.builtIns.booleanType, +fun GeneratorContext.oror(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.OROR): IrWhen = + IrIfThenElseImpl(startOffset, endOffset, builtIns.booleanType, a, IrConstImpl.constTrue(b.startOffset, b.endOffset, b.type!!), b, operator) -fun Generator.oror(a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.OROR): IrWhen = +fun GeneratorContext.oror(a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.OROR): IrWhen = oror(b.startOffset, b.endOffset, a, b, operator) -fun Generator.whenComma(a: IrExpression, b: IrExpression): IrWhen = +fun GeneratorContext.whenComma(a: IrExpression, b: IrExpression): IrWhen = oror(a, b, IrOperator.WHEN_COMMA) // a && b == if (a) b else false -fun Generator.andand(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.ANDAND): IrWhen = - IrIfThenElseImpl(startOffset, endOffset, context.builtIns.booleanType, +fun GeneratorContext.andand(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.ANDAND): IrWhen = + IrIfThenElseImpl(startOffset, endOffset, builtIns.booleanType, a, b, IrConstImpl.constFalse(b.startOffset, b.endOffset, b.type!!), operator) -fun Generator.andand(a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.ANDAND): IrWhen = +fun GeneratorContext.andand(a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.ANDAND): IrWhen = andand(b.startOffset, b.endOffset, a, b, operator) \ No newline at end of file diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt index 4f9f4baf8b9..e8ce1038106 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/StatementGenerator.kt @@ -82,7 +82,7 @@ class StatementGenerator( return irBlock } - fun declareComponentVariablesInBlock(multiDeclaration: KtDestructuringDeclaration, irBlock: IrBlockImpl, containerValue: Value) { + fun declareComponentVariablesInBlock(multiDeclaration: KtDestructuringDeclaration, irBlock: IrBlockImpl, containerValue: IntermediateValue) { val callGenerator = CallGenerator(this) for ((index, ktEntry) in multiDeclaration.entries.withIndex()) { val componentResolvedCall = getOrFail(BindingContext.COMPONENT_RESOLVED_CALL, ktEntry) diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/ArrayAccessAssignmentReceiver.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/ArrayAccessAssignmentReceiver.kt index 82ff6b1ee27..b2ec980cb99 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/ArrayAccessAssignmentReceiver.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/ArrayAccessAssignmentReceiver.kt @@ -64,7 +64,7 @@ class ArrayAccessAssignmentReceiver( return callGenerator.generateCall(startOffset, endOffset, indexedSetCall, IrOperator.EQ) } - private fun CallBuilder.fillArrayAndIndexArguments(arrayValue: Value, indexValues: List) { + private fun CallBuilder.fillArrayAndIndexArguments(arrayValue: IntermediateValue, indexValues: List) { setExplicitReceiverValue(arrayValue) indexValues.forEachIndexed { i, irIndexValue -> irValueArgumentsByIndex[i] = irIndexValue.load() diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/CallBuilder.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/CallBuilder.kt index 74f713b04d0..f85eff239a1 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/CallBuilder.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/CallBuilder.kt @@ -71,10 +71,10 @@ val CallBuilder.explicitReceiverParameter: ReceiverParameterDescriptor? get() = val CallBuilder.explicitReceiverType: KotlinType? get() = explicitReceiverParameter?.type -fun CallBuilder.setExplicitReceiverValue(explicitReceiverValue: Value) { +fun CallBuilder.setExplicitReceiverValue(explicitReceiverValue: IntermediateValue) { val previousCallReceiver = callReceiver callReceiver = object : CallReceiver { - override fun call(withDispatchAndExtensionReceivers: (Value?, Value?) -> IrExpression): IrExpression { + override fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression { return previousCallReceiver.call { dispatchReceiverValue, extensionReceiverValue -> val newDispatchReceiverValue = if (hasExtensionReceiver) dispatchReceiverValue else explicitReceiverValue val newExtensionReceiverValue = if (hasExtensionReceiver) explicitReceiverValue else null diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceCallValue.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceCallValue.kt index d63b089535e..9a752ebcaa8 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceCallValue.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceCallValue.kt @@ -29,7 +29,7 @@ class OnceCallValue( val statementGenerator: StatementGenerator, val call: CallBuilder, val operator: IrOperator? = null -): Value { +): IntermediateValue { private var instantiated = false override fun load(): IrExpression { diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceExpressionValue.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceExpressionValue.kt index 738d87a8d1d..3bdb4ac6599 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceExpressionValue.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/OnceExpressionValue.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.psi2ir.generators.CallGenerator import org.jetbrains.kotlin.types.KotlinType -class OnceExpressionValue(val irExpression: IrExpression) : Value { +class OnceExpressionValue(val irExpression: IrExpression) : IntermediateValue { init { irExpression.assertDetached() } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/RematerializableValue.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/RematerializableValue.kt index 7ab24199ed2..18f808f18b6 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/RematerializableValue.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/RematerializableValue.kt @@ -21,14 +21,14 @@ import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.psi2ir.generators.Scope import org.jetbrains.kotlin.types.KotlinType -class RematerializableValue(val irExpression: IrExpressionWithCopy) : Value { +class RematerializableValue(val irExpression: IrExpressionWithCopy) : IntermediateValue { override val type: KotlinType? get() = irExpression.type override fun load(): IrExpression = irExpression.copy() } -fun createRematerializableValue(irExpression: IrExpression): Value? = +fun createRematerializableValue(irExpression: IrExpression): IntermediateValue? = (irExpression as? IrExpressionWithCopy)?.let { RematerializableValue(it) } inline fun createRematerializableOrTemporary( @@ -36,7 +36,7 @@ inline fun createRematerializableOrTemporary( irExpression: IrExpression, nameHint: String? = null, addVariable: (IrVariable) -> Unit -): Value { +): IntermediateValue { val rematerializable = createRematerializableValue(irExpression) if (rematerializable != null) { return rematerializable @@ -47,7 +47,7 @@ inline fun createRematerializableOrTemporary( return VariableLValue(temporaryVariable) } -fun createRematerializableOrTemporary(scope: Scope, irExpression: IrExpression, block: IrBlockImpl, nameHint: String? = null): Value = +fun createRematerializableOrTemporary(scope: Scope, irExpression: IrExpression, block: IrBlockImpl, nameHint: String? = null): IntermediateValue = createRematerializableOrTemporary(scope, irExpression, nameHint) { block.addStatement(it) } \ 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 9e66cd36e65..628b789d59f 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 @@ -31,14 +31,14 @@ class SafeCallReceiver( val startOffset: Int, val endOffset: Int, val explicitReceiver: IrExpression, - val implicitDispatchReceiverValue: Value? + val implicitDispatchReceiverValue: IntermediateValue? ) : CallReceiver { - override fun call(withDispatchAndExtensionReceivers: (Value?, Value?) -> IrExpression): IrExpression { + override fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression { val irTmp = generator.scope.createTemporaryVariable(explicitReceiver, "safe_receiver") val safeReceiverValue = VariableLValue(irTmp) - val dispatchReceiverValue: Value - val extensionReceiverValue: Value? + val dispatchReceiverValue: IntermediateValue + val extensionReceiverValue: IntermediateValue? if (implicitDispatchReceiverValue != null) { dispatchReceiverValue = implicitDispatchReceiverValue extensionReceiverValue = safeReceiverValue @@ -56,8 +56,8 @@ class SafeCallReceiver( irBlock.addStatement(irTmp) val irIfThenElse = IrIfThenElseImpl(startOffset, endOffset, resultType, - generator.equalsNull(startOffset, endOffset, safeReceiverValue.load()), - generator.constNull(startOffset, endOffset), + generator.context.equalsNull(startOffset, endOffset, safeReceiverValue.load()), + generator.context.constNull(startOffset, endOffset), irResult, IrOperator.SAFE_CALL) irBlock.addStatement(irIfThenElse) diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SimpleCallReceiver.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SimpleCallReceiver.kt index bbd345c93ee..3ae8f470755 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SimpleCallReceiver.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/SimpleCallReceiver.kt @@ -19,10 +19,10 @@ package org.jetbrains.kotlin.psi2ir.intermediate import org.jetbrains.kotlin.ir.expressions.IrExpression class SimpleCallReceiver( - val dispatchReceiverValue: Value?, - val extensionReceiverValue: Value? + val dispatchReceiverValue: IntermediateValue?, + val extensionReceiverValue: IntermediateValue? ) : CallReceiver { - override fun call(withDispatchAndExtensionReceivers: (Value?, Value?) -> IrExpression): IrExpression { + override fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression { return withDispatchAndExtensionReceivers(dispatchReceiverValue, extensionReceiverValue) } } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/TransientReceiverValue.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/TransientReceiverValue.kt index e7662b22885..2d3979c9dbc 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/TransientReceiverValue.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/TransientReceiverValue.kt @@ -19,7 +19,7 @@ package org.jetbrains.kotlin.psi2ir.intermediate import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.types.KotlinType -class TransientReceiverValue(val description: String, override val type: KotlinType?): Value { +class TransientReceiverValue(val description: String, override val type: KotlinType?): IntermediateValue { override fun load(): IrExpression { throw AssertionError("Transient receiver should not be instantiated: $description") } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/Values.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/Values.kt index 5814e8cb2c2..2dfbb916dff 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/Values.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/intermediate/Values.kt @@ -19,12 +19,12 @@ package org.jetbrains.kotlin.psi2ir.intermediate import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.types.KotlinType -interface Value { +interface IntermediateValue { fun load(): IrExpression val type: KotlinType? } -interface LValue : Value { +interface LValue : IntermediateValue { fun store(irExpression: IrExpression): IrExpression } @@ -34,5 +34,5 @@ interface AssignmentReceiver { } interface CallReceiver { - fun call(withDispatchAndExtensionReceivers: (Value?, Value?) -> IrExpression): IrExpression + fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InlineSafeCallChains.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InlineSafeCallChains.kt new file mode 100644 index 00000000000..fc53a4c04ad --- /dev/null +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InlineSafeCallChains.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.psi2ir.transformations + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.detach +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.replaceWith +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.psi2ir.containsNull +import org.jetbrains.kotlin.psi2ir.defaultLoad +import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext +import org.jetbrains.kotlin.psi2ir.generators.constNull +import org.jetbrains.kotlin.psi2ir.generators.equalsNull +import org.jetbrains.kotlin.psi2ir.intermediate.OnceExpressionValue + +fun inlineSafeCallChains(context: GeneratorContext, element: IrElement) { + element.accept(InlineSafeCallChains(context), null) +} + +class InlineSafeCallChains(val context: GeneratorContext) : IrElementVisitor { + override fun visitElement(element: IrElement, data: Nothing?) { + element.acceptChildren(this, data) + } + + override fun visitBlock(expression: IrBlock, data: Nothing?) { + expression.acceptChildren(this, data) + + if (expression.operator == IrOperator.SAFE_CALL) { + val safeCall = getSafeCallInfo(expression) ?: return + val innerSafeCall = (safeCall.receiverValue as? IrBlock)?.let { getSafeCallInfo(it) } ?: return + rewriteSafeCallChain(safeCall, innerSafeCall) + } + } + + private fun rewriteSafeCallChain(outer: SafeCallInfo, inner: SafeCallInfo) { + val innerNestedCallReturnType = inner.nestedCall.type ?: return + if (innerNestedCallReturnType.containsNull()) return + + outer.root.replaceWith { + val newBlock = IrBlockImpl(it.startOffset, it.endOffset, it.type, it.hasResult, IrOperator.SAFE_CALL) + newBlock.addStatement(inner.receiverVariable.detach()) + outer.nestedCall.acceptChildren( + ReplaceTemporaryVariable(outer.receiverVariable, OnceExpressionValue(inner.nestedCall.detach())), + null) + newBlock.addStatement(IrIfThenElseImpl( + it.startOffset, it.endOffset, it.type, + context.equalsNull(it.startOffset, it.endOffset, inner.receiverVariable.defaultLoad()), + context.constNull(it.startOffset, it.endOffset), + outer.nestedCall.detach(), + IrOperator.SAFE_CALL)) + newBlock + } + } + + private class SafeCallInfo(val root: IrBlock, val receiverVariable: IrVariable, val nestedCall: IrExpression) { + val receiverValue = receiverVariable.initializer + } + + private fun getSafeCallInfo(block: IrBlock): SafeCallInfo? { + if (block.operator != IrOperator.SAFE_CALL) return null + val receiverVariable = block.statements[0] as? IrVariable ?: return null + if (receiverVariable.initializer == null) return null + val nestedCall = (block.statements[1] as? IrWhen)?.elseBranch ?: return null + return SafeCallInfo(block, receiverVariable, nestedCall) + } +} + diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/ReplaceTemporaryVariable.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/ReplaceTemporaryVariable.kt new file mode 100644 index 00000000000..bc2d5987644 --- /dev/null +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/ReplaceTemporaryVariable.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.psi2ir.transformations + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.IrGetVariable +import org.jetbrains.kotlin.ir.replaceWith +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.psi2ir.intermediate.IntermediateValue + +class ReplaceTemporaryVariable(val from: IrVariable, val to: IntermediateValue) : IrElementVisitor { + override fun visitElement(element: IrElement, data: Nothing?) { + element.acceptChildren(this, data) + } + + override fun visitGetVariable(expression: IrGetVariable, data: Nothing?) { + if (expression.descriptor == from.descriptor) { + expression.replaceWith { to.load() } + } + } +} \ No newline at end of file diff --git a/compiler/testData/ir/irText/chainOfSafeCalls.kt b/compiler/testData/ir/irText/chainOfSafeCalls.kt new file mode 100644 index 00000000000..89242c63126 --- /dev/null +++ b/compiler/testData/ir/irText/chainOfSafeCalls.kt @@ -0,0 +1,7 @@ +class C { + fun foo(): C = this + fun bar(): C? = this +} + +fun test(nc: C?) = + nc?.foo()?.bar()?.foo()?.foo() \ No newline at end of file diff --git a/compiler/testData/ir/irText/chainOfSafeCalls.txt b/compiler/testData/ir/irText/chainOfSafeCalls.txt new file mode 100644 index 00000000000..764373f69cb --- /dev/null +++ b/compiler/testData/ir/irText/chainOfSafeCalls.txt @@ -0,0 +1,27 @@ +IrFile /chainOfSafeCalls.kt + DUMMY C + IrFunction public fun test(/*0*/ nc: C?): C? + IrExpressionBody + BLOCK type= hasResult=false operator=null + RETURN type= + BLOCK type=C? hasResult=true operator=SAFE_CALL + VAR val tmp2_safe_receiver: C? + BLOCK type=C? hasResult=true operator=SAFE_CALL + VAR val tmp0_safe_receiver: C? + GET_VAR nc type=C? operator=null + WHEN type=C? operator=SAFE_CALL + if: CALL .EQEQ type=kotlin.Boolean operator=EQEQ + arg0: GET_VAR tmp0_safe_receiver type=C? operator=null + arg1: CONST Null type=kotlin.Nothing? value='null' + then: CONST Null type=kotlin.Nothing? value='null' + else: CALL .bar type=C? operator=null + $this: CALL .foo type=C operator=null + $this: GET_VAR tmp0_safe_receiver type=C? operator=null + WHEN type=C? operator=SAFE_CALL + if: CALL .EQEQ type=kotlin.Boolean operator=EQEQ + arg0: GET_VAR tmp2_safe_receiver type=C? operator=null + arg1: CONST Null type=kotlin.Nothing? value='null' + then: CONST Null type=kotlin.Nothing? value='null' + else: CALL .foo type=C operator=null + $this: CALL .foo type=C operator=null + $this: GET_VAR tmp2_safe_receiver type=C? operator=null diff --git a/compiler/testData/ir/irText/variableAsFunctionCall.txt b/compiler/testData/ir/irText/variableAsFunctionCall.txt index d7b51bd9680..967ad1336db 100644 --- a/compiler/testData/ir/irText/variableAsFunctionCall.txt +++ b/compiler/testData/ir/irText/variableAsFunctionCall.txt @@ -29,21 +29,13 @@ IrFile /variableAsFunctionCall.kt BLOCK type= hasResult=false operator=null RETURN type= BLOCK type=kotlin.String? hasResult=true operator=SAFE_CALL - VAR val tmp1_safe_receiver: (() -> kotlin.String)? - BLOCK type=(() -> kotlin.String)? hasResult=true operator=SAFE_CALL - VAR val tmp0_safe_receiver: kotlin.String? - GET_VAR ns type=kotlin.String? operator=null - WHEN type=(() -> kotlin.String)? operator=SAFE_CALL - if: CALL .EQEQ type=kotlin.Boolean operator=EQEQ - arg0: GET_VAR tmp0_safe_receiver type=kotlin.String? operator=null - arg1: CONST Null type=kotlin.Nothing? value='null' - then: CONST Null type=kotlin.Nothing? value='null' - else: CALL .k type=() -> kotlin.String operator=null - $this: GET_VAR tmp0_safe_receiver type=kotlin.String? operator=null + VAR val tmp0_safe_receiver: kotlin.String? + GET_VAR ns type=kotlin.String? operator=null WHEN type=kotlin.String? operator=SAFE_CALL if: CALL .EQEQ type=kotlin.Boolean operator=EQEQ - arg0: GET_VAR tmp1_safe_receiver type=(() -> kotlin.String)? operator=null + arg0: GET_VAR tmp0_safe_receiver type=kotlin.String? operator=null arg1: CONST Null type=kotlin.Nothing? value='null' then: CONST Null type=kotlin.Nothing? value='null' else: CALL .invoke type=kotlin.String operator=null - $this: GET_VAR tmp1_safe_receiver type=(() -> kotlin.String)? operator=null + $this: CALL .k type=() -> kotlin.String operator=null + $this: GET_VAR tmp0_safe_receiver type=kotlin.String? operator=null diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java index 81c3d22a45f..987bb7c8003 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java @@ -107,6 +107,12 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase { doTest(fileName); } + @TestMetadata("chainOfSafeCalls.kt") + public void testChainOfSafeCalls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/chainOfSafeCalls.kt"); + doTest(fileName); + } + @TestMetadata("conventionComparisons.kt") public void testConventionComparisons() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/conventionComparisons.kt");