IR: keep capture sets the same when copying objects for tailrec funs

tailrec f(x: () -> T = { y }, y: T = ...) = f()

-- at the call we know that in `x` the observed value of `y` is `null`,
but the constructor should still have a single parameter.
This commit is contained in:
pyos
2021-09-21 12:16:51 +02:00
committed by Mikhael Bogdanov
parent a4d1358e57
commit cb505d1101
10 changed files with 67 additions and 17 deletions
@@ -13090,6 +13090,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@Test
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@Test
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.collectTailRecursionCalls
import org.jetbrains.kotlin.backend.common.deepCopyWithVariables
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
@@ -156,25 +157,22 @@ private class BodyTransformer(
// that captures another parameter, it won't capture it as a mutable ref.
parameter to irTemporary(argument)
}
// For each unspecified argument set the corresponding variable to default:
parameters
.filter { it !in parameterToArgument }
.let { if (properComputationOrderOfTailrecDefaultParameters) it else it.asReversed() }
// Create new null-initialized variables for all other values in case of forward references:
// fun f(x: () -> T = { y }, y: T = ...) // in `f()`, `x()` returns `null`
val defaultValuedParameters = parameters.filter { it !in parameterToArgument }
defaultValuedParameters.associateWithTo(parameterToArgument) {
// Note that we intentionally keep the original type of the parameter for the variable even though that violates type safety
// if it's non-null. This ensures that capture parameters have the same types for all copies of `x`.
// TODO: on the JVM, this might not be correct for inline classes - copy code from JvmDefaultParameterInjector?
irTemporary(IrConstImpl.defaultValueForType(UNDEFINED_OFFSET, UNDEFINED_OFFSET, it.type.makeNullable()), irType = it.type)
}
// Now replace those variables with ones containing actual default values. Unused null-valued temporaries will hopefully
// be optimized out later.
val remapper = VariableRemapper(parameterToArgument)
defaultValuedParameters.let { if (properComputationOrderOfTailrecDefaultParameters) it else it.asReversed() }
.associateWithTo(parameterToArgument) { parameter ->
val originalDefaultValue = parameter.defaultValue?.expression ?: throw Error("no argument specified for $parameter")
// Copy default value, mapping parameters to variables containing freshly computed arguments:
val defaultValue = originalDefaultValue
.deepCopyWithVariables().patchDeclarationParents(parent)
.transform(object : VariableRemapper(parameterToArgument) {
override fun visitGetValue(expression: IrGetValue): IrExpression {
// If this parameter references a different parameter declared later, produce null:
if (expression.symbol.owner.let { it is IrValueParameter && it.parent == irFunction && it !in parameterToArgument })
return IrConstImpl.defaultValueForType(startOffset, endOffset, expression.type.makeNullable())
return super.visitGetValue(expression)
}
}, null)
irTemporary(defaultValue)
irTemporary(originalDefaultValue.deepCopyWithVariables().patchDeclarationParents(parent).transform(remapper, null))
}
// Copy the new `val`s into the `var`s declared outside the loop:
@@ -0,0 +1,9 @@
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND_FIR: JVM_IR
tailrec fun foo(x: () -> String? = { y }, y: String = "fail"): String? {
if (y == "start")
return foo()
return x()
}
fun box() = foo(y = "start") ?: "OK"
@@ -13012,6 +13012,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@Test
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@Test
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
@@ -13090,6 +13090,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@Test
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@Test
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
@@ -10415,6 +10415,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class DefaultArguments extends AbstractLightAnalysisModeTest {
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void ignoreUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
@@ -9362,6 +9362,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt");
@@ -8768,6 +8768,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt");
@@ -8733,6 +8733,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt");
@@ -4914,6 +4914,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt");
}
@TestMetadata("useNextParamInLambdaTailrec.kt")
public void testUseNextParamInLambdaTailrec() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambdaTailrec.kt");
}
@TestMetadata("useThisInLambda.kt")
public void testUseThisInLambda() throws Exception {
runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt");