Inline safe call chains.

This commit is contained in:
Dmitry Petrov
2016-08-25 11:06:46 +03:00
committed by Dmitry Petrov
parent 3f256fab67
commit dc4bb3015c
22 changed files with 215 additions and 60 deletions
@@ -23,25 +23,28 @@ import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
import org.jetbrains.kotlin.psi2ir.generators.ModuleGenerator import org.jetbrains.kotlin.psi2ir.generators.ModuleGenerator
import org.jetbrains.kotlin.psi2ir.transformations.foldStringConcatenation import org.jetbrains.kotlin.psi2ir.transformations.foldStringConcatenation
import org.jetbrains.kotlin.psi2ir.transformations.inlineDesugaredBlocks 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.psi2ir.transformations.insertImplicitCasts
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
class Psi2IrTranslator(val configuration: Configuration = Configuration()) { class Psi2IrTranslator(val configuration: Configuration = Configuration()) {
class Configuration( class Configuration(
val shouldInlineDesugaredBlocks: Boolean = false, val shouldInlineDesugaredBlocks: Boolean = false,
val shouldFoldStringConcatenation: Boolean = true val shouldFoldStringConcatenation: Boolean = true,
val shouldInlineSafeCallChains: Boolean = true
) )
fun generateModule(moduleDescriptor: ModuleDescriptor, ktFiles: List<KtFile>, bindingContext: BindingContext): IrModule { fun generateModule(moduleDescriptor: ModuleDescriptor, ktFiles: List<KtFile>, bindingContext: BindingContext): IrModule {
val irGeneratorContext = GeneratorContext(moduleDescriptor, bindingContext) val context = GeneratorContext(moduleDescriptor, bindingContext)
val irModule = ModuleGenerator(irGeneratorContext).generateModule(ktFiles) val irModule = ModuleGenerator(context).generateModule(ktFiles)
postprocess(irModule) postprocess(irModule, context)
return irModule return irModule
} }
private fun postprocess(irModule: IrModule) { private fun postprocess(irModule: IrModule, context: GeneratorContext) {
insertImplicitCasts(irModule.irBuiltins.builtIns, irModule) insertImplicitCasts(irModule.irBuiltins.builtIns, irModule)
if (configuration.shouldInlineDesugaredBlocks) inlineDesugaredBlocks(irModule) if (configuration.shouldInlineDesugaredBlocks) inlineDesugaredBlocks(irModule)
if (configuration.shouldFoldStringConcatenation) foldStringConcatenation(irModule) if (configuration.shouldFoldStringConcatenation) foldStringConcatenation(irModule)
if (configuration.shouldInlineSafeCallChains) inlineSafeCallChains(context, irModule)
} }
} }
@@ -93,7 +93,7 @@ class BranchingExpressionGenerator(val statementGenerator: StatementGenerator) :
generateWhenConditionWithSubject(ktCondition, irSubject) generateWhenConditionWithSubject(ktCondition, irSubject)
else else
generateWhenConditionNoSubject(ktCondition) generateWhenConditionNoSubject(ktCondition)
irBranchCondition = irBranchCondition?.let { whenComma(it, irCondition) } ?: irCondition irBranchCondition = irBranchCondition?.let { context.whenComma(it, irCondition) } ?: irCondition
} }
@@ -25,10 +25,10 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
import org.jetbrains.kotlin.resolve.calls.model.* import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.scopes.receivers.* 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) } 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) { if (receiver is TransientReceiver) {
return TransientReceiverValue(ktDefaultElement.text, receiver.type) return TransientReceiverValue(ktDefaultElement.text, receiver.type)
} }
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi2ir.intermediate.CallBuilder import org.jetbrains.kotlin.psi2ir.intermediate.CallBuilder
import org.jetbrains.kotlin.psi2ir.intermediate.getValueArgumentsInParameterOrder import org.jetbrains.kotlin.psi2ir.intermediate.getValueArgumentsInParameterOrder
import org.jetbrains.kotlin.psi2ir.intermediate.isValueArgumentReorderingRequired 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.psi2ir.intermediate.createRematerializableOrTemporary
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
@@ -126,7 +126,7 @@ class CallGenerator(
valueArgumentsToValueParameters[valueArgument] = valueParameter valueArgumentsToValueParameters[valueArgument] = valueParameter
} }
val irArgumentValues = HashMap<ValueParameterDescriptor, Value>() val irArgumentValues = HashMap<ValueParameterDescriptor, IntermediateValue>()
for (valueArgument in valueArgumentsInEvaluationOrder) { for (valueArgument in valueArgumentsInEvaluationOrder) {
val valueParameter = valueArgumentsToValueParameters[valueArgument]!! val valueParameter = valueArgumentsToValueParameters[valueArgument]!!
@@ -121,7 +121,7 @@ class OperatorExpressionGenerator(
val irArgument0Value = createRematerializableOrTemporary(scope, irArgument0, irBlock, "elvis_lhs") val irArgument0Value = createRematerializableOrTemporary(scope, irArgument0, irBlock, "elvis_lhs")
irBlock.addStatement(IrIfThenElseImpl( irBlock.addStatement(IrIfThenElseImpl(
expression.startOffset, expression.endOffset, returnType, expression.startOffset, expression.endOffset, returnType,
equalsNull(expression.startOffset, expression.endOffset, irArgument0Value.load()), context.equalsNull(expression.startOffset, expression.endOffset, irArgument0Value.load()),
irArgument1, irArgument1,
irArgument0Value.load() irArgument0Value.load()
)) ))
@@ -133,9 +133,9 @@ class OperatorExpressionGenerator(
val irArgument1 = statementGenerator.generateExpression(expression.right!!) val irArgument1 = statementGenerator.generateExpression(expression.right!!)
return when (irOperator) { return when (irOperator) {
IrOperator.OROR -> IrOperator.OROR ->
oror(expression.startOffset, expression.endOffset, irArgument0, irArgument1) context.oror(expression.startOffset, expression.endOffset, irArgument0, irArgument1)
IrOperator.ANDAND -> IrOperator.ANDAND ->
andand(expression.startOffset, expression.endOffset, irArgument0, irArgument1) context.andand(expression.startOffset, expression.endOffset, irArgument0, irArgument1)
else -> else ->
throw AssertionError("Unexpected binary boolean operator $irOperator") throw AssertionError("Unexpected binary boolean operator $irOperator")
} }
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.psi2ir.generators package org.jetbrains.kotlin.psi2ir.generators
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
fun primitiveOp1(startOffset: Int, endOffset: Int, primitiveOpDescriptor: CallableDescriptor, irOperator: IrOperator, 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 = argument1: IrExpression, argument2: IrExpression): IrExpression =
IrBinaryPrimitiveImpl(startOffset, endOffset, irOperator, primitiveOpDescriptor, argument1, argument2) IrBinaryPrimitiveImpl(startOffset, endOffset, irOperator, primitiveOpDescriptor, argument1, argument2)
fun Generator.constNull(startOffset: Int, endOffset: Int): IrExpression = fun GeneratorContext.constNull(startOffset: Int, endOffset: Int): IrExpression =
IrConstImpl.constNull(startOffset, endOffset, context.builtIns.nullableNothingType) IrConstImpl.constNull(startOffset, endOffset, builtIns.nullableNothingType)
fun Generator.equalsNull(startOffset: Int, endOffset: Int, argument: IrExpression): IrExpression = fun GeneratorContext.equalsNull(startOffset: Int, endOffset: Int, argument: IrExpression): IrExpression =
primitiveOp2(startOffset, endOffset, context.irBuiltIns.eqeq, IrOperator.EQEQ, primitiveOp2(startOffset, endOffset, irBuiltIns.eqeq, IrOperator.EQEQ,
argument, constNull(startOffset, endOffset)) argument, constNull(startOffset, endOffset))
// a || b == if (a) true else b // a || b == if (a) true else b
fun Generator.oror(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.OROR): IrWhen = fun GeneratorContext.oror(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.OROR): IrWhen =
IrIfThenElseImpl(startOffset, endOffset, context.builtIns.booleanType, IrIfThenElseImpl(startOffset, endOffset, builtIns.booleanType,
a, IrConstImpl.constTrue(b.startOffset, b.endOffset, b.type!!), b, a, IrConstImpl.constTrue(b.startOffset, b.endOffset, b.type!!), b,
operator) 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) 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) oror(a, b, IrOperator.WHEN_COMMA)
// a && b == if (a) b else false // a && b == if (a) b else false
fun Generator.andand(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.ANDAND): IrWhen = fun GeneratorContext.andand(startOffset: Int, endOffset: Int, a: IrExpression, b: IrExpression, operator: IrOperator = IrOperator.ANDAND): IrWhen =
IrIfThenElseImpl(startOffset, endOffset, context.builtIns.booleanType, IrIfThenElseImpl(startOffset, endOffset, builtIns.booleanType,
a, b, IrConstImpl.constFalse(b.startOffset, b.endOffset, b.type!!), a, b, IrConstImpl.constFalse(b.startOffset, b.endOffset, b.type!!),
operator) 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) andand(b.startOffset, b.endOffset, a, b, operator)
@@ -82,7 +82,7 @@ class StatementGenerator(
return irBlock return irBlock
} }
fun declareComponentVariablesInBlock(multiDeclaration: KtDestructuringDeclaration, irBlock: IrBlockImpl, containerValue: Value) { fun declareComponentVariablesInBlock(multiDeclaration: KtDestructuringDeclaration, irBlock: IrBlockImpl, containerValue: IntermediateValue) {
val callGenerator = CallGenerator(this) val callGenerator = CallGenerator(this)
for ((index, ktEntry) in multiDeclaration.entries.withIndex()) { for ((index, ktEntry) in multiDeclaration.entries.withIndex()) {
val componentResolvedCall = getOrFail(BindingContext.COMPONENT_RESOLVED_CALL, ktEntry) val componentResolvedCall = getOrFail(BindingContext.COMPONENT_RESOLVED_CALL, ktEntry)
@@ -64,7 +64,7 @@ class ArrayAccessAssignmentReceiver(
return callGenerator.generateCall(startOffset, endOffset, indexedSetCall, IrOperator.EQ) return callGenerator.generateCall(startOffset, endOffset, indexedSetCall, IrOperator.EQ)
} }
private fun CallBuilder.fillArrayAndIndexArguments(arrayValue: Value, indexValues: List<Value>) { private fun CallBuilder.fillArrayAndIndexArguments(arrayValue: IntermediateValue, indexValues: List<IntermediateValue>) {
setExplicitReceiverValue(arrayValue) setExplicitReceiverValue(arrayValue)
indexValues.forEachIndexed { i, irIndexValue -> indexValues.forEachIndexed { i, irIndexValue ->
irValueArgumentsByIndex[i] = irIndexValue.load() irValueArgumentsByIndex[i] = irIndexValue.load()
@@ -71,10 +71,10 @@ val CallBuilder.explicitReceiverParameter: ReceiverParameterDescriptor? get() =
val CallBuilder.explicitReceiverType: KotlinType? get() = val CallBuilder.explicitReceiverType: KotlinType? get() =
explicitReceiverParameter?.type explicitReceiverParameter?.type
fun CallBuilder.setExplicitReceiverValue(explicitReceiverValue: Value) { fun CallBuilder.setExplicitReceiverValue(explicitReceiverValue: IntermediateValue) {
val previousCallReceiver = callReceiver val previousCallReceiver = callReceiver
callReceiver = object : 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 -> return previousCallReceiver.call { dispatchReceiverValue, extensionReceiverValue ->
val newDispatchReceiverValue = if (hasExtensionReceiver) dispatchReceiverValue else explicitReceiverValue val newDispatchReceiverValue = if (hasExtensionReceiver) dispatchReceiverValue else explicitReceiverValue
val newExtensionReceiverValue = if (hasExtensionReceiver) explicitReceiverValue else null val newExtensionReceiverValue = if (hasExtensionReceiver) explicitReceiverValue else null
@@ -29,7 +29,7 @@ class OnceCallValue(
val statementGenerator: StatementGenerator, val statementGenerator: StatementGenerator,
val call: CallBuilder, val call: CallBuilder,
val operator: IrOperator? = null val operator: IrOperator? = null
): Value { ): IntermediateValue {
private var instantiated = false private var instantiated = false
override fun load(): IrExpression { override fun load(): IrExpression {
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.psi2ir.generators.CallGenerator import org.jetbrains.kotlin.psi2ir.generators.CallGenerator
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
class OnceExpressionValue(val irExpression: IrExpression) : Value { class OnceExpressionValue(val irExpression: IrExpression) : IntermediateValue {
init { init {
irExpression.assertDetached() irExpression.assertDetached()
} }
@@ -21,14 +21,14 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.psi2ir.generators.Scope import org.jetbrains.kotlin.psi2ir.generators.Scope
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
class RematerializableValue(val irExpression: IrExpressionWithCopy) : Value { class RematerializableValue(val irExpression: IrExpressionWithCopy) : IntermediateValue {
override val type: KotlinType? override val type: KotlinType?
get() = irExpression.type get() = irExpression.type
override fun load(): IrExpression = irExpression.copy() override fun load(): IrExpression = irExpression.copy()
} }
fun createRematerializableValue(irExpression: IrExpression): Value? = fun createRematerializableValue(irExpression: IrExpression): IntermediateValue? =
(irExpression as? IrExpressionWithCopy)?.let { RematerializableValue(it) } (irExpression as? IrExpressionWithCopy)?.let { RematerializableValue(it) }
inline fun createRematerializableOrTemporary( inline fun createRematerializableOrTemporary(
@@ -36,7 +36,7 @@ inline fun createRematerializableOrTemporary(
irExpression: IrExpression, irExpression: IrExpression,
nameHint: String? = null, nameHint: String? = null,
addVariable: (IrVariable) -> Unit addVariable: (IrVariable) -> Unit
): Value { ): IntermediateValue {
val rematerializable = createRematerializableValue(irExpression) val rematerializable = createRematerializableValue(irExpression)
if (rematerializable != null) { if (rematerializable != null) {
return rematerializable return rematerializable
@@ -47,7 +47,7 @@ inline fun createRematerializableOrTemporary(
return VariableLValue(temporaryVariable) 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) { createRematerializableOrTemporary(scope, irExpression, nameHint) {
block.addStatement(it) block.addStatement(it)
} }
@@ -31,14 +31,14 @@ class SafeCallReceiver(
val startOffset: Int, val startOffset: Int,
val endOffset: Int, val endOffset: Int,
val explicitReceiver: IrExpression, val explicitReceiver: IrExpression,
val implicitDispatchReceiverValue: Value? val implicitDispatchReceiverValue: IntermediateValue?
) : CallReceiver { ) : 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 irTmp = generator.scope.createTemporaryVariable(explicitReceiver, "safe_receiver")
val safeReceiverValue = VariableLValue(irTmp) val safeReceiverValue = VariableLValue(irTmp)
val dispatchReceiverValue: Value val dispatchReceiverValue: IntermediateValue
val extensionReceiverValue: Value? val extensionReceiverValue: IntermediateValue?
if (implicitDispatchReceiverValue != null) { if (implicitDispatchReceiverValue != null) {
dispatchReceiverValue = implicitDispatchReceiverValue dispatchReceiverValue = implicitDispatchReceiverValue
extensionReceiverValue = safeReceiverValue extensionReceiverValue = safeReceiverValue
@@ -56,8 +56,8 @@ class SafeCallReceiver(
irBlock.addStatement(irTmp) irBlock.addStatement(irTmp)
val irIfThenElse = IrIfThenElseImpl(startOffset, endOffset, resultType, val irIfThenElse = IrIfThenElseImpl(startOffset, endOffset, resultType,
generator.equalsNull(startOffset, endOffset, safeReceiverValue.load()), generator.context.equalsNull(startOffset, endOffset, safeReceiverValue.load()),
generator.constNull(startOffset, endOffset), generator.context.constNull(startOffset, endOffset),
irResult, irResult,
IrOperator.SAFE_CALL) IrOperator.SAFE_CALL)
irBlock.addStatement(irIfThenElse) irBlock.addStatement(irIfThenElse)
@@ -19,10 +19,10 @@ package org.jetbrains.kotlin.psi2ir.intermediate
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpression
class SimpleCallReceiver( class SimpleCallReceiver(
val dispatchReceiverValue: Value?, val dispatchReceiverValue: IntermediateValue?,
val extensionReceiverValue: Value? val extensionReceiverValue: IntermediateValue?
) : CallReceiver { ) : CallReceiver {
override fun call(withDispatchAndExtensionReceivers: (Value?, Value?) -> IrExpression): IrExpression { override fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression {
return withDispatchAndExtensionReceivers(dispatchReceiverValue, extensionReceiverValue) return withDispatchAndExtensionReceivers(dispatchReceiverValue, extensionReceiverValue)
} }
} }
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.psi2ir.intermediate
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.types.KotlinType 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 { override fun load(): IrExpression {
throw AssertionError("Transient receiver should not be instantiated: $description") throw AssertionError("Transient receiver should not be instantiated: $description")
} }
@@ -19,12 +19,12 @@ package org.jetbrains.kotlin.psi2ir.intermediate
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
interface Value { interface IntermediateValue {
fun load(): IrExpression fun load(): IrExpression
val type: KotlinType? val type: KotlinType?
} }
interface LValue : Value { interface LValue : IntermediateValue {
fun store(irExpression: IrExpression): IrExpression fun store(irExpression: IrExpression): IrExpression
} }
@@ -34,5 +34,5 @@ interface AssignmentReceiver {
} }
interface CallReceiver { interface CallReceiver {
fun call(withDispatchAndExtensionReceivers: (Value?, Value?) -> IrExpression): IrExpression fun call(withDispatchAndExtensionReceivers: (IntermediateValue?, IntermediateValue?) -> IrExpression): IrExpression
} }
@@ -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<Unit, Nothing?> {
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)
}
}
@@ -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<Unit, Nothing?> {
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() }
}
}
}
+7
View File
@@ -0,0 +1,7 @@
class C {
fun foo(): C = this
fun bar(): C? = this
}
fun test(nc: C?) =
nc?.foo()?.bar()?.foo()?.foo()
+27
View File
@@ -0,0 +1,27 @@
IrFile /chainOfSafeCalls.kt
DUMMY C
IrFunction public fun test(/*0*/ nc: C?): C?
IrExpressionBody
BLOCK type=<no-type> hasResult=false operator=null
RETURN type=<no-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
+5 -13
View File
@@ -29,21 +29,13 @@ IrFile /variableAsFunctionCall.kt
BLOCK type=<no-type> hasResult=false operator=null BLOCK type=<no-type> hasResult=false operator=null
RETURN type=<no-type> RETURN type=<no-type>
BLOCK type=kotlin.String? hasResult=true operator=SAFE_CALL BLOCK type=kotlin.String? hasResult=true operator=SAFE_CALL
VAR val tmp1_safe_receiver: (() -> kotlin.String)? VAR val tmp0_safe_receiver: kotlin.String?
BLOCK type=(() -> kotlin.String)? hasResult=true operator=SAFE_CALL GET_VAR ns 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 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
WHEN type=kotlin.String? operator=SAFE_CALL WHEN type=kotlin.String? operator=SAFE_CALL
if: CALL .EQEQ type=kotlin.Boolean operator=EQEQ 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' arg1: CONST Null type=kotlin.Nothing? value='null'
then: CONST Null type=kotlin.Nothing? value='null' then: CONST Null type=kotlin.Nothing? value='null'
else: CALL .invoke type=kotlin.String operator=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
@@ -107,6 +107,12 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase {
doTest(fileName); 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") @TestMetadata("conventionComparisons.kt")
public void testConventionComparisons() throws Exception { public void testConventionComparisons() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/conventionComparisons.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/conventionComparisons.kt");