[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.
This commit is contained in:
+5
@@ -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")
|
||||
|
||||
+14
-2
@@ -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))
|
||||
|
||||
+13
-12
@@ -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<ConstructorDescriptor>()?.isAnnotationConstructor() ?: false
|
||||
@@ -314,6 +314,7 @@ class FunctionGenerator(declarationGenerator: DeclarationGenerator) : Declaratio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateReceiverParameterDeclaration(
|
||||
receiverParameterDescriptor: ReceiverParameterDescriptor,
|
||||
|
||||
@@ -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" })
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
FILE fqName:<root> fileName:/useNextParamInLambda.kt
|
||||
FUN name:f visibility:public modality:FINAL <> (f1:kotlin.Function0<kotlin.String>, f2:kotlin.Function0<kotlin.String>) returnType:kotlin.String
|
||||
VALUE_PARAMETER name:f1 index:0 type:kotlin.Function0<kotlin.String>
|
||||
EXPRESSION_BODY
|
||||
FUN_EXPR type=kotlin.Function0<IrErrorType> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:IrErrorType
|
||||
BLOCK_BODY
|
||||
ERROR_CALL 'Unresolved reference: <Unresolved name: f2>#' type=IrErrorType
|
||||
VALUE_PARAMETER name:f2 index:1 type:kotlin.Function0<kotlin.String>
|
||||
EXPRESSION_BODY
|
||||
FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> 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<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>'
|
||||
CALL 'public abstract fun invoke (): kotlin.String declared in kotlin.Function0' type=kotlin.String origin=null
|
||||
$this: GET_VAR 'f1: kotlin.Function0<kotlin.String> declared in <root>.f' type=kotlin.Function0<kotlin.String> 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<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>' type=kotlin.String origin=null
|
||||
CATCH parameter=val e: java.lang.Exception [val] declared in <root>.box
|
||||
VAR name:e type:java.lang.Exception [val]
|
||||
BLOCK type=kotlin.Unit origin=null
|
||||
SET_VAR 'var result: kotlin.String [var] declared in <root>.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 <root>'
|
||||
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<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>' type=kotlin.String origin=null
|
||||
f1: FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> 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<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>' type=kotlin.String origin=null
|
||||
f1: FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String
|
||||
BLOCK_BODY
|
||||
CONST String type=kotlin.String value="K"
|
||||
@@ -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" })
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
FILE fqName:<root> fileName:/useNextParamInLambda.kt
|
||||
FUN name:f visibility:public modality:FINAL <> (f1:kotlin.Function0<kotlin.String>, f2:kotlin.Function0<kotlin.String>) returnType:kotlin.String
|
||||
VALUE_PARAMETER name:f1 index:0 type:kotlin.Function0<kotlin.String>
|
||||
EXPRESSION_BODY
|
||||
FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String
|
||||
BLOCK_BODY
|
||||
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String declared in <root>.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<kotlin.String> declared in <root>.f' type=kotlin.Function0<kotlin.String> origin=VARIABLE_AS_FUNCTION
|
||||
VALUE_PARAMETER name:f2 index:1 type:kotlin.Function0<kotlin.String>
|
||||
EXPRESSION_BODY
|
||||
FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String
|
||||
BLOCK_BODY
|
||||
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String declared in <root>.f'
|
||||
CONST String type=kotlin.String value="FAIL"
|
||||
BLOCK_BODY
|
||||
RETURN type=kotlin.Nothing from='public final fun f (f1: kotlin.Function0<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>'
|
||||
CALL 'public abstract fun invoke (): R of kotlin.Function0 declared in kotlin.Function0' type=kotlin.String origin=INVOKE
|
||||
$this: GET_VAR 'f1: kotlin.Function0<kotlin.String> declared in <root>.f' type=kotlin.Function0<kotlin.String> 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<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>' type=kotlin.String origin=null
|
||||
CATCH parameter=val e: java.lang.Exception{ kotlin.Exception } [val] declared in <root>.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 <root>.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 <root>'
|
||||
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<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>' type=kotlin.String origin=null
|
||||
f2: FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String
|
||||
BLOCK_BODY
|
||||
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String declared in <root>.box'
|
||||
CONST String type=kotlin.String value="O"
|
||||
other: CALL 'public final fun f (f1: kotlin.Function0<kotlin.String>, f2: kotlin.Function0<kotlin.String>): kotlin.String declared in <root>' type=kotlin.String origin=null
|
||||
f1: FUN_EXPR type=kotlin.Function0<kotlin.String> origin=LAMBDA
|
||||
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String
|
||||
BLOCK_BODY
|
||||
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String declared in <root>.box'
|
||||
CONST String type=kotlin.String value="K"
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
@@ -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")
|
||||
|
||||
Generated
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user