diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RangeTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RangeTest.java index e85c9e78681..78ac985b643 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RangeTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RangeTest.java @@ -24,6 +24,10 @@ public final class RangeTest extends SingleFileTranslationTest { super("range/"); } + public void testCreatingProgressions() throws Exception { + fooBoxTest(); + } + public void testExplicitRange() throws Exception { fooBoxTest(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StandardClasses.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StandardClasses.java index 8729ed4561e..18636293ffd 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StandardClasses.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StandardClasses.java @@ -98,25 +98,15 @@ public final class StandardClasses { if (type == PrimitiveType.CHAR || type == PrimitiveType.LONG) continue; String typeName = type.getTypeName().asString(); - standardClasses.declare().forFQ("kotlin." + typeName + "Range").kotlinClass("NumberRange") - .methods("iterator", "contains").properties("start", "end", "increment"); - - standardClasses.declare().forFQ("kotlin." + typeName + "Progression").kotlinClass("NumberProgression") - .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin." + typeName + "Range").kotlinClass("NumberRange"); + standardClasses.declare().forFQ("kotlin." + typeName + "Progression").kotlinClass("NumberProgression"); } - standardClasses.declare().forFQ("kotlin.LongRange").kotlinClass("LongRange") - .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin.LongRange").kotlinClass("LongRange"); + standardClasses.declare().forFQ("kotlin.CharRange").kotlinClass("CharRange"); - standardClasses.declare().forFQ("kotlin.CharRange").kotlinClass("CharRange") - .methods("iterator", "contains").properties("start", "end", "increment"); - - - standardClasses.declare().forFQ("kotlin.LongProgression").kotlinClass("LongProgression") - .methods("iterator", "contains").properties("start", "end", "increment"); - - standardClasses.declare().forFQ("kotlin.CharProgression").kotlinClass("CharProgression") - .methods("iterator", "contains").properties("start", "end", "increment"); + standardClasses.declare().forFQ("kotlin.LongProgression").kotlinClass("LongProgression"); + standardClasses.declare().forFQ("kotlin.CharProgression").kotlinClass("CharProgression"); standardClasses.declare().forFQ("kotlin.Enum").kotlinClass("Enum"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java index ded30f23523..3869906c93e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/FunctionIntrinsics.java @@ -47,6 +47,7 @@ public final class FunctionIntrinsics { register(ArrayFIF.INSTANCE); register(TopLevelFIF.INSTANCE); register(NumberAndCharConversionFIF.INSTANCE$); + register(ProgressionCompanionFIF.INSTANCE); } private void register(@NotNull FunctionIntrinsicFactory instance) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/ProgressionCompanionFIF.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/ProgressionCompanionFIF.kt new file mode 100644 index 00000000000..e3ad0fa5dfe --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/ProgressionCompanionFIF.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2015 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.js.translate.intrinsic.functions.factories + +import com.google.dart.compiler.backend.js.ast.* +import org.jetbrains.kotlin.builtins.PrimitiveType +import org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern +import org.jetbrains.kotlin.js.translate.context.Namer +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic + + +public object ProgressionCompanionFIF : CompositeFIF() { + init { + val numberProgressionConstructor = CallProgressionConstructorIntrinsic("NumberProgression") + for (type in arrayOf(PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.INT)) { + add(methodPattern("${type.typeName}Progression"), numberProgressionConstructor) + } + add(methodPattern("LongProgression"), CallProgressionConstructorIntrinsic("LongProgression")) + add(methodPattern("CharProgression"), CallProgressionConstructorIntrinsic("CharProgression")) + } + + private fun methodPattern(builtinProgressionName: String) = pattern("kotlin", builtinProgressionName, "Companion", "fromClosedRange") + + private class CallProgressionConstructorIntrinsic(val libraryProgressionName: String) : FunctionIntrinsic() { + override fun apply(receiver: JsExpression?, arguments: MutableList, context: TranslationContext): JsExpression + = JsNew(JsNameRef(libraryProgressionName, Namer.KOTLIN_OBJECT_REF), arguments) + } + +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/for/cases/forIteratesOverLiteralRange.kt b/js/js.translator/testData/expression/for/cases/forIteratesOverLiteralRange.kt index 3f7a72c7739..7a78891deea 100644 --- a/js/js.translator/testData/expression/for/cases/forIteratesOverLiteralRange.kt +++ b/js/js.translator/testData/expression/for/cases/forIteratesOverLiteralRange.kt @@ -20,14 +20,6 @@ fun box(): String { n++ assertEquals(4, n) - // http://youtrack.jetbrains.com/issue/KT-4381 - // JS: fails to iterate over Double range - var nd = 0.0 - for (i in 0.0 .. 10.0) { - nd += i - } - assertEquals(55.0, nd) - // Evaluation order for (i in up("A", 0)..up("B", 5)) { } @@ -53,10 +45,6 @@ fun box(): String { sLong += i assertEquals(55L, sLong) - var sDouble = 0.0 - for(i in 0L..10.0) - sDouble += i - assertEquals(55.0, sDouble) return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/expression/for/cases/forIteratesOverNonLiteralRange.kt b/js/js.translator/testData/expression/for/cases/forIteratesOverNonLiteralRange.kt index ab5c5a25ed5..07a3e26545e 100644 --- a/js/js.translator/testData/expression/for/cases/forIteratesOverNonLiteralRange.kt +++ b/js/js.translator/testData/expression/for/cases/forIteratesOverNonLiteralRange.kt @@ -18,11 +18,5 @@ fun box(): String { sLong += i assertEquals(55L, sLong) - var sDouble = 0.0 - var rDouble = 0L..10.0 - for(i in rDouble) - sDouble += i - assertEquals(55.0, sDouble) - return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index c67ef286552..54bb5e917a1 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -790,53 +790,128 @@ var classObject = this.constructor; if (this instanceof classObject && other instanceof classObject) { return this.isEmpty() && other.isEmpty() || - (this.start === other.start && this.end === other.end && this.increment === other.increment); + (this.first === other.first && this.last === other.last && this.increment === other.increment); } return false; } - Kotlin.NumberRange = Kotlin.createClassNow(null, - function (start, end) { + function isSameLongRanges(other) { + var classObject = this.constructor; + if (this instanceof classObject && other instanceof classObject) { + return this.isEmpty() && other.isEmpty() || + (this.first.equals_za3rmp$(other.first) && this.last.equals_za3rmp$(other.last) && this.increment.equals_za3rmp$(other.increment)); + } + return false; + } + + // reference implementation in core/builtins/src/kotlin/internal/progressionUtil.kt + function getProgressionFinalElement(start, end, increment) { + function mod(a, b) { + var mod = a % b; + return mod >= 0 ? mod : mod + b; + } + function differenceModulo(a, b, c) { + return mod(mod(a, c) - mod(b, c), c); + } + + if (increment > 0) { + return end - differenceModulo(end, start, increment); + } + else if (increment < 0) { + return end + differenceModulo(start, end, -increment); + } + else { + throw new Kotlin.IllegalArgumentException('Increment is zero.'); + } + } + + // reference implementation in core/builtins/src/kotlin/internal/progressionUtil.kt + function getProgressionFinalElementLong(start, end, increment) { + function mod(a, b) { + var mod = a.modulo(b); + return !mod.isNegative() ? mod : mod.add(b); + } + function differenceModulo(a, b, c) { + return mod(mod(a, c).subtract(mod(b, c)), c); + } + + var diff; + if (increment.compareTo_za3rmp$(Kotlin.Long.ZERO) > 0) { + diff = differenceModulo(end, start, increment); + return diff.isZero() ? end : end.subtract(diff); + } + else if (increment.compareTo_za3rmp$(Kotlin.Long.ZERO) < 0) { + diff = differenceModulo(start, end, increment.unaryMinus()); + return diff.isZero() ? end : end.add(diff); + } + else { + throw new Kotlin.IllegalArgumentException('Increment is zero.'); + } + } + + lazyInitClasses.NumberProgression = Kotlin.createClass( + function () { + return [Kotlin.modules['builtins'].kotlin.Iterable]; + }, + function (start, end, increment) { this.start = start; - this.end = end; - this.increment = 1; + this.endInclusive = end; + this.first = start; + this.last = getProgressionFinalElement(start, end, increment); + this.increment = increment; + if (this.increment === 0) + throw new Kotlin.IllegalArgumentException('Increment must be non-zero'); }, { - contains: function (number) { - return this.start <= number && number <= this.end; - }, + end: { get: function () { + return this.endInclusive; + }}, iterator: function () { - return new Kotlin.RangeIterator(this.start, this.end, this.increment); + return new Kotlin.RangeIterator(this.first, this.last, this.increment); }, isEmpty: function () { - return this.start > this.end; + return this.increment > 0 ? this.first > this.last : this.first < this.last; }, - hashCode: function() { - return this.isEmpty() ? -1 : (31 * this.start|0 + this.end|0); + hashCode: function () { + return this.isEmpty() ? -1 : 31 * (31 * this.first + this.last) + this.increment; }, - equals_za3rmp$: isSameNotNullRanges + equals_za3rmp$: isSameNotNullRanges, + toString: function () { + return this.increment > 0 ? this.first.toString() + '..' + this.last + ' step ' + this.increment : this.first.toString() + ' downTo ' + this.last + ' step ' + -this.increment; + } + }); + + lazyInitClasses.NumberRange = Kotlin.createClass( + function() { + return [Kotlin.modules['builtins'].kotlin.ClosedRange, Kotlin.NumberProgression] + }, + function $fun(start, endInclusive) { + $fun.baseInitializer.call(this, start, endInclusive, 1); + this.start = start; + this.endInclusive = endInclusive; + }, { + end: { get: function () { + return this.endInclusive; + }}, + contains_htax2k$: function (item) { + return this.start <= item && item <= this.endInclusive; + }, + isEmpty: function () { + return this.start > this.endInclusive; + }, + hashCode: function () { + return this.isEmpty() ? -1 : 31 * this.start + this.endInclusive; + }, + equals_za3rmp$: isSameNotNullRanges, + toString: function () { + return this.start.toString() + '..' + this.endInclusive; + } }, { object_initializer$: function () { return { EMPTY : new this(1, 0) }; } }); - Kotlin.NumberProgression = Kotlin.createClassNow(null, - function (start, end, increment) { - this.start = start; - this.end = end; - this.increment = increment; - }, { - iterator: function () { - return new Kotlin.RangeIterator(this.start, this.end, this.increment); - }, - isEmpty: function() { - return this.increment > 0 ? this.start > this.end : this.start < this.end; - }, - hashCode: function() { - return this.isEmpty() ? -1 : (31 * (31 * this.start|0 + this.end|0) + this.increment|0); - }, - equals_za3rmp$: isSameNotNullRanges - }); + lazyInitClasses.LongRangeIterator = Kotlin.createClass( function () { @@ -861,48 +936,69 @@ } }); - Kotlin.LongRange = Kotlin.createClassNow(null, - function (start, end) { - this.start = start; - this.end = end; - this.increment = Kotlin.Long.ONE; - }, { - contains: function (number) { - return this.start.compare(number) <= 0 && number.compare(this.end) <= 0; - }, - iterator: function () { - return new Kotlin.LongRangeIterator(this.start, this.end, this.increment); - }, - isEmpty: function () { - return this.start.compare(this.end) > 0; - }, - hashCode: function() { - return this.isEmpty() ? -1 : (31 * this.start.toInt() + this.end.toInt()); + lazyInitClasses.LongProgression = Kotlin.createClass( + function () { + return [Kotlin.modules['builtins'].kotlin.Iterable]; + }, + function (start, end, increment) { + this.start = start; + this.endInclusive = end; + this.first = start; + this.last = getProgressionFinalElementLong(start, end, increment); + this.increment = increment; + if (this.increment.isZero()) + throw new Kotlin.IllegalArgumentException('Increment must be non-zero'); + }, { + end: { get: function () { + return this.endInclusive; + }}, + iterator: function () { + return new Kotlin.LongRangeIterator(this.first, this.last, this.increment); }, - equals_za3rmp$: isSameNotNullRanges + isEmpty: function() { + return this.increment.isNegative() ? this.first.compare(this.last) < 0 : this.first.compare(this.last) > 0; + }, + hashCode: function() { + return this.isEmpty() ? -1 : (31 * (31 * this.first.toInt() + this.last.toInt()) + this.increment.toInt()); + }, + equals_za3rmp$: isSameLongRanges, + toString: function () { + return !this.increment.isNegative() ? this.first.toString() + '..' + this.last + ' step ' + this.increment : this.first.toString() + ' downTo ' + this.last + ' step ' + this.increment.unaryMinus(); + } + }); + + lazyInitClasses.LongRange = Kotlin.createClass( + function () { + return [Kotlin.modules['builtins'].kotlin.ClosedRange, Kotlin.LongProgression]; + }, + function $fun(start, endInclusive) { + $fun.baseInitializer.call(this, start, endInclusive, Kotlin.Long.ONE); + this.start = start; + this.endInclusive = endInclusive; + }, { + end: { get: function () { + return this.endInclusive; + }}, + contains_htax2k$: function (item) { + return this.start.compareTo_za3rmp$(item) <= 0 && item.compareTo_za3rmp$(this.endInclusive) <= 0; + }, + isEmpty: function () { + return this.start.compare(this.endInclusive) > 0; + }, + hashCode: function() { + return this.isEmpty() ? -1 : (31 * this.start.toInt() + this.endInclusive.toInt()); + }, + equals_za3rmp$: isSameLongRanges, + toString: function () { + return this.start.toString() + '..' + this.endInclusive; + } }, { object_initializer$: function () { return { EMPTY : new this(Kotlin.Long.ONE, Kotlin.Long.ZERO) }; } }); - Kotlin.LongProgression = Kotlin.createClassNow(null, - function (start, end, increment) { - this.start = start; - this.end = end; - this.increment = increment; - }, { - iterator: function () { - return new Kotlin.LongRangeIterator(this.start, this.end, this.increment); - }, - isEmpty: function() { - return this.increment.isNegative() ? this.start.compare(this.end) < 0 : this.start.compare(this.end) > 0; - }, - hashCode: function() { - return this.isEmpty() ? -1 : (31 * (31 * this.start.toInt() + this.end.toInt()) + this.increment.toInt()); - }, - equals_za3rmp$: isSameNotNullRanges - }); + lazyInitClasses.CharRangeIterator = Kotlin.createClass( function () { @@ -918,52 +1014,71 @@ } }); - Kotlin.CharRange = Kotlin.createClassNow(null, - function (start, end) { + lazyInitClasses.CharProgression = Kotlin.createClassNow( + function () { + return [Kotlin.modules['builtins'].kotlin.Iterable]; + }, + function (start, end, increment) { this.start = start; + this.endInclusive = end; + this.first = start; this.startCode = start.charCodeAt(0); - this.end = end; - this.endCode = end.charCodeAt(0); - this.increment = 1; + this.endCode = getProgressionFinalElement(this.startCode, end.charCodeAt(0), increment); + this.last = String.fromCharCode(this.endCode); + this.increment = increment; + if (this.increment === 0) + throw new Kotlin.IllegalArgumentException('Increment must be non-zero'); }, { - contains: function (char) { - return this.start <= char && char <= this.end; - }, + end: { get: function () { + return this.endInclusive; + }}, iterator: function () { return new Kotlin.CharRangeIterator(this.startCode, this.endCode, this.increment); }, + isEmpty: function() { + return this.increment > 0 ? this.startCode > this.endCode : this.startCode < this.endCode; + }, + hashCode: function() { + return this.isEmpty() ? -1 : (31 * (31 * this.startCode|0 + this.endCode|0) + this.increment|0); + }, + equals_za3rmp$: isSameNotNullRanges, + toString: function () { + return this.increment > 0 ? this.first.toString() + '..' + this.last + ' step ' + this.increment : this.first.toString() + ' downTo ' + this.last + ' step ' + -this.increment; + } + }); + + + lazyInitClasses.CharRange = Kotlin.createClass( + function() { + return [Kotlin.modules['builtins'].kotlin.ClosedRange, Kotlin.CharProgression] + }, + function $fun(start, endInclusive) { + $fun.baseInitializer.call(this, start, endInclusive, 1); + this.start = start; + this.endInclusive = endInclusive; + }, { + end: { get: function () { + return this.endInclusive; + }}, + contains_htax2k$: function (item) { + return this.start <= item && item <= this.endInclusive; + }, isEmpty: function () { - return this.start > this.end; + return this.start > this.endInclusive; }, hashCode: function() { return this.isEmpty() ? -1 : (31 * this.startCode|0 + this.endCode|0); }, - equals_za3rmp$: isSameNotNullRanges + equals_za3rmp$: isSameNotNullRanges, + toString: function () { + return this.start.toString() + '..' + this.endInclusive; + } }, { object_initializer$: function () { return { EMPTY : new this(Kotlin.toChar(1), Kotlin.toChar(0)) }; } }); - Kotlin.CharProgression = Kotlin.createClassNow(null, - function (start, end, increment) { - this.start = start; - this.startCode = start.charCodeAt(0); - this.end = end; - this.endCode = end.charCodeAt(0); - this.increment = increment; - }, { - iterator: function () { - return new Kotlin.CharRangeIterator(this.startCode, this.endCode, this.increment); - }, - isEmpty: function() { - return this.increment > 0 ? this.start > this.end : this.start < this.end; - }, - hashCode: function() { - return this.isEmpty() ? -1 : (31 * (31 * this.startCode|0 + this.endCode|0) + this.increment|0); - }, - equals_za3rmp$: isSameNotNullRanges - }); /** * @interface diff --git a/js/js.translator/testData/range/cases/creatingProgressions.kt b/js/js.translator/testData/range/cases/creatingProgressions.kt new file mode 100644 index 00000000000..f1e10c543a8 --- /dev/null +++ b/js/js.translator/testData/range/cases/creatingProgressions.kt @@ -0,0 +1,22 @@ +package foo + +fun box(): Boolean { + + val intProgression = IntProgression.fromClosedRange(0, 10, 2) + assertEquals(10, intProgression.last) + + val longProgression = LongProgression.fromClosedRange(0, 420004200042000L, 420004200042000L) + assertEquals(420004200042000L, longProgression.last) + + val charProgression = CharProgression.fromClosedRange('a', 'z', 2) + assertEquals('y', charProgression.last) + + // deprecated + val byteProgression = ByteProgression.fromClosedRange(1, 127, 2) + assertEquals(127.toByte(), byteProgression.last) + + val shortProgression = ShortProgression.fromClosedRange(1, 32767, 2) + assertEquals(32767.toShort(), shortProgression.last) + + return true +} diff --git a/libraries/stdlib/test/language/RangeTest.kt b/libraries/stdlib/test/language/RangeTest.kt index 12bb8bcc2ac..e45e0bebbf5 100644 --- a/libraries/stdlib/test/language/RangeTest.kt +++ b/libraries/stdlib/test/language/RangeTest.kt @@ -22,6 +22,9 @@ public class RangeTest { assertFalse(range.isEmpty()) + assertTrue(9 in (range as ClosedRange)) + assertFalse((range as ClosedRange).isEmpty()) + assertTrue(1.toShort() in range) assertTrue(1.toByte() in range) assertTrue(1.toLong() in range) @@ -122,6 +125,9 @@ public class RangeTest { assertFalse(range.isEmpty()) + assertTrue(9 in (range as ClosedRange)) + assertFalse((range as ClosedRange).isEmpty()) + assertTrue(1.toByte() in range) assertTrue(1.toShort() in range) assertTrue(1.toInt() in range) @@ -155,6 +161,9 @@ public class RangeTest { assertFalse(range.isEmpty()) + assertTrue('v' in (range as ClosedRange)) + assertFalse((range as ClosedRange).isEmpty()) + val openRange = 'A' until 'Z' assertTrue('Y' in openRange) assertFalse('Z' in openRange) @@ -239,6 +248,7 @@ public class RangeTest { assertTrue(IntRange.EMPTY == IntRange.EMPTY) assertEquals(IntRange.EMPTY, IntRange.EMPTY) assertEquals(0L..42L, 0L..42L) + assertEquals(0L..4200000042000000L, 0L..4200000042000000L) assertEquals(3 downTo 0, 3 downTo 0) assertEquals(2..1, 1..0)