diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index febfbc47b59..4be5d9a4335 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -656,6 +656,11 @@ public class ExpressionCodegen extends KtVisitor impleme assert extensionReceiver != null : "Extension receiver should be non-null for optimizable 'Collection.indices' call"; return new ForInCollectionIndicesRangeLoopGenerator(forExpression, extensionReceiver); } + else if (RangeCodegenUtil.isCharSequenceIndices(loopRangeCallee)) { + ReceiverValue extensionReceiver = loopRangeCall.getExtensionReceiver(); + assert extensionReceiver != null : "Extension receiver should be non-null for optimizable 'CharSequence.indices' call"; + return new ForInCharSequenceIndicesRangeLoopGenerator(forExpression, extensionReceiver); + } return null; } @@ -1198,7 +1203,7 @@ public class ExpressionCodegen extends KtVisitor impleme StackValue receiver = generateReceiverValue(receiverValue, false); Type receiverType = asmType(receiverValue.getType()); - receiver.put(receiverType, v); // NB receiverType is a collection or an array + receiver.put(receiverType, v); getReceiverSizeAsInt(); v.iconst(1); v.sub(Type.INT_TYPE); @@ -1233,6 +1238,17 @@ public class ExpressionCodegen extends KtVisitor impleme } } + private class ForInCharSequenceIndicesRangeLoopGenerator extends ForInOptimizedIndicesLoopGenerator { + private ForInCharSequenceIndicesRangeLoopGenerator(@NotNull KtForExpression forExpression, @NotNull ReceiverValue receiverValue) { + super(forExpression, receiverValue); + } + + @Override + protected void getReceiverSizeAsInt() { + v.invokeinterface("java/lang/CharSequence", "length", "()I"); + } + } + private class ForInProgressionExpressionLoopGenerator extends AbstractForInProgressionOrRangeLoopGenerator { private int incrementVar; private Type incrementType; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.java index 53125e529b9..4af6b89e866 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.java @@ -165,6 +165,17 @@ public class RangeCodegenUtil { return true; } + public static boolean isCharSequenceIndices(@NotNull CallableDescriptor descriptor) { + if (!isTopLevelInPackage(descriptor, "indices", "kotlin.text")) return false; + + ReceiverParameterDescriptor extensionReceiver = descriptor.getExtensionReceiverParameter(); + if (extensionReceiver == null) return false; + KotlinType extensionReceiverType = extensionReceiver.getType(); + if (!KotlinBuiltIns.isCharSequenceOrNullableCharSequence(extensionReceiverType)) return false; + + return true; + } + private static boolean isTopLevelInPackage(@NotNull CallableDescriptor descriptor, @NotNull String name, @NotNull String packageName) { if (!name.equals(descriptor.getName().asString())) return false; diff --git a/compiler/testData/codegen/box/ranges/forInIndices/forInCharSequenceIndices.kt b/compiler/testData/codegen/box/ranges/forInIndices/forInCharSequenceIndices.kt new file mode 100644 index 00000000000..1600771fa32 --- /dev/null +++ b/compiler/testData/codegen/box/ranges/forInIndices/forInCharSequenceIndices.kt @@ -0,0 +1,16 @@ +// WITH_RUNTIME + +fun test(s: CharSequence): Int { + var result = 0 + for (i in s.indices) { + result = result * 10 + (i + 1) + } + return result +} + +fun box(): String { + val test = test("abcd") + if (test != 1234) return "Fail: $test" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/forLoop/forInIndices/forInCharSequenceIndices.kt b/compiler/testData/codegen/bytecodeText/forLoop/forInIndices/forInCharSequenceIndices.kt new file mode 100644 index 00000000000..82f6bec9ab2 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/forLoop/forInIndices/forInCharSequenceIndices.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME + +fun test(s: CharSequence): Int { + var result = 0 + for (i in s.indices) { + result = result * 10 + (i + 1) + } + return result +} + +// 0 iterator +// 0 getStart +// 0 getEnd +// 0 getFirst +// 0 getLast \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index aeb8d502a1d..3a789cf965a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -10884,6 +10884,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/forInIndices"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("forInCharSequenceIndices.kt") + public void testForInCharSequenceIndices() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInIndices/forInCharSequenceIndices.kt"); + doTest(fileName); + } + @TestMetadata("forInCollectionImplicitReceiverIndices.kt") public void testForInCollectionImplicitReceiverIndices() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInIndices/forInCollectionImplicitReceiverIndices.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index e3945a589c0..37c381a4ddb 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -909,6 +909,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/forLoop/forInIndices"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("forInCharSequenceIndices.kt") + public void testForInCharSequenceIndices() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/forLoop/forInIndices/forInCharSequenceIndices.kt"); + doTest(fileName); + } + @TestMetadata("forInCollectionImplicitReceiverIndices.kt") public void testForInCollectionImplicitReceiverIndices() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/forLoop/forInIndices/forInCollectionImplicitReceiverIndices.kt");