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.KtForExpression
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
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) =
|
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
|
||||||
ForInArrayLoopGenerator(codegen, forExpression, canCacheArrayLength)
|
ForInArrayLoopGenerator(codegen, forExpression, canCacheArrayLength, shouldAlwaysStoreArrayInNewVar)
|
||||||
|
|
||||||
override fun createInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression): InExpressionGenerator =
|
override fun createInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression): InExpressionGenerator =
|
||||||
CallBasedInExpressionGenerator(codegen, operatorReference)
|
CallBasedInExpressionGenerator(codegen, operatorReference)
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ package org.jetbrains.kotlin.codegen.range
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.codegen.*
|
import org.jetbrains.kotlin.codegen.*
|
||||||
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
@@ -46,8 +48,15 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
|
|||||||
|
|
||||||
val builtIns = state.module.builtIns
|
val builtIns = state.module.builtIns
|
||||||
return when {
|
return when {
|
||||||
asmRangeType.sort == Type.ARRAY ->
|
asmRangeType.sort == Type.ARRAY -> {
|
||||||
ArrayRangeValue(!isLocalVarReference(rangeExpression, bindingContext))
|
val properForInArraySemantics =
|
||||||
|
state.languageVersionSettings.supportsFeature(LanguageFeature.ProperForInArrayLoopRangeVariableAssignmentSemantic)
|
||||||
|
ArrayRangeValue(
|
||||||
|
properForInArraySemantics || !isLocalVarReference(rangeExpression, bindingContext),
|
||||||
|
properForInArraySemantics
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
isPrimitiveRange(rangeType) ->
|
isPrimitiveRange(rangeType) ->
|
||||||
PrimitiveRangeRangeValue()
|
PrimitiveRangeRangeValue()
|
||||||
isPrimitiveProgression(rangeType) ->
|
isPrimitiveProgression(rangeType) ->
|
||||||
@@ -64,7 +73,10 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
|
|||||||
fun isLocalVarReference(rangeExpression: KtExpression, bindingContext: BindingContext): Boolean {
|
fun isLocalVarReference(rangeExpression: KtExpression, bindingContext: BindingContext): Boolean {
|
||||||
if (rangeExpression !is KtSimpleNameExpression) return false
|
if (rangeExpression !is KtSimpleNameExpression) return false
|
||||||
val resultingDescriptor = rangeExpression.getResolvedCall(bindingContext)?.resultingDescriptor ?: 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) =
|
private fun isSubtypeOfString(type: KotlinType, builtIns: KotlinBuiltIns) =
|
||||||
|
|||||||
+3
-4
@@ -29,9 +29,8 @@ import org.jetbrains.org.objectweb.asm.Type
|
|||||||
class ForInArrayLoopGenerator(
|
class ForInArrayLoopGenerator(
|
||||||
codegen: ExpressionCodegen,
|
codegen: ExpressionCodegen,
|
||||||
forExpression: KtForExpression,
|
forExpression: KtForExpression,
|
||||||
// We can cache array length if the corresponding range expression is not a local var.
|
private val canCacheArrayLength: Boolean,
|
||||||
// See https://youtrack.jetbrains.com/issue/KT-21354.
|
private val shouldAlwaysStoreArrayInNewVar: Boolean
|
||||||
private val canCacheArrayLength: Boolean
|
|
||||||
) : AbstractForLoopGenerator(codegen, forExpression) {
|
) : AbstractForLoopGenerator(codegen, forExpression) {
|
||||||
private var indexVar: Int = 0
|
private var indexVar: Int = 0
|
||||||
private var arrayVar: Int = 0
|
private var arrayVar: Int = 0
|
||||||
@@ -46,7 +45,7 @@ class ForInArrayLoopGenerator(
|
|||||||
val loopRange = forExpression.loopRange
|
val loopRange = forExpression.loopRange
|
||||||
val value = codegen.gen(loopRange)
|
val value = codegen.gen(loopRange)
|
||||||
val asmLoopRangeType = codegen.asmType(loopRangeType)
|
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
|
arrayVar = value.index // no need to copy local variable into another variable
|
||||||
}
|
}
|
||||||
else {
|
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
|
// WITH_RUNTIME
|
||||||
// IGNORE_BACKEND: JS
|
// LANGUAGE_VERSION: 1.3
|
||||||
|
|
||||||
// In Kotlin 1.0, in a for-in-array loop, range expression is cached in
|
// 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.
|
// a local variable unless it is already a local variable.
|
||||||
@@ -19,5 +19,5 @@ fun box(): String {
|
|||||||
sum = sum * 10 + x
|
sum = sum * 10 + x
|
||||||
xs = intArrayOf(4, 5)
|
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);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
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);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14555,9 +14555,15 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
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);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14555,9 +14555,15 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
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);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-3
@@ -15827,9 +15827,9 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody12.kt")
|
||||||
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
public void testForInArrayWithArrayVarUpdatedInLoopBody12() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody12.kt");
|
||||||
try {
|
try {
|
||||||
doTest(fileName);
|
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.");
|
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")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user