[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:
Mads Ager
2019-10-08 15:41:01 +02:00
committed by max-kammerer
parent 834eeb567b
commit 92cf521e11
13 changed files with 181 additions and 14 deletions
@@ -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")
@@ -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))
@@ -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" })
}
@@ -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"
@@ -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");
@@ -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");
@@ -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")
@@ -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");
@@ -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");