From 92cf521e1178918f0a8a97aa22f11b18228b880d Mon Sep 17 00:00:00 2001 From: Mads Ager Date: Tue, 8 Oct 2019 15:41:01 +0200 Subject: [PATCH] [IR] Deal with forward references in default argument lambdas. Rely on the frontend weeding out cases that are not supported. In psi2ir, introduce all the parameters before processing default values. Change the DefaultArgumentStubGenerator to generate code that matches the behavior of the current backend. --- .../kotlin/fir/Fir2IrTextTestGenerated.java | 5 ++ .../lower/DefaultArgumentStubGenerator.kt | 16 ++++++- .../psi2ir/generators/FunctionGenerator.kt | 25 +++++----- .../defaultArguments/useNextParamInLambda.kt | 16 +++++++ .../parameters/useNextParamInLambda.fir.txt | 41 ++++++++++++++++ .../parameters/useNextParamInLambda.kt | 14 ++++++ .../parameters/useNextParamInLambda.txt | 48 +++++++++++++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 5 ++ .../LightAnalysisModeTestGenerated.java | 5 ++ .../ir/IrBlackBoxCodegenTestGenerated.java | 5 ++ .../kotlin/ir/IrTextTestCaseGenerated.java | 5 ++ .../IrJsCodegenBoxTestGenerated.java | 5 ++ .../semantics/JsCodegenBoxTestGenerated.java | 5 ++ 13 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt create mode 100644 compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.fir.txt create mode 100644 compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.kt create mode 100644 compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.txt diff --git a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java index b11a9e46928..5ed1fc3f327 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java @@ -581,6 +581,11 @@ public class Fir2IrTextTestGenerated extends AbstractFir2IrTextTest { public void testTypeParameterBoundedBySubclass() throws Exception { runTest("compiler/testData/ir/irText/declarations/parameters/typeParameterBoundedBySubclass.kt"); } + + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.kt"); + } } @TestMetadata("compiler/testData/ir/irText/declarations/provideDelegate") diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt index c81509edbfc..4564559f536 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/DefaultArgumentStubGenerator.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.backend.common.ir.* import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* -import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl @@ -84,6 +83,20 @@ open class DefaultArgumentStubGenerator( variables[it] = newIrFunction.extensionReceiverParameter!! } + // In order to deal with forward references in default value lambdas, + // accesses to the parameter before it has been determined if there is + // a default value or not is redirected to the actual parameter of the + // $default function. This is to ensure that examples such as: + // + // fun f(f1: () -> String = { f2() }, + // f2: () -> String = { "OK" }) = f1() + // + // works correctly so that `f() { "OK" }` returns "OK" and + // `f()` throws a NullPointerException. + irFunction.valueParameters.associateWithTo(variables) { + newIrFunction.valueParameters[it.index] + } + for (valueParameter in irFunction.valueParameters) { val parameter = newIrFunction.valueParameters[valueParameter.index] val remapped = if (valueParameter.defaultValue != null) { @@ -129,7 +142,6 @@ open class DefaultArgumentStubGenerator( passTypeArgumentsFrom(newIrFunction.parentAsClass) passTypeArgumentsFrom(newIrFunction) dispatchReceiver = newIrFunction.dispatchReceiverParameter?.let { irGet(it) } - params.forEachIndexed { i, variable -> putValueArgument(i, irGet(variable)) } } is IrSimpleFunction -> +irReturn(dispatchToImplementation(irFunction, newIrFunction, params)) diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/FunctionGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/FunctionGenerator.kt index fb88cc16a6b..687ffdb9c07 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/FunctionGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/FunctionGenerator.kt @@ -289,21 +289,21 @@ class FunctionGenerator(declarationGenerator: DeclarationGenerator) : Declaratio } val bodyGenerator = createBodyGenerator(irFunction.symbol) + + // Declare all the value parameters up first. functionDescriptor.valueParameters.mapTo(irFunction.valueParameters) { valueParameterDescriptor -> val ktParameter = DescriptorToSourceUtils.getSourceFromDescriptor(valueParameterDescriptor) as? KtParameter - generateValueParameterDeclaration(valueParameterDescriptor, ktParameter, bodyGenerator, withDefaultValues, irFunction) + declareParameter(valueParameterDescriptor, ktParameter, irFunction) } - } - - private fun generateValueParameterDeclaration( - valueParameterDescriptor: ValueParameterDescriptor, - ktParameter: KtParameter?, - bodyGenerator: BodyGenerator, - withDefaultValues: Boolean, - irOwnerElement: IrElement - ): IrValueParameter = - declareParameter(valueParameterDescriptor, ktParameter, irOwnerElement).also { irValueParameter -> - if (withDefaultValues) { + // Only after value parameters have been declared, generate default values. This ensures + // that forward references to other parameters works in default value lambdas. For example: + // + // fun f(f1: () -> String = { f2() }, + // f2: () -> String) = f1() + if (withDefaultValues) { + irFunction.valueParameters.forEachIndexed { index, irValueParameter -> + val valueParameterDescriptor = functionDescriptor.valueParameters[index] + val ktParameter = DescriptorToSourceUtils.getSourceFromDescriptor(valueParameterDescriptor) as? KtParameter irValueParameter.defaultValue = ktParameter?.defaultValue?.let { defaultValue -> val inAnnotation = valueParameterDescriptor.containingDeclaration.safeAs()?.isAnnotationConstructor() ?: false @@ -314,6 +314,7 @@ class FunctionGenerator(declarationGenerator: DeclarationGenerator) : Declaratio } } } + } private fun generateReceiverParameterDeclaration( receiverParameterDescriptor: ReceiverParameterDescriptor, diff --git a/compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt b/compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt new file mode 100644 index 00000000000..ee46fb45d3a --- /dev/null +++ b/compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt @@ -0,0 +1,16 @@ +// IGNORE_BACKEND: JS, JS_IR + +fun f( + f1: () -> String = { f2() }, + f2: () -> String = { "FAIL" } +): String = f1() + +fun box(): String { + var result = "fail" + try { + f() + } catch (e : Exception) { + result = "OK" + } + return f(f2 = { "O" }) + f(f1 = { "K" }) +} diff --git a/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.fir.txt b/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.fir.txt new file mode 100644 index 00000000000..72a563ee315 --- /dev/null +++ b/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.fir.txt @@ -0,0 +1,41 @@ +FILE fqName: fileName:/useNextParamInLambda.kt + FUN name:f visibility:public modality:FINAL <> (f1:kotlin.Function0, f2:kotlin.Function0) returnType:kotlin.String + VALUE_PARAMETER name:f1 index:0 type:kotlin.Function0 + EXPRESSION_BODY + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:IrErrorType + BLOCK_BODY + ERROR_CALL 'Unresolved reference: #' type=IrErrorType + VALUE_PARAMETER name:f2 index:1 type:kotlin.Function0 + EXPRESSION_BODY + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + CONST String type=kotlin.String value="FAIL" + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' + CALL 'public abstract fun invoke (): kotlin.String declared in kotlin.Function0' type=kotlin.String origin=null + $this: GET_VAR 'f1: kotlin.Function0 declared in .f' type=kotlin.Function0 origin=null + FUN name:box visibility:public modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + VAR name:result type:kotlin.String [var] + CONST String type=kotlin.String value="fail" + TRY type=kotlin.String + try: CALL 'public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' type=kotlin.String origin=null + CATCH parameter=val e: java.lang.Exception [val] declared in .box + VAR name:e type:java.lang.Exception [val] + BLOCK type=kotlin.Unit origin=null + SET_VAR 'var result: kotlin.String [var] declared in .box' type=kotlin.String origin=null + CONST String type=kotlin.String value="OK" + RETURN type=kotlin.Nothing from='public final fun box (): kotlin.String declared in ' + CALL 'public final fun plus (other: kotlin.Any?): kotlin.String declared in kotlin.String' type=kotlin.String origin=null + $this: CALL 'public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' type=kotlin.String origin=null + f1: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + CONST String type=kotlin.String value="O" + other: CALL 'public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' type=kotlin.String origin=null + f1: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + CONST String type=kotlin.String value="K" diff --git a/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.kt b/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.kt new file mode 100644 index 00000000000..37365373d0d --- /dev/null +++ b/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.kt @@ -0,0 +1,14 @@ +fun f( + f1: () -> String = { f2() }, + f2: () -> String = { "FAIL" } +): String = f1() + +fun box(): String { + var result = "fail" + try { + f() + } catch (e : Exception) { + result = "OK" + } + return f(f2 = { "O" }) + f(f1 = { "K" }) +} diff --git a/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.txt b/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.txt new file mode 100644 index 00000000000..ecb8e162fa4 --- /dev/null +++ b/compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.txt @@ -0,0 +1,48 @@ +FILE fqName: fileName:/useNextParamInLambda.kt + FUN name:f visibility:public modality:FINAL <> (f1:kotlin.Function0, f2:kotlin.Function0) returnType:kotlin.String + VALUE_PARAMETER name:f1 index:0 type:kotlin.Function0 + EXPRESSION_BODY + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String declared in .f' + CALL 'public abstract fun invoke (): R of kotlin.Function0 declared in kotlin.Function0' type=kotlin.String origin=INVOKE + $this: GET_VAR 'f2: kotlin.Function0 declared in .f' type=kotlin.Function0 origin=VARIABLE_AS_FUNCTION + VALUE_PARAMETER name:f2 index:1 type:kotlin.Function0 + EXPRESSION_BODY + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String declared in .f' + CONST String type=kotlin.String value="FAIL" + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' + CALL 'public abstract fun invoke (): R of kotlin.Function0 declared in kotlin.Function0' type=kotlin.String origin=INVOKE + $this: GET_VAR 'f1: kotlin.Function0 declared in .f' type=kotlin.Function0 origin=VARIABLE_AS_FUNCTION + FUN name:box visibility:public modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + VAR name:result type:kotlin.String [var] + CONST String type=kotlin.String value="fail" + TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit + TRY type=kotlin.Any + try: BLOCK type=kotlin.String origin=null + CALL 'public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' type=kotlin.String origin=null + CATCH parameter=val e: java.lang.Exception{ kotlin.Exception } [val] declared in .box + VAR CATCH_PARAMETER name:e type:java.lang.Exception{ kotlin.Exception } [val] + BLOCK type=kotlin.Unit origin=null + SET_VAR 'var result: kotlin.String [var] declared in .box' type=kotlin.Unit origin=EQ + CONST String type=kotlin.String value="OK" + RETURN type=kotlin.Nothing from='public final fun box (): kotlin.String declared in ' + CALL 'public final fun plus (other: kotlin.Any?): kotlin.String declared in kotlin.String' type=kotlin.String origin=PLUS + $this: CALL 'public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' type=kotlin.String origin=null + f2: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String declared in .box' + CONST String type=kotlin.String value="O" + other: CALL 'public final fun f (f1: kotlin.Function0, f2: kotlin.Function0): kotlin.String declared in ' type=kotlin.String origin=null + f1: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String declared in .box' + CONST String type=kotlin.String value="K" diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 11aa096c6be..7acf1513ad5 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -8992,6 +8992,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/defaultArguments/superCallCheck.kt"); } + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt"); + } + @TestMetadata("useThisInLambda.kt") public void testUseThisInLambda() throws Exception { runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index c5fc5e46255..7bd5f7fb89f 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -8992,6 +8992,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/defaultArguments/superCallCheck.kt"); } + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt"); + } + @TestMetadata("useThisInLambda.kt") public void testUseThisInLambda() throws Exception { runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 1af6d052a9c..9c89922d905 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -7877,6 +7877,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/defaultArguments/superCallCheck.kt"); } + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt"); + } + @TestMetadata("useThisInLambda.kt") public void testUseThisInLambda() throws Exception { runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java index 298a9f08036..d4719ca4212 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java @@ -581,6 +581,11 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase { public void testTypeParameterBoundedBySubclass() throws Exception { runTest("compiler/testData/ir/irText/declarations/parameters/typeParameterBoundedBySubclass.kt"); } + + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/ir/irText/declarations/parameters/useNextParamInLambda.kt"); + } } @TestMetadata("compiler/testData/ir/irText/declarations/provideDelegate") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 0d149478a79..45fdeda8c71 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -6722,6 +6722,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/defaultArguments/simpleFromOtherFile.kt"); } + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt"); + } + @TestMetadata("useThisInLambda.kt") public void testUseThisInLambda() throws Exception { runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index bf223d04a43..5f2c6eeac3a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -7807,6 +7807,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/defaultArguments/simpleFromOtherFile.kt"); } + @TestMetadata("useNextParamInLambda.kt") + public void testUseNextParamInLambda() throws Exception { + runTest("compiler/testData/codegen/box/defaultArguments/useNextParamInLambda.kt"); + } + @TestMetadata("useThisInLambda.kt") public void testUseThisInLambda() throws Exception { runTest("compiler/testData/codegen/box/defaultArguments/useThisInLambda.kt");