Maintain proper evaluation order for 'a in x .. y'

As of Kotlin 1.0 and 1.1, expression 'a in x .. y' is considered
equivalent to 'x.rangeTo(y).a', and should be evaluated in the following
order:
1. x
2. y
3. a
4. compare x with a
5. compare y with a (if needed)
This commit is contained in:
Dmitry Petrov
2017-07-07 12:31:23 +03:00
parent fc3e9318d9
commit 905a16e1df
14 changed files with 298 additions and 44 deletions
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.range
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
abstract class AbstractBoundedValue(
protected val codegen: ExpressionCodegen,
protected val rangeCall: ResolvedCall<out CallableDescriptor>,
override val isLowInclusive: Boolean = true,
override val isHighInclusive: Boolean = true
) : BoundedValue {
override val instanceType: Type = codegen.asmType(rangeCall.resultingDescriptor.returnType!!)
override fun putInstance(v: InstructionAdapter, type: Type) {
codegen.invokeFunction(rangeCall.call, rangeCall, StackValue.none()).put(type, v)
}
}
@@ -25,19 +25,21 @@ import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class ArrayIndicesRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) {
private val expectedReceiverType: KotlinType = ExpressionCodegen.getExpectedReceiverType(rangeCall)
override fun getBoundedValue(codegen: ExpressionCodegen) =
SimpleBoundedValue(
codegen, rangeCall,
lowBound = StackValue.constant(0, asmElementType),
highBound = StackValue.operation(Type.INT_TYPE) { v ->
codegen.generateCallReceiver(rangeCall).put(codegen.asmType(expectedReceiverType), v)
v.arraylength()
}
)
object : AbstractBoundedValue(codegen, rangeCall) {
override fun putHighLow(v: InstructionAdapter, type: Type) {
codegen.generateCallReceiver(rangeCall).put(codegen.asmType(expectedReceiverType), v)
v.arraylength()
StackValue.coerce(Type.INT_TYPE, type, v)
StackValue.constant(0, asmElementType).put(type, v)
}
}
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
ForInArrayIndicesRangeLoopGenerator(codegen, forExpression, rangeCall)
@@ -25,19 +25,21 @@ import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class CharSequenceIndicesRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) {
private val expectedReceiverType: KotlinType = ExpressionCodegen.getExpectedReceiverType(rangeCall)
override fun getBoundedValue(codegen: ExpressionCodegen) =
SimpleBoundedValue(
codegen, rangeCall,
lowBound = StackValue.constant(0, asmElementType),
highBound = StackValue.operation(Type.INT_TYPE) { v ->
codegen.generateCallReceiver(rangeCall).put(codegen.asmType(expectedReceiverType), v)
v.invokeinterface("java/lang/CharSequence", "length", "()I")
}
)
object : AbstractBoundedValue(codegen, rangeCall) {
override fun putHighLow(v: InstructionAdapter, type: Type) {
codegen.generateCallReceiver(rangeCall).put(codegen.asmType(expectedReceiverType), v)
v.invokeinterface("java/lang/CharSequence", "length", "()I")
StackValue.coerce(Type.INT_TYPE, type, v)
StackValue.constant(0, asmElementType).put(type, v)
}
}
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
ForInCharSequenceIndicesRangeLoopGenerator(codegen, forExpression, rangeCall)
@@ -25,19 +25,21 @@ import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class CollectionIndicesRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) {
private val expectedReceiverType: KotlinType = ExpressionCodegen.getExpectedReceiverType(rangeCall)
override fun getBoundedValue(codegen: ExpressionCodegen) =
SimpleBoundedValue(
codegen, rangeCall,
lowBound = StackValue.constant(0, asmElementType),
highBound = StackValue.operation(Type.INT_TYPE) { v ->
codegen.generateCallReceiver(rangeCall).put(codegen.asmType(expectedReceiverType), v)
v.invokeinterface("java/util/Collection", "size", "()I")
}
)
object : AbstractBoundedValue(codegen, rangeCall) {
override fun putHighLow(v: InstructionAdapter, type: Type) {
codegen.generateCallReceiver(rangeCall).put(codegen.asmType(expectedReceiverType), v)
v.invokeinterface("java/util/Collection", "size", "()I")
StackValue.coerce(Type.INT_TYPE, type, v)
StackValue.constant(0, asmElementType).put(type, v)
}
}
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
ForInCollectionIndicesRangeLoopGenerator(codegen, forExpression, rangeCall)
@@ -37,7 +37,10 @@ interface BoundedValue {
fun putInstance(v: InstructionAdapter, type: Type)
// It is necessary to maintain the proper evaluation order as of Kotlin 1.0 and 1.1
// to evaluate range bounds left to right and put them on stack as 'high; low'.
fun putHighLow(v: InstructionAdapter, type: Type)
val isLowInclusive: Boolean
val isHighInclusive: Boolean
}
@@ -16,23 +16,20 @@
package org.jetbrains.kotlin.codegen.range
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.generateCallReceiver
import org.jetbrains.kotlin.codegen.generateCallSingleArgument
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class SimpleBoundedValue(
private val codegen: ExpressionCodegen,
private val rangeCall: ResolvedCall<out CallableDescriptor>,
codegen: ExpressionCodegen,
rangeCall: ResolvedCall<out CallableDescriptor>,
private val lowBound: StackValue,
override val isLowInclusive: Boolean,
isLowInclusive: Boolean,
private val highBound: StackValue,
override val isHighInclusive: Boolean
): BoundedValue {
isHighInclusive: Boolean
): AbstractBoundedValue(codegen, rangeCall, isLowInclusive, isHighInclusive) {
constructor(
codegen: ExpressionCodegen,
rangeCall: ResolvedCall<out CallableDescriptor>,
@@ -54,18 +51,9 @@ class SimpleBoundedValue(
highBound: StackValue
) : this(codegen, rangeCall, lowBound, true, highBound, true)
override val instanceType: Type = codegen.asmType(rangeCall.resultingDescriptor.returnType!!)
override fun putInstance(v: InstructionAdapter, type: Type) {
codegen.invokeFunction(rangeCall.call, rangeCall, StackValue.none()).put(type, v)
}
override fun putHighLow(v: InstructionAdapter, type: Type) {
highBound.put(type, v)
lowBound.put(type, v)
}
companion object {
highBound.put(type, v)
AsmUtil.swap(v, type, type)
}
}
@@ -0,0 +1,27 @@
// WITH_RUNTIME
val order = StringBuilder()
inline fun expectOrder(at: String, expected: String, body: () -> Unit) {
order.setLength(0)
body()
if (order.toString() != expected) throw AssertionError("$at: expected: $expected, actual: $order")
}
fun list(): List<Int> {
order.append("L")
return emptyList()
}
fun x(i: Int): Int {
order.append("X")
return i
}
fun box(): String {
expectOrder("1 in []", "LX") { x(1) in list() }
expectOrder("1 !in []", "LX") { x(1) !in list() }
return "OK"
}
@@ -0,0 +1,35 @@
// WITH_RUNTIME
val order = StringBuilder()
inline fun expectOrder(at: String, expected: String, body: () -> Unit) {
order.setLength(0)
body()
if (order.toString() != expected) {
throw AssertionError("$at: expected: '$expected', actual: '$order'")
}
}
class Z(val x: Int) : Comparable<Z> {
override fun compareTo(other: Z): Int {
order.append("c:$x,${other.x} ")
return x.compareTo(other.x)
}
}
fun z(i: Int): Z {
order.append("z:$i ")
return Z(i)
}
fun box(): String {
expectOrder("z0 in z1 .. z3", "z:1 z:3 z:0 c:1,0 ") { z(0) in z(1) .. z(3) }
expectOrder("z2 in z1 .. z3", "z:1 z:3 z:2 c:1,2 c:3,2 ") { z(2) in z(1) .. z(3) }
expectOrder("z4 in z1 .. z3", "z:1 z:4 z:2 c:1,2 c:4,2 ") { z(2) in z(1) .. z(4) }
expectOrder("z0 !in z1 .. z3", "z:1 z:3 z:0 c:1,0 ") { z(0) !in z(1) .. z(3) }
expectOrder("z2 !in z1 .. z3", "z:1 z:3 z:2 c:1,2 c:3,2 ") { z(2) !in z(1) .. z(3) }
expectOrder("z4 !in z1 .. z3", "z:1 z:4 z:2 c:1,2 c:4,2 ") { z(2) !in z(1) .. z(4) }
return "OK"
}
@@ -0,0 +1,31 @@
// WITH_RUNTIME
val order = StringBuilder()
inline fun expectOrder(at: String, expected: String, body: () -> Unit) {
order.setLength(0)
body()
if (order.toString() != expected) throw AssertionError("$at: expected: $expected, actual: $order")
}
fun low(i: Int): Int {
order.append("L")
return i
}
fun high(i: Int): Int {
order.append("H")
return i
}
fun x(i: Int): Int {
order.append("X")
return i
}
fun box(): String {
expectOrder("0 in 1 .. 3", "HLX") { x(0) in high(3) downTo low(1) }
expectOrder("0 !in 1 .. 3", "HLX") { x(0) !in high(3) downTo low(1) }
return "OK"
}
@@ -0,0 +1,31 @@
// WITH_RUNTIME
val order = StringBuilder()
inline fun expectOrder(at: String, expected: String, body: () -> Unit) {
order.setLength(0)
body()
if (order.toString() != expected) throw AssertionError("$at: expected: $expected, actual: $order")
}
fun low(i: Int): Int {
order.append("L")
return i
}
fun high(i: Int): Int {
order.append("H")
return i
}
fun x(i: Int): Int {
order.append("X")
return i
}
fun box(): String {
expectOrder("0 in 1 .. 3", "LHX") { x(0) in low(1) .. high(3) }
expectOrder("0 !in 1 .. 3", "LHX") { x(0) !in low(1) .. high(3) }
return "OK"
}
@@ -13177,6 +13177,30 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/contains"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("evaluationOrderForCollection.kt")
public void testEvaluationOrderForCollection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForCollection.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForComparableRange.kt")
public void testEvaluationOrderForComparableRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForComparableRange.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForDownTo.kt")
public void testEvaluationOrderForDownTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForDownTo.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForRangeLiteral.kt")
public void testEvaluationOrderForRangeLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForRangeLiteral.kt");
doTest(fileName);
}
@TestMetadata("inArray.kt")
public void testInArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inArray.kt");
@@ -13177,6 +13177,30 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/contains"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("evaluationOrderForCollection.kt")
public void testEvaluationOrderForCollection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForCollection.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForComparableRange.kt")
public void testEvaluationOrderForComparableRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForComparableRange.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForDownTo.kt")
public void testEvaluationOrderForDownTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForDownTo.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForRangeLiteral.kt")
public void testEvaluationOrderForRangeLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForRangeLiteral.kt");
doTest(fileName);
}
@TestMetadata("inArray.kt")
public void testInArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inArray.kt");
@@ -13177,6 +13177,30 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/contains"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("evaluationOrderForCollection.kt")
public void testEvaluationOrderForCollection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForCollection.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForComparableRange.kt")
public void testEvaluationOrderForComparableRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForComparableRange.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForDownTo.kt")
public void testEvaluationOrderForDownTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForDownTo.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForRangeLiteral.kt")
public void testEvaluationOrderForRangeLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForRangeLiteral.kt");
doTest(fileName);
}
@TestMetadata("inArray.kt")
public void testInArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inArray.kt");
@@ -14785,6 +14785,30 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/contains"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("evaluationOrderForCollection.kt")
public void testEvaluationOrderForCollection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForCollection.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForComparableRange.kt")
public void testEvaluationOrderForComparableRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForComparableRange.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForDownTo.kt")
public void testEvaluationOrderForDownTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForDownTo.kt");
doTest(fileName);
}
@TestMetadata("evaluationOrderForRangeLiteral.kt")
public void testEvaluationOrderForRangeLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/evaluationOrderForRangeLiteral.kt");
doTest(fileName);
}
@TestMetadata("inArray.kt")
public void testInArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inArray.kt");