[FIR] Capture array and indices for postfix/prefix increment/decrement

of array element (including overloaded indexed access operators, e.g.,
`a[b, c]++`).

This prevents double-evaluation of the array and indices expressions,
which may have side-effects.
This commit is contained in:
Mark Punzalan
2020-09-30 07:14:46 +00:00
committed by teamcityserver
parent eb631bc429
commit a2a4d94834
22 changed files with 441 additions and 160 deletions
@@ -12991,6 +12991,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/increment"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@TestMetadata("argumentWithSideEffects.kt")
public void testArgumentWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/increment/argumentWithSideEffects.kt");
}
@TestMetadata("arrayElement.kt")
public void testArrayElement() throws Exception {
runTest("compiler/testData/codegen/box/increment/arrayElement.kt");
@@ -115,6 +115,12 @@ open class BaseConverter(
return null
}
override val LighterASTNode?.arrayExpression: LighterASTNode?
get() = this?.getFirstChildExpression()
override val LighterASTNode?.indexExpressions: List<LighterASTNode>?
get() = this?.getLastChildExpression()?.getChildrenAsArray()?.filterNotNull()?.filter { it.isExpression() }
fun LighterASTNode.getParent(): LighterASTNode? {
return tree.getParent(this)
}
@@ -132,6 +132,12 @@ class RawFirBuilder(
override val PsiElement?.selectorExpression: PsiElement?
get() = (this as? KtQualifiedExpression)?.selectorExpression
override val PsiElement?.arrayExpression: PsiElement?
get() = (this as? KtArrayAccessExpression)?.arrayExpression
override val PsiElement?.indexExpressions: List<PsiElement>?
get() = (this as? KtArrayAccessExpression)?.indexExpressions
private val KtModifierListOwner.visibility: Visibility
get() = with(modifierList) {
when {
@@ -57,14 +57,18 @@ FILE: unary.kt
}
public? final? fun test3(arr: Array<Int>): R|kotlin/Unit| {
lval x1: <implicit> = {
lval <unary>: <implicit> = arr#.get#(IntegerLiteral(0))
arr#.set#(IntegerLiteral(0), R|<local>/<unary>|.inc#())
lval <array>: <implicit> = arr#
lval <index0>: <implicit> = IntegerLiteral(0)
lval <unary>: <implicit> = R|<local>/<array>|.get#(R|<local>/<index0>|)
R|<local>/<array>|.set#(R|<local>/<index0>|, R|<local>/<unary>|.inc#())
R|<local>/<unary>|
}
lval x2: <implicit> = {
lval <unary-result>: <implicit> = arr#.get#(IntegerLiteral(1)).inc#()
arr#.set#(IntegerLiteral(1), R|<local>/<unary-result>|)
lval <array>: <implicit> = arr#
lval <index0>: <implicit> = IntegerLiteral(1)
lval <unary-result>: <implicit> = R|<local>/<array>|.get#(R|<local>/<index0>|).inc#()
R|<local>/<array>|.set#(R|<local>/<index0>|, R|<local>/<unary-result>|)
R|<local>/<unary-result>|
}
@@ -80,14 +84,18 @@ FILE: unary.kt
}
public? final? fun test4(y: Y): R|kotlin/Unit| {
lval x1: <implicit> = {
lval <unary>: <implicit> = y#.arr#.get#(IntegerLiteral(0))
y#.arr#.set#(IntegerLiteral(0), R|<local>/<unary>|.inc#())
lval <array>: <implicit> = y#.arr#
lval <index0>: <implicit> = IntegerLiteral(0)
lval <unary>: <implicit> = R|<local>/<array>|.get#(R|<local>/<index0>|)
R|<local>/<array>|.set#(R|<local>/<index0>|, R|<local>/<unary>|.inc#())
R|<local>/<unary>|
}
lval x2: <implicit> = {
lval <unary-result>: <implicit> = y#.arr#.get#(IntegerLiteral(1)).inc#()
y#.arr#.set#(IntegerLiteral(1), R|<local>/<unary-result>|)
lval <array>: <implicit> = y#.arr#
lval <index0>: <implicit> = IntegerLiteral(1)
lval <unary-result>: <implicit> = R|<local>/<array>|.get#(R|<local>/<index0>|).inc#()
R|<local>/<array>|.set#(R|<local>/<index0>|, R|<local>/<unary-result>|)
R|<local>/<unary-result>|
}
@@ -55,6 +55,8 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
abstract fun T.getChildNodeByType(type: IElementType): T?
abstract val T?.receiverExpression: T?
abstract val T?.selectorExpression: T?
abstract val T?.arrayExpression: T?
abstract val T?.indexExpressions: List<T>?
/**** Class name utils ****/
inline fun <T> withChildClassName(
@@ -435,6 +437,17 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
)
}
if (unwrappedArgument.elementType == ARRAY_ACCESS_EXPRESSION) {
return generateIncrementOrDecrementBlockForArrayAccess(
baseExpression,
operationReference,
unwrappedArgument,
callName,
prefix,
convert
)
}
return buildBlock {
val baseSource = baseExpression?.toFirSourceElement()
val desugaredSource = baseSource?.fakeElement(FirFakeSourceElementKind.DesugaredIncrementOrDecrement)
@@ -623,6 +636,147 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
}
}
/**
* given:
* a[b, c]++
*
* result:
* {
* val <array> = a
* val <index0> = b
* val <index1> = c
* val <unary> = <array>.get(b, c)
* <array>.set(b, c, <unary>.inc())
* ^<unary>
* }
*
* given:
* ++a[b, c]
*
* result:
* {
* val <array> = a
* val <index0> = b
* val <index1> = c
* val <unary-result> = <array>.get(b, c).inc()
* <array>.set(b, c, <unary-result>)
* ^<unary-result>
* }
*
*/
private fun generateIncrementOrDecrementBlockForArrayAccess(
baseExpression: T,
operationReference: T?,
argument: T,
callName: Name,
prefix: Boolean,
convert: T.() -> FirExpression
): FirExpression {
return buildBlock {
val baseSource = baseExpression?.toFirSourceElement()
val desugaredSource = baseSource?.fakeElement(FirFakeSourceElementKind.DesugaredIncrementOrDecrement)
source = desugaredSource
val array = argument.arrayExpression
val indices = argument.indexExpressions
requireNotNull(indices) { "No indices in ${baseExpression.asText}" }
val arrayVariable = generateTemporaryVariable(
this@BaseFirBuilder.baseSession,
array?.toFirSourceElement(),
Name.special("<array>"),
array?.convert() ?: buildErrorExpression {
source = argument.toFirSourceElement()
diagnostic = ConeSimpleDiagnostic("No array expression", DiagnosticKind.Syntax)
}
).also { statements += it }
val indexVariables = indices.mapIndexed { i, index ->
generateTemporaryVariable(
this@BaseFirBuilder.baseSession,
index.toFirSourceElement(),
Name.special("<index$i>"),
index.convert()
).also { statements += it }
}
val firArgument = buildFunctionCall {
source = desugaredSource
calleeReference = buildSimpleNamedReference {
source = argument?.toFirSourceElement()
name = OperatorNameConventions.GET
}
explicitReceiver = generateResolvedAccessExpression(arrayVariable.source, arrayVariable)
argumentList = buildArgumentList {
for (indexVar in indexVariables) {
arguments += generateResolvedAccessExpression(indexVar.source, indexVar)
}
}
}
// initialValueVar is only used for postfix increment/decrement (stores the argument value before increment/decrement).
val initialValueVar = generateTemporaryVariable(
this@BaseFirBuilder.baseSession,
desugaredSource,
Name.special("<unary>"),
firArgument
)
// resultInitializer is the expression for `argument.inc()`
val resultInitializer = buildFunctionCall {
source = desugaredSource
calleeReference = buildSimpleNamedReference {
source = operationReference?.toFirSourceElement()
name = callName
}
explicitReceiver = if (prefix) {
firArgument
} else {
generateResolvedAccessExpression(desugaredSource, initialValueVar)
}
}
// resultVar is only used for prefix increment/decrement.
val resultVar = generateTemporaryVariable(
this@BaseFirBuilder.baseSession,
desugaredSource,
Name.special("<unary-result>"),
resultInitializer
)
fun appendAssignment() {
statements += buildFunctionCall {
source = desugaredSource
calleeReference = buildSimpleNamedReference {
source = argument.toFirSourceElement()
name = OperatorNameConventions.SET
}
explicitReceiver = generateResolvedAccessExpression(arrayVariable.source, arrayVariable)
argumentList = buildArgumentList {
for (indexVar in indexVariables) {
arguments += generateResolvedAccessExpression(indexVar.source, indexVar)
}
arguments += if (prefix) {
generateResolvedAccessExpression(source, resultVar)
} else {
resultInitializer
}
}
}
}
if (prefix) {
statements += resultVar
appendAssignment()
statements += generateResolvedAccessExpression(desugaredSource, resultVar)
} else {
statements += initialValueVar
appendAssignment()
statements += generateResolvedAccessExpression(desugaredSource, initialValueVar)
}
}
}
private fun FirQualifiedAccessBuilder.initializeLValue(
left: T?,
convertQualified: T.() -> FirQualifiedAccess?