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");