Skip to content

Commit b489e93

Browse files
committed
Fix lowering of receiver access in IR scripting
#KT-53947 fixed without the fix, in the presence of the implicit receiver of the same type as the processed receive, the lowering was incorrectly substituting the correct receiver with the accessor to the implicit one
1 parent 8a8853c commit b489e93

File tree

3 files changed

+108
-46
lines changed

3 files changed

+108
-46
lines changed

compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ScriptLowering.kt

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ private class ScriptToClassTransformer(
598598
val ctorDispatchReceiverType = expression.symbol.owner.dispatchReceiverParameter?.type
599599
?: if (capturingClassesConstructors.keys.any { it.symbol == expression.symbol }) scriptClassReceiver.type else null
600600
if (ctorDispatchReceiverType != null) {
601-
getDispatchReceiverExpression(data, expression, ctorDispatchReceiverType, expression.origin)?.let {
601+
getDispatchReceiverExpression(data, expression, ctorDispatchReceiverType, expression.origin, null)?.let {
602602
expression.dispatchReceiver = it
603603
}
604604
}
@@ -607,18 +607,29 @@ private class ScriptToClassTransformer(
607607
}
608608

609609
private fun getDispatchReceiverExpression(
610-
data: ScriptToClassTransformerContext, expression: IrDeclarationReference, receiverType: IrType, origin: IrStatementOrigin?,
610+
data: ScriptToClassTransformerContext,
611+
expression: IrDeclarationReference,
612+
receiverType: IrType,
613+
origin: IrStatementOrigin?,
614+
originalReceiverParameter: IrValueParameter?,
611615
): IrExpression? {
612616
return if (receiverType == scriptClassReceiver.type) {
613-
getAccessCallForScriptInstance(data, expression.startOffset, expression.endOffset, origin)
617+
getAccessCallForScriptInstance(data, expression.startOffset, expression.endOffset, origin, originalReceiverParameter)
614618
} else {
615-
getAccessCallForImplicitReceiver(data, expression, receiverType, origin)
619+
getAccessCallForImplicitReceiver(data, expression, receiverType, origin, originalReceiverParameter)
616620
}
617621
}
618622

619623
private fun getAccessCallForScriptInstance(
620-
data: ScriptToClassTransformerContext, startOffset: Int, endOffset: Int, origin: IrStatementOrigin?
621-
) = when {
624+
data: ScriptToClassTransformerContext,
625+
startOffset: Int,
626+
endOffset: Int,
627+
origin: IrStatementOrigin?,
628+
originalReceiverParameter: IrValueParameter?
629+
): IrExpression? = when {
630+
originalReceiverParameter != null && originalReceiverParameter != scriptClassReceiver ->
631+
null
632+
622633
data.fieldForScriptThis != null ->
623634
IrGetFieldImpl(
624635
startOffset, endOffset,
@@ -634,30 +645,36 @@ private class ScriptToClassTransformer(
634645
origin
635646
)
636647
}
648+
637649
data.valueParameterForScriptThis != null ->
638650
IrGetValueImpl(
639651
startOffset, endOffset,
640652
scriptClassReceiver.type,
641653
data.valueParameterForScriptThis,
642654
origin
643655
)
656+
644657
else -> error("Unexpected script transformation state: $data")
645658
}
646659

647660
private fun getAccessCallForImplicitReceiver(
648661
data: ScriptToClassTransformerContext,
649662
expression: IrDeclarationReference,
650663
receiverType: IrType,
651-
expressionOrigin: IrStatementOrigin?
664+
expressionOrigin: IrStatementOrigin?,
665+
originalReceiverParameter: IrValueParameter?
652666
): IrExpression? {
653667
// implicit receivers has priority (as per descriptor outer scopes)
654-
implicitReceiversFieldsWithParameters.firstOrNull { it.second.type == receiverType }?.let { (field, param) ->
668+
implicitReceiversFieldsWithParameters.firstOrNull {
669+
if (originalReceiverParameter != null) it.second == originalReceiverParameter
670+
else it.second.type == receiverType
671+
}?.let { (field, param) ->
655672
val builder = context.createIrBuilder(expression.symbol)
656673
return if (data.isInScriptConstructor) {
657674
builder.irGet(param.type, param.symbol)
658675
} else {
659676
val scriptReceiver =
660-
getAccessCallForScriptInstance(data, expression.startOffset, expression.endOffset, expressionOrigin)
677+
getAccessCallForScriptInstance(data, expression.startOffset, expression.endOffset, expressionOrigin, null)
661678
builder.irGetField(scriptReceiver, field)
662679
}
663680
}
@@ -692,7 +709,7 @@ private class ScriptToClassTransformer(
692709
builder.irGet(objArray.defaultType, irScript.earlierScriptsParameter!!.symbol)
693710
} else {
694711
val scriptReceiver =
695-
getAccessCallForScriptInstance(data, expression.startOffset, expression.endOffset, expressionOrigin)
712+
getAccessCallForScriptInstance(data, expression.startOffset, expression.endOffset, expressionOrigin, null)
696713
builder.irGetField(scriptReceiver, earlierScriptsField!!)
697714
}
698715
val getPrevScriptObjectExpression = builder.irCall(objArrayGet).apply {
@@ -711,40 +728,13 @@ private class ScriptToClassTransformer(
711728
return null
712729
}
713730

714-
override fun visitGetField(expression: IrGetField, data: ScriptToClassTransformerContext): IrExpression {
715-
if (irScript.needsReceiverProcessing) {
716-
val receiver = expression.receiver
717-
if (receiver is IrGetValue && receiver.symbol.owner.name == SpecialNames.THIS) {
718-
val newReceiver = getDispatchReceiverExpression(data, expression, receiver.type, expression.origin)
719-
if (newReceiver != null) {
720-
val newGetField =
721-
IrGetFieldImpl(expression.startOffset, expression.endOffset, expression.symbol, expression.type, newReceiver)
722-
return super.visitGetField(newGetField, data)
723-
}
724-
}
725-
}
726-
return super.visitGetField(expression, data)
727-
}
728-
729-
override fun visitCall(expression: IrCall, data: ScriptToClassTransformerContext): IrExpression {
730-
if (irScript.needsReceiverProcessing) {
731-
val target = expression.symbol.owner
732-
val receiver: IrValueParameter? = target.dispatchReceiverParameter
733-
if (receiver?.name == SpecialNames.THIS) {
734-
val newReceiver = getDispatchReceiverExpression(data, expression, receiver.type, expression.origin)
735-
if (newReceiver != null) {
736-
expression.dispatchReceiver = newReceiver
737-
}
738-
}
739-
}
740-
return super.visitCall(expression, data) as IrExpression
741-
}
742-
743731
override fun visitGetValue(expression: IrGetValue, data: ScriptToClassTransformerContext): IrExpression {
744732
if (irScript.needsReceiverProcessing) {
745733
val getValueParameter = expression.symbol.owner as? IrValueParameter
746734
if (getValueParameter != null && getValueParameter.name == SpecialNames.THIS) {
747-
val newExpression = getDispatchReceiverExpression(data, expression, getValueParameter.type, expression.origin)
735+
val newExpression = getDispatchReceiverExpression(
736+
data, expression, getValueParameter.type, expression.origin, getValueParameter
737+
)
748738
if (newExpression != null) {
749739
return super.visitExpression(newExpression, data)
750740
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package kotlin.script.experimental.jvmhost.test
7+
8+
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
9+
import org.junit.Test
10+
import kotlin.script.experimental.api.*
11+
import kotlin.test.assertTrue
12+
13+
class CapturingTest {
14+
15+
@Test
16+
fun testScriptWithImplicitReceiverAndSimpleCapturing() {
17+
// Reproducing (a bit extended) scenario from KT-53947: without the fix, in the presence of the implicit receiver
18+
// of the same type as the receiver in the `apply` function body, the lowering was incorrectly substituting
19+
// the correct receiver with the accessor to the implicit one
20+
val res = evalString<ScriptWithImplicitReceiver>(
21+
"""
22+
class C {
23+
fun foo() = receiverString + "."
24+
}
25+
26+
C().foo()
27+
""".trimIndent()
28+
) {
29+
implicitReceivers(ImplicitReceiverClass("Ok"))
30+
}
31+
32+
assertTrue(
33+
res.safeAs<ResultWithDiagnostics.Success<EvaluationResult>>()?.value?.returnValue?.safeAs<ResultValue.Value>()?.value == "Ok.",
34+
"test failed:\n ${res.render()}"
35+
)
36+
}
37+
38+
@Test
39+
fun testScriptWithImplicitReceiverAndNoCapturing() {
40+
// Reproducing (a bit extended) scenario from KT-53947: without the fix, in the presence of the implicit receiver
41+
// of the same type as the receiver in the `C2.apply` function body, the lowering was incorrectly substituting
42+
// the correct receiver with the accessor to the implicit one
43+
// `C1.foo` tests the similar situation with extension receiver.
44+
val res = evalString<ScriptWithImplicitReceiver>(
45+
"""
46+
import kotlin.script.experimental.jvmhost.test.ImplicitReceiverClass
47+
48+
class C1 {
49+
fun run(receiver: ImplicitReceiverClass): String = receiver.foo()
50+
51+
fun ImplicitReceiverClass.foo() = "--" + receiverString
52+
}
53+
54+
class C2 {
55+
fun apply(receiver: ImplicitReceiverClass): String =
56+
"++" + receiver.receiverString
57+
}
58+
59+
C2().apply(
60+
ImplicitReceiverClass(
61+
C1().run(ImplicitReceiverClass("Ok"))
62+
)
63+
)
64+
""".trimIndent()
65+
) {
66+
implicitReceivers(ImplicitReceiverClass("Not Ok."))
67+
}
68+
69+
assertTrue(
70+
res.safeAs<ResultWithDiagnostics.Success<EvaluationResult>>()?.value?.returnValue?.safeAs<ResultValue.Value>()?.value == "++--Ok",
71+
"test failed:\n ${res.render()}"
72+
)
73+
}
74+
}

libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ConstructorArgumentOrderTest.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
package kotlin.script.experimental.jvmhost.test
77

88
import org.junit.Test
9-
import kotlin.script.experimental.api.EvaluationResult
10-
import kotlin.script.experimental.api.ResultWithDiagnostics
11-
import kotlin.script.experimental.api.implicitReceivers
12-
import kotlin.script.experimental.api.providedProperties
9+
import kotlin.script.experimental.api.*
1310
import kotlin.test.assertTrue
1411

1512
class ConstructorArgumentsOrderTest {
@@ -51,6 +48,7 @@ class ConstructorArgumentsOrderTest {
5148
)
5249
}
5350

54-
private fun ResultWithDiagnostics<EvaluationResult>.render() =
55-
reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception!!.printStackTrace()}" }
5651
}
52+
53+
internal fun ResultWithDiagnostics<EvaluationResult>.render() =
54+
reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception!!.printStackTrace()}" }

0 commit comments

Comments
 (0)