diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/coroutines/AddContinuationToFunctionsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/coroutines/AddContinuationToFunctionsLowering.kt index 6d24523a1a4..9c24ded402c 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/coroutines/AddContinuationToFunctionsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/coroutines/AddContinuationToFunctionsLowering.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name /** * Replaces suspend functions with regular non-suspend functions with additional @@ -78,7 +79,13 @@ private fun transformSuspendFunction(context: CommonBackendContext, function: Ir newBody.statements.lastOrNull() !is IrReturn ) { // Adding explicit return of Unit. - newBody.statements += context.createIrBuilder(newFunctionWithContinuation.symbol).irReturnUnit() + // Set both offsets of the IrReturn to body.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression + // could be hit. + newBody.statements += context.createIrBuilder( + newFunctionWithContinuation.symbol, + startOffset = newBody.endOffset - 1, + endOffset = newBody.endOffset - 1 + ).irReturnUnit() } newFunctionWithContinuation.body = newBody @@ -123,11 +130,13 @@ private fun IrSimpleFunction.createSuspendFunctionStub(context: CommonBackendCon val remapper = ValueRemapper(mapping) function.valueParameters.forEach { it.defaultValue = it.defaultValue?.transform(remapper, null) } - function.addValueParameter( - "\$cont", - continuationType(context).substitute(substitutionMap), - IrDeclarationOrigin.CONTINUATION - ) + function.addValueParameter { + startOffset = function.startOffset + endOffset = function.endOffset + origin = IrDeclarationOrigin.CONTINUATION + name = Name.identifier("\$completion") + type = continuationType(context).substitute(substitutionMap) + } } } @@ -136,4 +145,4 @@ private fun IrFunction.continuationType(context: CommonBackendContext): IrType { } fun loweredSuspendFunctionReturnType(function: IrFunction, irBuiltIns: IrBuiltIns): IrType = - if (function.returnType.isNullable()) irBuiltIns.anyNType else irBuiltIns.anyType \ No newline at end of file + if (function.returnType.isNullable()) irBuiltIns.anyNType else irBuiltIns.anyType diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AbstractSuspendFunctionsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AbstractSuspendFunctionsLowering.kt index 114f9b7e902..89906f1dd8c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AbstractSuspendFunctionsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AbstractSuspendFunctionsLowering.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.lower.CallableReferenceLowering import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.buildClass @@ -93,7 +94,7 @@ abstract class AbstractSuspendFunctionsLowering(val co }) // It is important to optimize the case where there is only one suspend call and it is the last statement // because we don't need to build a fat coroutine class in that case. - // This happens a lot in practise because of suspend functions with default arguments. + // This happens a lot in practice because of suspend functions with default arguments. // TODO: use TailRecursionCallsCollector. val lastCall = when (val lastStatement = (body as IrBlockBody).statements.lastOrNull()) { is IrCall -> lastStatement @@ -181,7 +182,7 @@ abstract class AbstractSuspendFunctionsLowering(val co // It is not a lambda - replace original function with a call to constructor of the built coroutine. with(function) { - val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset) + val irBuilder = context.createIrBuilder(symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET) val functionBody = body as IrBlockBody functionBody.statements.clear() functionBody.statements.addAll(irBuilder.irBlockBody { @@ -221,8 +222,6 @@ abstract class AbstractSuspendFunctionsLowering(val co private fun buildNewCoroutineClass(function: IrSimpleFunction): IrClass = context.irFactory.buildClass { - startOffset = function.startOffset - endOffset = function.endOffset origin = DECLARATION_ORIGIN_COROUTINE_IMPL name = nameForCoroutineClass(function) visibility = function.visibility @@ -243,8 +242,6 @@ abstract class AbstractSuspendFunctionsLowering(val co } return context.irFactory.buildConstructor { - startOffset = function.startOffset - endOffset = function.endOffset origin = DECLARATION_ORIGIN_COROUTINE_IMPL name = coroutineBaseClassConstructor.name visibility = function.visibility @@ -261,6 +258,8 @@ abstract class AbstractSuspendFunctionsLowering(val co valueParameters += continuationParameter.copyTo( this, DECLARATION_ORIGIN_COROUTINE_IMPL, index = valueParameters.size, + startOffset = function.startOffset, + endOffset = function.endOffset, type = continuationType, defaultValue = null ) @@ -329,8 +328,8 @@ abstract class AbstractSuspendFunctionsLowering(val co // return i private fun buildCreateMethod(superCreateFunction: IrSimpleFunction?, constructor: IrConstructor): IrSimpleFunction = context.irFactory.buildFun { - startOffset = function.startOffset - endOffset = function.endOffset + startOffset = UNDEFINED_OFFSET + endOffset = UNDEFINED_OFFSET origin = DECLARATION_ORIGIN_COROUTINE_IMPL name = Name.identifier("create") visibility = DescriptorVisibilities.PROTECTED @@ -396,7 +395,7 @@ abstract class AbstractSuspendFunctionsLowering(val co } private fun transformInvokeMethod(createFunction: IrSimpleFunction, stateMachineFunction: IrSimpleFunction) { - val irBuilder = context.createIrBuilder(function.symbol, startOffset, endOffset) + val irBuilder = context.createIrBuilder(function.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET) val thisReceiver = function.dispatchReceiverParameter ?: compilationException( "Expected dispatch receiver for invoke", @@ -404,7 +403,7 @@ abstract class AbstractSuspendFunctionsLowering(val co ) val functionBody = function.body as IrBlockBody functionBody.statements.clear() - functionBody.statements.addAll(irBuilder.irBlockBody(startOffset, endOffset) { + functionBody.statements.addAll(irBuilder.irBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) { generateCoroutineStart(stateMachineFunction, irCall(createFunction).apply { dispatchReceiver = irGet(thisReceiver) var index = 0 @@ -486,11 +485,24 @@ abstract class AbstractSuspendFunctionsLowering(val co if (delegatingCall.isReturnIfSuspendedCall()) delegatingCall.getValueArgument(0)!! else delegatingCall - context.createIrBuilder(irFunction.symbol).run { - val statements = (irFunction.body as IrBlockBody).statements + val body = irFunction.body as IrBlockBody + + // Set both offsets to body.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression + // could be hit. + context.createIrBuilder(irFunction.symbol, startOffset = body.endOffset - 1, endOffset = body.endOffset - 1).run { + val statements = body.statements val lastStatement = statements.last() assert(lastStatement == delegatingCall || lastStatement is IrReturn) { "Unexpected statement $lastStatement" } - statements[statements.lastIndex] = irReturn(generateDelegatedCall(irFunction.returnType, returnValue)) + + // Instead of returning right away, we save the value to a temporary variable and after that return that variable. + // This is done solely to improve the debugging experience. Otherwise, a breakpoint set to the closing brace of the function + // cannot be hit. + val tempVar = scope.createTemporaryVariable( + generateDelegatedCall(irFunction.returnType, returnValue), + irType = context.irBuiltIns.anyType, + ) + statements[statements.lastIndex] = tempVar + statements.add(irReturn(irGet(tempVar))) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt index 2d6841a5a48..7036b7e2dba 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt @@ -96,7 +96,18 @@ class StateMachineBuilder( fun finalizeStateMachine() { globalCatch = buildGlobalCatch() if (currentBlock.statements.lastOrNull() !is IrReturn) { - addStatement(JsIrBuilder.buildReturn(function, unitValue, nothing)) + // Set both offsets to rootLoop.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression + // could be hit. + // NOTE: rootLoop's offsets are the same as in the original function. + addStatement( + IrReturnImpl( + startOffset = rootLoop.endOffset - 1, + endOffset = rootLoop.endOffset - 1, + nothing, + function, + unitValue + ) + ) } if (!hasExceptions) entryState.successors += rootExceptionTrap } diff --git a/compiler/testData/debug/stepping/suspendFunWithLambdaParameter.kt b/compiler/testData/debug/stepping/suspendFunWithLambdaParameter.kt index 461d6adeff7..d7acd06d8d8 100644 --- a/compiler/testData/debug/stepping/suspendFunWithLambdaParameter.kt +++ b/compiler/testData/debug/stepping/suspendFunWithLambdaParameter.kt @@ -19,13 +19,8 @@ suspend fun box() { // test.kt:11 box // EXPECTATIONS JS_IR -// test.kt:7 box -// test.kt:7 -// test.kt:7 -// test.kt:7 box -// test.kt:7 box -// test.kt:7 box // test.kt:8 doResume // test.kt:4 foo // test.kt:4 foo // test.kt:9 box$lambda +// test.kt:11 doResume diff --git a/js/js.translator/testData/lineNumbers/coroutine.kt b/js/js.translator/testData/lineNumbers/coroutine.kt index dd65cc945d9..c769c09452b 100644 --- a/js/js.translator/testData/lineNumbers/coroutine.kt +++ b/js/js.translator/testData/lineNumbers/coroutine.kt @@ -15,4 +15,4 @@ suspend fun bar(): Unit { } // LINES(JS): 39 4 4 4 7 5 5 45 45 5 93 45 5 5 6 4 4 4 9 15 9 9 9 * 9 15 10 10 11 11 11 11 11 * 11 12 12 13 13 13 13 13 13 13 14 14 * 9 15 9 9 9 9 -// LINES(JS_IR): 4 4 * 19 * 19 19 * 5 * 45 * 50 50 93 93 3 45 3 45 45 6 6 19 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 * 10 10 * 11 * 11 12 12 * 13 * 13 13 14 14 +// LINES(JS_IR): 4 4 * 19 * 19 19 * 5 * 45 * 50 50 93 93 3 45 3 45 45 6 6 19 19 7 7 9 9 * 9 * 10 10 * 11 * 11 12 12 * 13 * 13 13 14 14 15 15 diff --git a/js/js.translator/testData/lineNumbers/coroutineNullAssertion.kt b/js/js.translator/testData/lineNumbers/coroutineNullAssertion.kt index 64e5ab0ce25..0bc7cd6d26f 100644 --- a/js/js.translator/testData/lineNumbers/coroutineNullAssertion.kt +++ b/js/js.translator/testData/lineNumbers/coroutineNullAssertion.kt @@ -9,4 +9,4 @@ suspend fun delay() { } // LINES(JS): 1 6 1 1 * 1 6 2 2 2 2 2 * 3 3 4 4 4 4 4 5 5 * 1 6 1 1 1 1 8 9 -// LINES(JS_IR): 1 1 1 1 1 1 1 1 1 1 8 8 * 1 1 1 1 1 * 2 * 3 3 3 * 5 5 +// LINES(JS_IR): 1 1 * 8 8 9 9 * 1 * 2 * 3 3 3 * 5 5 6 6