[JS IR] Improve debug info precision for blocks
Namely: - Generate debug info for closing braces, which allows the breakpoints set on closing braces to be hit - Generate debug info for 'if' and 'try/catch' statements. KT-46276
This commit is contained in:
committed by
Space Team
parent
a1c61bb9a0
commit
a939f9ccd0
+15
-3
@@ -353,11 +353,13 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
|
||||
lambdaContextMapping: Map<IrFieldSymbol, IrValueSymbol>,
|
||||
outerReceiverMapping: Map<IrFieldSymbol, IrGetField> = emptyMap()
|
||||
): IrBlockBody {
|
||||
val body = invokeFun.body
|
||||
val oldBody = invokeFun.body as? IrBlockBody
|
||||
?: compilationException(
|
||||
"invoke() method has to have a body",
|
||||
invokeFun
|
||||
)
|
||||
// Don't use offsets from oldBody, use offsets from invokeFun instead. This is more precise.
|
||||
val body = context.irFactory.createBlockBody(invokeFun.startOffset, invokeFun.endOffset, oldBody.statements)
|
||||
|
||||
fun IrExpression.getValue(d: IrValueSymbol): IrExpression = IrGetValueImpl(startOffset, endOffset, d)
|
||||
fun IrExpression.getCastedValue(d: IrValueSymbol, toType: IrType): IrExpression =
|
||||
@@ -406,10 +408,20 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
|
||||
|
||||
if (invokeFun.returnType.isUnit()) {
|
||||
val unitValue = JsIrBuilder.buildGetObjectValue(context.irBuiltIns.unitType, context.irBuiltIns.unitClass)
|
||||
(body as IrBlockBody).statements.add(IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.nothingType, lambdaDeclaration.symbol, unitValue))
|
||||
// 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.
|
||||
body.statements.add(
|
||||
IrReturnImpl(
|
||||
body.endOffset - 1,
|
||||
body.endOffset - 1,
|
||||
context.irBuiltIns.nothingType,
|
||||
lambdaDeclaration.symbol,
|
||||
unitValue
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return body as IrBlockBody
|
||||
return body
|
||||
}
|
||||
|
||||
private fun buildLambdaBody(instance: IrVariable, lambdaDeclaration: IrSimpleFunction, invokeFun: IrSimpleFunction): IrBlockBody {
|
||||
|
||||
+5
-5
@@ -32,7 +32,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
|
||||
}
|
||||
|
||||
override fun visitBlockBody(body: IrBlockBody, context: JsGenerationContext): JsStatement {
|
||||
return JsBlock(body.statements.map { it.accept(this, context) })
|
||||
return JsBlock(body.statements.map { it.accept(this, context) }).withSource(body, context, container = context.currentFunction)
|
||||
}
|
||||
|
||||
override fun visitBlock(expression: IrBlock, context: JsGenerationContext): JsStatement {
|
||||
@@ -53,7 +53,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
|
||||
}
|
||||
} else {
|
||||
JsBlock(statements)
|
||||
}
|
||||
}.withSource(expression, context)
|
||||
}
|
||||
|
||||
private fun List<JsStatement>.wrapInCommentsInlineFunctionCall(expression: IrReturnableBlock): List<JsStatement> {
|
||||
@@ -65,7 +65,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
|
||||
}
|
||||
|
||||
override fun visitComposite(expression: IrComposite, context: JsGenerationContext): JsStatement {
|
||||
return JsBlock(expression.statements.map { it.accept(this, context) })
|
||||
return JsBlock(expression.statements.map { it.accept(this, context) }).withSource(expression, context)
|
||||
}
|
||||
|
||||
override fun visitExpression(expression: IrExpression, context: JsGenerationContext): JsStatement {
|
||||
@@ -186,12 +186,12 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
|
||||
val jsCatch = aTry.catches.singleOrNull()?.let {
|
||||
val name = context.getNameForValueDeclaration(it.catchParameter)
|
||||
val jsCatchBlock = it.result.accept(this, context)
|
||||
JsCatch(emptyScope, name.ident, jsCatchBlock)
|
||||
JsCatch(emptyScope, name.ident, jsCatchBlock).withSource(it, context)
|
||||
}
|
||||
|
||||
val jsFinallyBlock = aTry.finallyExpression?.accept(this, context)?.asBlock()
|
||||
|
||||
return JsTry(jsTryBlock, jsCatch, jsFinallyBlock)
|
||||
return JsTry(jsTryBlock, jsCatch, jsFinallyBlock).withSource(aTry, context)
|
||||
}
|
||||
|
||||
override fun visitWhen(expression: IrWhen, context: JsGenerationContext): JsStatement {
|
||||
|
||||
+42
-17
@@ -45,16 +45,16 @@ fun jsVar(name: JsName, initializer: IrExpression?, context: JsGenerationContext
|
||||
|
||||
fun <T : JsNode> IrWhen.toJsNode(
|
||||
tr: BaseIrElementToJsNodeTransformer<T, JsGenerationContext>,
|
||||
data: JsGenerationContext,
|
||||
context: JsGenerationContext,
|
||||
node: (JsExpression, T, T?) -> T,
|
||||
implicitElse: T? = null
|
||||
): T? =
|
||||
branches.foldRight(implicitElse) { br, n ->
|
||||
val body = br.result.accept(tr, data)
|
||||
val body = br.result.accept(tr, context)
|
||||
if (isElseBranch(br)) body
|
||||
else {
|
||||
val condition = br.condition.accept(IrElementToJsExpressionTransformer(), data)
|
||||
node(condition, body, n)
|
||||
val condition = br.condition.accept(IrElementToJsExpressionTransformer(), context)
|
||||
node(condition, body, n).withSource(br, context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,25 +521,46 @@ object JsAstUtils {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T : JsNode> T.withSource(node: IrElement, context: JsGenerationContext, useNameOf: IrDeclarationWithName? = null): T {
|
||||
addSourceInfoIfNeed(node, context, useNameOf)
|
||||
internal fun <T : JsNode> T.withSource(
|
||||
node: IrElement,
|
||||
context: JsGenerationContext,
|
||||
useNameOf: IrDeclarationWithName? = null,
|
||||
container: IrDeclaration? = null
|
||||
): T {
|
||||
addSourceInfoIfNeed(node, context, useNameOf, container)
|
||||
return this
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun <T : JsNode> T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext, useNameOf: IrDeclarationWithName?) {
|
||||
|
||||
private inline fun <T : JsNode> T.addSourceInfoIfNeed(
|
||||
node: IrElement,
|
||||
context: JsGenerationContext,
|
||||
useNameOf: IrDeclarationWithName?,
|
||||
container: IrDeclaration?
|
||||
) {
|
||||
val sourceMapsInfo = context.staticContext.backendContext.sourceMapsInfo ?: return
|
||||
|
||||
val originalName = useNameOf?.originalNameForUseInSourceMap(sourceMapsInfo.namesPolicy)
|
||||
|
||||
val location = context.getLocationForIrElement(node, originalName) ?: return
|
||||
|
||||
val location = context.getStartLocationForIrElement(node, originalName) ?: return
|
||||
val isNodeFromCurrentModule = context.currentFile.module.descriptor == context.staticContext.backendContext.module
|
||||
|
||||
// TODO maybe it's better to fix in JsExpressionStatement
|
||||
val locationTarget = if (this is JsExpressionStatement) this.expression else this
|
||||
|
||||
if (locationTarget is JsBlock && (node is IrBlockBody || node is IrBlock)) {
|
||||
locationTarget.closingBraceSource = if (container is IrConstructor) {
|
||||
// This is a hack. Without this special case, the closing brace in the generated code for constructors would always be mapped
|
||||
// to the closing brace of the Kotlin class declaration.
|
||||
context.getStartLocationForIrElement(node)
|
||||
} else {
|
||||
context.getEndLocationForIrElement(node)?.run {
|
||||
// Assuming that endOffset for IrBlock and IrBlockBody points to the character after the closing brace.
|
||||
// TODO: This doesn't produce good results if the node originates from an expression body
|
||||
// (meaning, in the source code; not to be confused with IrExpressionBody)
|
||||
if (startChar > 0) copy(startChar = startChar - 1) else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locationTarget.source = when (sourceMapsInfo.sourceMapContentEmbedding) {
|
||||
SourceMapSourceEmbedding.NEVER -> location
|
||||
SourceMapSourceEmbedding.INLINING -> if (isNodeFromCurrentModule) location else location.withEmbeddedSource(context)
|
||||
@@ -568,16 +589,20 @@ private fun JsLocation.withEmbeddedSource(
|
||||
}
|
||||
}
|
||||
|
||||
fun IrElement.getSourceInfo(container: IrDeclaration): JsLocation? {
|
||||
fun IrElement.getStartSourceLocation(container: IrDeclaration): JsLocation? {
|
||||
val fileEntry = container.fileOrNull?.fileEntry ?: return null
|
||||
return getSourceInfo(fileEntry)
|
||||
return getStartSourceLocation(fileEntry)
|
||||
}
|
||||
|
||||
fun IrElement.getSourceInfo(fileEntry: IrFileEntry): JsLocation? {
|
||||
fun IrElement.getStartSourceLocation(fileEntry: IrFileEntry) =
|
||||
getSourceLocation(fileEntry) { startOffset }
|
||||
|
||||
inline fun IrElement.getSourceLocation(fileEntry: IrFileEntry, offsetSelector: IrElement.() -> Int): JsLocation? {
|
||||
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET) return null
|
||||
val path = fileEntry.name
|
||||
val startLine = fileEntry.getLineNumber(startOffset)
|
||||
val startColumn = fileEntry.getColumnNumber(startOffset)
|
||||
val offset = offsetSelector()
|
||||
val startLine = fileEntry.getLineNumber(offset)
|
||||
val startColumn = fileEntry.getColumnNumber(offset)
|
||||
return JsLocation(path, startLine, startColumn)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -28,14 +28,14 @@ fun translateJsCodeIntoStatementList(code: IrExpression, context: JsIrBackendCon
|
||||
translateJsCodeIntoStatementList(
|
||||
code,
|
||||
context,
|
||||
code.getSourceInfo(container) ?: container.fileOrNull?.fileEntry?.let { JsLocation(it.name, 0, 0) }
|
||||
code.getStartSourceLocation(container) ?: container.fileOrNull?.fileEntry?.let { JsLocation(it.name, 0, 0) }
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns null if constant expression could not be parsed.
|
||||
*/
|
||||
fun translateJsCodeIntoStatementList(code: IrExpression, context: JsIrBackendContext?, fileEntry: IrFileEntry) =
|
||||
translateJsCodeIntoStatementList(code, context, code.getSourceInfo(fileEntry) ?: JsLocation(fileEntry.name, 0, 0))
|
||||
translateJsCodeIntoStatementList(code, context, code.getStartSourceLocation(fileEntry) ?: JsLocation(fileEntry.name, 0, 0))
|
||||
|
||||
private fun translateJsCodeIntoStatementList(
|
||||
code: IrExpression,
|
||||
|
||||
+18
-7
@@ -6,7 +6,8 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.utils
|
||||
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getSourceInfo
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getSourceLocation
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getStartSourceLocation
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrLoop
|
||||
import org.jetbrains.kotlin.ir.expressions.IrReturnableBlock
|
||||
@@ -33,7 +34,8 @@ class JsGenerationContext(
|
||||
private val nameCache: MutableMap<IrElement, JsName> = mutableMapOf(),
|
||||
private val useBareParameterNames: Boolean = false,
|
||||
) : IrNamer by staticContext {
|
||||
private val locationCache = mutableMapOf<Int, JsLocation>()
|
||||
private val startLocationCache = mutableMapOf<Int, JsLocation>()
|
||||
private val endLocationCache = mutableMapOf<Int, JsLocation>()
|
||||
|
||||
fun newFile(file: IrFile, func: IrFunction? = null, localNames: LocalNameGenerator? = null): JsGenerationContext {
|
||||
return JsGenerationContext(
|
||||
@@ -87,9 +89,18 @@ class JsGenerationContext(
|
||||
|
||||
fun checkIfHasAssociatedJsCode(symbol: IrFunctionSymbol): Boolean = staticContext.backendContext.getJsCodeForFunction(symbol) != null
|
||||
|
||||
fun getLocationForIrElement(irElement: IrElement, originalName: String? = null): JsLocation? {
|
||||
return locationCache.getOrPut(irElement.startOffset) {
|
||||
irElement.getSourceInfo(currentFile.fileEntry) ?: return null
|
||||
}.copy(name = originalName)
|
||||
}
|
||||
fun getStartLocationForIrElement(irElement: IrElement, originalName: String? = null) =
|
||||
getLocationForIrElement(irElement, originalName, startLocationCache) { startOffset }
|
||||
|
||||
fun getEndLocationForIrElement(irElement: IrElement, originalName: String? = null) =
|
||||
getLocationForIrElement(irElement, originalName, endLocationCache) { endOffset }
|
||||
|
||||
private inline fun getLocationForIrElement(
|
||||
irElement: IrElement,
|
||||
originalName: String?,
|
||||
cache: MutableMap<Int, JsLocation>,
|
||||
offsetSelector: IrElement.() -> Int,
|
||||
): JsLocation? = cache.getOrPut(irElement.offsetSelector()) {
|
||||
irElement.getSourceLocation(currentFile.fileEntry, offsetSelector) ?: return null
|
||||
}.copy(name = originalName)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user