Generate for-in-array loops with proper semantics in 1.3+
In Kotlin 1.3+, assignment to the for-in-array loop range variable in the loop body doesn't affect loop execution (as if it was a loop on an array iterator, or some other container). #KT-21354 In Progress #KT-21321 In Progress
This commit is contained in:
@@ -23,9 +23,12 @@ import org.jetbrains.kotlin.codegen.range.inExpression.InExpressionGenerator
|
||||
import org.jetbrains.kotlin.psi.KtForExpression
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
|
||||
class ArrayRangeValue(private val canCacheArrayLength: Boolean) : RangeValue {
|
||||
class ArrayRangeValue(
|
||||
private val canCacheArrayLength: Boolean,
|
||||
private val shouldAlwaysStoreArrayInNewVar: Boolean
|
||||
) : RangeValue {
|
||||
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
|
||||
ForInArrayLoopGenerator(codegen, forExpression, canCacheArrayLength)
|
||||
ForInArrayLoopGenerator(codegen, forExpression, canCacheArrayLength, shouldAlwaysStoreArrayInNewVar)
|
||||
|
||||
override fun createInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression): InExpressionGenerator =
|
||||
CallBasedInExpressionGenerator(codegen, operatorReference)
|
||||
|
||||
@@ -18,8 +18,10 @@ package org.jetbrains.kotlin.codegen.range
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -46,8 +48,15 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
|
||||
|
||||
val builtIns = state.module.builtIns
|
||||
return when {
|
||||
asmRangeType.sort == Type.ARRAY ->
|
||||
ArrayRangeValue(!isLocalVarReference(rangeExpression, bindingContext))
|
||||
asmRangeType.sort == Type.ARRAY -> {
|
||||
val properForInArraySemantics =
|
||||
state.languageVersionSettings.supportsFeature(LanguageFeature.ProperForInArrayLoopRangeVariableAssignmentSemantic)
|
||||
ArrayRangeValue(
|
||||
properForInArraySemantics || !isLocalVarReference(rangeExpression, bindingContext),
|
||||
properForInArraySemantics
|
||||
)
|
||||
}
|
||||
|
||||
isPrimitiveRange(rangeType) ->
|
||||
PrimitiveRangeRangeValue()
|
||||
isPrimitiveProgression(rangeType) ->
|
||||
@@ -64,7 +73,10 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
|
||||
fun isLocalVarReference(rangeExpression: KtExpression, bindingContext: BindingContext): Boolean {
|
||||
if (rangeExpression !is KtSimpleNameExpression) return false
|
||||
val resultingDescriptor = rangeExpression.getResolvedCall(bindingContext)?.resultingDescriptor ?: return false
|
||||
return resultingDescriptor is LocalVariableDescriptor && resultingDescriptor.isVar
|
||||
return resultingDescriptor is LocalVariableDescriptor &&
|
||||
resultingDescriptor !is SyntheticFieldDescriptor &&
|
||||
!resultingDescriptor.isDelegated &&
|
||||
resultingDescriptor.isVar
|
||||
}
|
||||
|
||||
private fun isSubtypeOfString(type: KotlinType, builtIns: KotlinBuiltIns) =
|
||||
|
||||
+3
-4
@@ -29,9 +29,8 @@ import org.jetbrains.org.objectweb.asm.Type
|
||||
class ForInArrayLoopGenerator(
|
||||
codegen: ExpressionCodegen,
|
||||
forExpression: KtForExpression,
|
||||
// We can cache array length if the corresponding range expression is not a local var.
|
||||
// See https://youtrack.jetbrains.com/issue/KT-21354.
|
||||
private val canCacheArrayLength: Boolean
|
||||
private val canCacheArrayLength: Boolean,
|
||||
private val shouldAlwaysStoreArrayInNewVar: Boolean
|
||||
) : AbstractForLoopGenerator(codegen, forExpression) {
|
||||
private var indexVar: Int = 0
|
||||
private var arrayVar: Int = 0
|
||||
@@ -46,7 +45,7 @@ class ForInArrayLoopGenerator(
|
||||
val loopRange = forExpression.loopRange
|
||||
val value = codegen.gen(loopRange)
|
||||
val asmLoopRangeType = codegen.asmType(loopRangeType)
|
||||
if (value is StackValue.Local && value.type == asmLoopRangeType) {
|
||||
if (!shouldAlwaysStoreArrayInNewVar && value is StackValue.Local && value.type == asmLoopRangeType) {
|
||||
arrayVar = value.index // no need to copy local variable into another variable
|
||||
}
|
||||
else {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// WITH_RUNTIME
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
fun box(): String {
|
||||
var xs = intArrayOf(1, 2, 3)
|
||||
var sum = 0
|
||||
for (x in xs) {
|
||||
sum = sum * 10 + x
|
||||
xs = intArrayOf(4, 5)
|
||||
}
|
||||
return if (sum == 15) "OK" else "Fail: $sum"
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND: JS
|
||||
// LANGUAGE_VERSION: 1.3
|
||||
|
||||
// In Kotlin 1.0, in a for-in-array loop, range expression is cached in
|
||||
// a local variable unless it is already a local variable.
|
||||
@@ -19,5 +19,5 @@ fun box(): String {
|
||||
sum = sum * 10 + x
|
||||
xs = intArrayOf(4, 5)
|
||||
}
|
||||
return if (sum == 15) "OK" else "Fail: $sum"
|
||||
return if (sum == 123) "OK" else "Fail: $sum"
|
||||
}
|
||||
+9
-3
@@ -14555,9 +14555,15 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody12.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody13.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody13() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody13.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
|
||||
@@ -14555,9 +14555,15 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody12.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody13.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody13() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody13.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
|
||||
@@ -14555,9 +14555,15 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody12.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody13.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody13() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody13.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
|
||||
+9
-3
@@ -15827,9 +15827,9 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody12.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
@@ -15839,6 +15839,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
}
|
||||
|
||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody13.kt")
|
||||
public void testForInArrayWithArrayVarUpdatedInLoopBody13() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody13.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||
|
||||
Reference in New Issue
Block a user