[IR] Improve stepping/LVT behavior around destructuring

This commit is contained in:
Kristoffer Andersen
2022-03-07 19:39:32 +01:00
committed by Alexander Udalov
parent c3dbb44e5f
commit 7f531d8426
21 changed files with 197 additions and 38 deletions
@@ -313,6 +313,12 @@ public class FirSteppingTestGenerated extends AbstractFirSteppingTest {
runTest("compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt");
}
@Test
@TestMetadata("localProperty.kt")
public void testLocalProperty() throws Exception {
runTest("compiler/testData/debug/stepping/localProperty.kt");
}
@Test
@TestMetadata("multilineExpression.kt")
public void testMultilineExpression() throws Exception {
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmIrMangler
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
@@ -24,7 +25,6 @@ import org.jetbrains.kotlin.ir.builders.declarations.buildClass
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
@@ -252,4 +252,7 @@ open class JvmGeneratorExtensionsImpl(
override val parametersAreAssignable: Boolean
get() = true
override val debugInfoOnlyOnVariablesInDestructuringDeclarations: Boolean
get() = true
}
@@ -722,10 +722,10 @@ private class TypeOperatorLowering(private val backendContext: JvmBackendContext
val source = if (owner is IrFunction && owner.isDelegated()) {
"${owner.name.asString()}(...)"
} else {
val (startOffset, endOffset) = expression.extents()
val declarationParent = parent as? IrDeclaration
val sourceView = declarationParent?.let(::sourceViewFor)
if (sourceView != null && startOffset >= 0 && endOffset < sourceView.length) {
val (startOffset, endOffset) = expression.extents()
if (sourceView?.validSourcePosition(startOffset, endOffset) == true) {
sourceView.subSequence(startOffset, endOffset).toString()
} else {
// Fallback for inconsistent line numbers
@@ -757,6 +757,9 @@ private class TypeOperatorLowering(private val backendContext: JvmBackendContext
origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR ||
origin == IrDeclarationOrigin.DELEGATED_MEMBER
private fun CharSequence.validSourcePosition(startOffset: Int, endOffset: Int): Boolean =
startOffset in 0 until endOffset && endOffset < length
private fun IrElement.extents(): Pair<Int, Int> {
var startOffset = Int.MAX_VALUE
var endOffset = 0
@@ -103,7 +103,7 @@ class BodyGenerator(
valueParameter.type.toIrType(),
IrStatementOrigin.DESTRUCTURING_DECLARATION
)
statementGenerator.declareComponentVariablesInBlock(ktDestructuringDeclaration, irBlockBody, parameterValue)
statementGenerator.declareComponentVariablesInBlock(ktDestructuringDeclaration, irBlockBody, parameterValue, parameterValue)
}
val ktBodyStatements = ktBody.statements
@@ -463,8 +463,14 @@ fun IrExpression.isUnchanging() =
fun IrExpression.hasNoSideEffects() = isUnchanging() || this is IrGetValue
fun CallGenerator.generateCall(ktElement: KtElement, call: CallBuilder, origin: IrStatementOrigin? = null) =
generateCall(ktElement.startOffsetSkippingComments, ktElement.endOffset, call, origin)
fun CallGenerator.generateCall(
ktElement: KtElement,
call: CallBuilder,
origin: IrStatementOrigin? = null,
startOffset: Int = ktElement.startOffsetSkippingComments,
endOffset: Int = ktElement.endOffset,
) =
generateCall(startOffset, endOffset, call, origin)
fun CallGenerator.generateCall(irExpression: IrExpression, call: CallBuilder, origin: IrStatementOrigin? = null) =
generateCall(irExpression.startOffset, irExpression.endOffset, call, origin)
@@ -48,4 +48,35 @@ open class GeneratorExtensions : StubGeneratorExtensions() {
open val parametersAreAssignable: Boolean
get() = false
/**
* Enables improved source offsets for the desugared IR generated for
* destructuring declarations.
*
* Local variables defined by destructuring, e.g.
*
* val (x, y) = destructee()
*
* is represented in IR by
*
* block {
* val containerTmp = destructee()
* val x = containerTmp.component1()
* val y = containerTmp.component2()
* }
*
* When [debugInfoOnlyOnVariablesInDestructuringDeclarations] is `false`, the
* access to `containerTmp` in the calls to `component` calls are given source
* positions corresponding to `destructee()` which causes multi-line
* destructuring declarations to step back and forth between the variables being
* declared and the right-hand side, implying the repeated evaluation of the
* right-hand side.
*
* When `true`, only the stores to `x` and `y` in the generated code are are
* given source offsets, the source offsets of `x` and `y` in the original
* declaration, giving fewer, more accurate steps, that are closer to the JVM
* backend in behavior.
*/
open val debugInfoOnlyOnVariablesInDestructuringDeclarations: Boolean
get() = false
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -202,10 +203,16 @@ class LoopExpressionGenerator(statementGenerator: StatementGenerator) : Statemen
irInnerBody.statements.add(irLoopParameter)
if (ktLoopDestructuringDeclaration != null) {
val firstContainerValue = VariableLValue(context, irLoopParameter)
statementGenerator.declareComponentVariablesInBlock(
ktLoopDestructuringDeclaration,
irInnerBody,
VariableLValue(context, irLoopParameter)
firstContainerValue,
if (context.extensions.debugInfoOnlyOnVariablesInDestructuringDeclarations) {
VariableLValue(context, irLoopParameter, startOffset = SYNTHETIC_OFFSET, endOffset = SYNTHETIC_OFFSET)
} else {
firstContainerValue
}
)
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.psi2ir.generators
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.BackendException
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.IrStatement
@@ -25,6 +26,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.referenceFunction
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.psi.*
@@ -32,7 +34,8 @@ import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffsetSkippingComments
import org.jetbrains.kotlin.psi2ir.deparenthesize
import org.jetbrains.kotlin.psi2ir.intermediate.IntermediateValue
import org.jetbrains.kotlin.psi2ir.intermediate.createTemporaryVariableInBlock
import org.jetbrains.kotlin.psi2ir.intermediate.VariableLValue
import org.jetbrains.kotlin.psi2ir.intermediate.declareTemporaryVariableInBlock
import org.jetbrains.kotlin.psi2ir.intermediate.setExplicitReceiverValue
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST
@@ -110,8 +113,14 @@ class StatementGenerator(
)
}
val sourceElement =
if (context.extensions.debugInfoOnlyOnVariablesInDestructuringDeclarations) {
property.nameIdentifier ?: property
} else {
property
}
return context.symbolTable.declareVariable(
property.startOffsetSkippingComments, property.endOffset, IrDeclarationOrigin.DEFINED,
sourceElement.startOffsetSkippingComments, sourceElement.endOffset, IrDeclarationOrigin.DEFINED,
variableDescriptor,
variableDescriptor.type.toIrType(),
property.initializer?.let { generateExpression(it) }
@@ -128,14 +137,31 @@ class StatementGenerator(
.generateLocalDelegatedProperty(ktProperty, ktDelegate, variableDescriptor, scopeOwnerSymbol)
override fun visitDestructuringDeclaration(multiDeclaration: KtDestructuringDeclaration, data: Nothing?): IrStatement {
val (blockStartOffset, blockEndOffset) = if (context.extensions.debugInfoOnlyOnVariablesInDestructuringDeclarations) {
SYNTHETIC_OFFSET to SYNTHETIC_OFFSET
} else {
multiDeclaration.startOffsetSkippingComments to multiDeclaration.endOffset
}
val irBlock = IrCompositeImpl(
multiDeclaration.startOffsetSkippingComments, multiDeclaration.endOffset,
blockStartOffset, blockEndOffset,
context.irBuiltIns.unitType, IrStatementOrigin.DESTRUCTURING_DECLARATION
)
val ktInitializer = multiDeclaration.initializer!!
val containerValue = scope.createTemporaryVariableInBlock(context, generateExpression(ktInitializer), irBlock, "container")
val irInitializer = generateExpression(ktInitializer)
declareComponentVariablesInBlock(multiDeclaration, irBlock, containerValue)
val containerVariable = scope.declareTemporaryVariableInBlock(irInitializer, irBlock, nameHint = "container")
val firstContainerValue = VariableLValue(context, containerVariable)
declareComponentVariablesInBlock(
multiDeclaration,
irBlock,
firstContainerValue,
if (context.extensions.debugInfoOnlyOnVariablesInDestructuringDeclarations) {
VariableLValue(context, containerVariable, startOffset = SYNTHETIC_OFFSET, endOffset = SYNTHETIC_OFFSET)
} else {
firstContainerValue
}
)
return irBlock
}
@@ -143,26 +169,47 @@ class StatementGenerator(
fun declareComponentVariablesInBlock(
multiDeclaration: KtDestructuringDeclaration,
irBlock: IrStatementContainer,
containerValue: IntermediateValue
firstContainerValue: IntermediateValue,
restContainerValue: IntermediateValue
) {
val callGenerator = CallGenerator(this)
// TODO: Every access to the container value causes a null check even though subsequent checks after the first can be assumed to pass.
var containerValue = firstContainerValue
for ((index, ktEntry) in multiDeclaration.entries.withIndex()) {
val componentResolvedCall = getOrFail(BindingContext.COMPONENT_RESOLVED_CALL, ktEntry)
val componentSubstitutedCall = pregenerateCall(componentResolvedCall)
componentSubstitutedCall.setExplicitReceiverValue(containerValue)
val componentVariable = getOrFail(BindingContext.VARIABLE, ktEntry)
// componentN for '_' SHOULD NOT be evaluated
if (componentVariable.name.isSpecial) continue
val componentResolvedCall = getOrFail(BindingContext.COMPONENT_RESOLVED_CALL, ktEntry)
val componentSubstitutedCall = pregenerateCall(componentResolvedCall)
componentSubstitutedCall.setExplicitReceiverValue(containerValue)
containerValue = restContainerValue
val (componentCallStartOffset, componentCallEndOffset) =
if (context.extensions.debugInfoOnlyOnVariablesInDestructuringDeclarations) {
SYNTHETIC_OFFSET to SYNTHETIC_OFFSET
} else {
ktEntry.startOffsetSkippingComments to ktEntry.endOffset
}
val irComponentCall = callGenerator.generateCall(
ktEntry.startOffsetSkippingComments, ktEntry.endOffset, componentSubstitutedCall,
componentCallStartOffset, componentCallEndOffset,
componentSubstitutedCall,
IrStatementOrigin.COMPONENT_N.withIndex(index + 1)
)
val componentVarOffsetSource: PsiElement =
if (context.extensions.debugInfoOnlyOnVariablesInDestructuringDeclarations) {
ktEntry.nameIdentifier ?: ktEntry
} else {
ktEntry
}
val irComponentVar = context.symbolTable.declareVariable(
ktEntry.startOffsetSkippingComments, ktEntry.endOffset, IrDeclarationOrigin.DEFINED,
componentVarOffsetSource.startOffsetSkippingComments, componentVarOffsetSource.endOffset,
IrDeclarationOrigin.DEFINED,
componentVariable, componentVariable.type.toIrType(), irComponentCall
)
irBlock.statements.add(irComponentVar)
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.psi2ir.intermediate
import org.jetbrains.kotlin.ir.builders.IrGeneratorContext
import org.jetbrains.kotlin.ir.builders.Scope
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementContainer
import org.jetbrains.kotlin.ir.types.IrType
@@ -31,9 +32,26 @@ fun Scope.createTemporaryVariableInBlock(
context: IrGeneratorContext,
irExpression: IrExpression,
block: IrStatementContainer,
nameHint: String? = null
nameHint: String? = null,
startOffset: Int = irExpression.startOffset,
endOffset: Int = irExpression.endOffset
): IntermediateValue {
val temporaryVariable = createTemporaryVariable(irExpression, nameHint)
block.statements.add(temporaryVariable)
return VariableLValue(context, temporaryVariable)
return VariableLValue(
context,
declareTemporaryVariableInBlock(irExpression, block, nameHint, startOffset, endOffset)
)
}
fun Scope.declareTemporaryVariableInBlock(
irExpression: IrExpression,
block: IrStatementContainer,
nameHint: String? = null,
startOffset: Int = irExpression.startOffset,
endOffset: Int = irExpression.endOffset
): IrVariable {
val temporaryVariable = createTemporaryVariable(irExpression, nameHint, startOffset = startOffset, endOffset = endOffset)
block.statements.add(temporaryVariable)
return temporaryVariable
}
@@ -38,8 +38,14 @@ class VariableLValue(
LValue,
AssignmentReceiver {
constructor(context: IrGeneratorContext, irVariable: IrVariable, origin: IrStatementOrigin? = null) :
this(context, irVariable.startOffset, irVariable.endOffset, irVariable.symbol, irVariable.type, origin)
constructor(
context: IrGeneratorContext,
irVariable: IrVariable,
origin: IrStatementOrigin? = null,
startOffset: Int = irVariable.startOffset,
endOffset: Int = irVariable.endOffset
) :
this(context, startOffset, endOffset, irVariable.symbol, irVariable.type, origin)
override fun load(): IrExpression =
IrGetValueImpl(startOffset, endOffset, type, symbol, origin)
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.ir.builders
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
@@ -71,12 +70,14 @@ class Scope(val scopeOwnerSymbol: IrSymbol) {
nameHint: String? = null,
isMutable: Boolean = false,
origin: IrDeclarationOrigin = IrDeclarationOrigin.IR_TEMPORARY_VARIABLE,
irType: IrType? = null
irType: IrType? = null,
startOffset: Int = irExpression.startOffset,
endOffset: Int = irExpression.endOffset
): IrVariable {
return createTemporaryVariableDeclaration(
irType ?: irExpression.type,
nameHint, isMutable,
origin, irExpression.startOffset, irExpression.endOffset
origin, startOffset, endOffset
).apply {
initializer = irExpression
}
+1 -1
View File
@@ -1,5 +1,5 @@
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_FIR: JVM_IR
// FILE: test.kt
fun box(): String {
val
@@ -1,5 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// FILE: test.kt
class MyPair(val x: String, val y: String) {
operator fun component1(): String {
@@ -1,6 +1,6 @@
// WITH_STDLIB
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// FILE: test.kt
fun box(): String {
val p = "O" to "K"
@@ -1,6 +1,6 @@
// WITH_STDLIB
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// FILE: test.kt
fun box(): String {
val p = Triple("X","O","K")
@@ -49,11 +49,9 @@ fun box() {
// test.kt:17 box:
// test.kt:14 foo: a:MyPair=MyPair, block:kotlin.jvm.functions.Function1=TestKt$box$1
// test.kt:19 invoke:
// test.kt:20 invoke:
// test.kt:6 component1:
// test.kt:20 invoke:
// test.kt:19 invoke: x:java.lang.String="O":java.lang.String
// test.kt:22 invoke: x:java.lang.String="O":java.lang.String
// test.kt:10 component2:
// test.kt:22 invoke: x:java.lang.String="O":java.lang.String
// test.kt:25 invoke: x:java.lang.String="O":java.lang.String, y:java.lang.String="K":java.lang.String
+21
View File
@@ -0,0 +1,21 @@
// IGNORE_BACKEND_FIR: JVM_IR
// FILE: test.kt
fun box(): String {
val
o
=
"O"
val k = "K"
return o + k
}
// EXPECTATIONS
// test.kt:8 box
// test.kt:6 box
// test.kt:11 box
// test.kt:13 box
+1 -1
View File
@@ -18,5 +18,5 @@
@3:27..53 PROPERTY_REFERENCE 'public final lazyNullString: kotlin.String [delegated,val]' field=null getter='public final fun <get-lazyNullString> (): kotlin.String declared in <root>' setter=null type=kotlin.reflect.KProperty0<kotlin.String> origin=PROPERTY_REFERENCE_FOR_DELEGATE
@5:0..7:1 FUN name:testLazyNullString visibility:public modality:FINAL <> () returnType:kotlin.Unit
@5:25..7:1 BLOCK_BODY
@6:4..34 VAR name:s type:kotlin.String [val]
@6:8..9 VAR name:s type:kotlin.String [val]
@6:20..34 CALL 'public final fun <get-lazyNullString> (): kotlin.String declared in <root>' type=kotlin.String origin=GET_PROPERTY
@@ -1,9 +1,9 @@
@0:0..6:0 FILE fqName:<root> fileName:/postfixIncrementDecrement.kt
@0:0..5:1 FUN name:test visibility:public modality:FINAL <> () returnType:kotlin.Unit
@0:11..5:1 BLOCK_BODY
@1:4..13 VAR name:x type:kotlin.Int [var]
@1:8..9 VAR name:x type:kotlin.Int [var]
@1:12..13 CONST Int type=kotlin.Int value=0
@2:4..13 VAR name:y type:kotlin.Int [var]
@2:8..9 VAR name:y type:kotlin.Int [var]
@2:12..13 CONST Int type=kotlin.Int value=0
@3:4..5 SET_VAR 'var y: kotlin.Int [var] declared in <root>.test' type=kotlin.Unit origin=EQ
@3:8..11 BLOCK type=kotlin.Int origin=POSTFIX_INCR
@@ -313,6 +313,12 @@ public class IrSteppingTestGenerated extends AbstractIrSteppingTest {
runTest("compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt");
}
@Test
@TestMetadata("localProperty.kt")
public void testLocalProperty() throws Exception {
runTest("compiler/testData/debug/stepping/localProperty.kt");
}
@Test
@TestMetadata("multilineExpression.kt")
public void testMultilineExpression() throws Exception {
@@ -313,6 +313,12 @@ public class SteppingTestGenerated extends AbstractSteppingTest {
runTest("compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt");
}
@Test
@TestMetadata("localProperty.kt")
public void testLocalProperty() throws Exception {
runTest("compiler/testData/debug/stepping/localProperty.kt");
}
@Test
@TestMetadata("multilineExpression.kt")
public void testMultilineExpression() throws Exception {