From dc57d69085e53d1c74fb152d9b4d5328b164a59e Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Sat, 1 Oct 2016 03:51:59 +0300 Subject: [PATCH] Improve onEach templates. Add new functions to reference API. Add tests for onEach #KT-8220 --- .../stdlib/src/generated/_Collections.kt | 8 ++++ libraries/stdlib/src/generated/_Maps.kt | 8 ++++ libraries/stdlib/src/generated/_Sequences.kt | 11 ++++++ libraries/stdlib/src/generated/_Strings.kt | 8 ++++ .../stdlib/test/collections/IterableTests.kt | 11 ++++++ libraries/stdlib/test/collections/MapTest.kt | 13 +++++++ .../stdlib/test/collections/SequenceTest.kt | 16 ++++++++ libraries/stdlib/test/text/StringTest.kt | 12 ++++++ .../reference-public-api/kotlin-stdlib.txt | 4 ++ .../src/templates/Aggregates.kt | 38 ++++++++++++------- 10 files changed, 115 insertions(+), 14 deletions(-) diff --git a/libraries/stdlib/src/generated/_Collections.kt b/libraries/stdlib/src/generated/_Collections.kt index 692554f2fed..a9ade75da3e 100644 --- a/libraries/stdlib/src/generated/_Collections.kt +++ b/libraries/stdlib/src/generated/_Collections.kt @@ -1586,6 +1586,14 @@ public inline fun Iterable.none(predicate: (T) -> Boolean): Boolean { return true } +/** + * Performs the given [action] on each element and returns the collection itself afterwards. + */ +@SinceKotlin("1.1") +public inline fun > C.onEach(action: (T) -> Unit): C { + return apply { for (element in this) action(element) } +} + /** * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. */ diff --git a/libraries/stdlib/src/generated/_Maps.kt b/libraries/stdlib/src/generated/_Maps.kt index 62a462c6c7f..96e2cb44159 100644 --- a/libraries/stdlib/src/generated/_Maps.kt +++ b/libraries/stdlib/src/generated/_Maps.kt @@ -178,6 +178,14 @@ public inline fun Map.none(predicate: (Map.Entry) -> Bool return true } +/** + * Performs the given [action] on each entry and returns the map itself afterwards. + */ +@SinceKotlin("1.1") +public inline fun > M.onEach(action: (Map.Entry) -> Unit): M { + return apply { for (element in this) action(element) } +} + /** * Creates an [Iterable] instance that wraps the original map returning its entries when being iterated. */ diff --git a/libraries/stdlib/src/generated/_Sequences.kt b/libraries/stdlib/src/generated/_Sequences.kt index a6148c2e877..2f6d211c9ad 100644 --- a/libraries/stdlib/src/generated/_Sequences.kt +++ b/libraries/stdlib/src/generated/_Sequences.kt @@ -969,6 +969,17 @@ public inline fun Sequence.none(predicate: (T) -> Boolean): Boolean { return true } +/** + * Returns a sequence which performs the given [action] on each element of the original sequence as they pass though it. + */ +@SinceKotlin("1.1") +public fun Sequence.onEach(action: (T) -> Unit): Sequence { + return map { + action(it) + it + } +} + /** * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. */ diff --git a/libraries/stdlib/src/generated/_Strings.kt b/libraries/stdlib/src/generated/_Strings.kt index 485f3611e09..82eae0a206d 100644 --- a/libraries/stdlib/src/generated/_Strings.kt +++ b/libraries/stdlib/src/generated/_Strings.kt @@ -1010,6 +1010,14 @@ public inline fun CharSequence.none(predicate: (Char) -> Boolean): Boolean { return true } +/** + * Performs the given [action] on each character and returns the char sequence itself afterwards. + */ +@SinceKotlin("1.1") +public inline fun S.onEach(action: (Char) -> Unit): S { + return apply { for (element in this) action(element) } +} + /** * Accumulates value starting with the first character and applying [operation] from left to right to current accumulator value and each character. */ diff --git a/libraries/stdlib/test/collections/IterableTests.kt b/libraries/stdlib/test/collections/IterableTests.kt index 9ac76ea86bd..729ed8358af 100644 --- a/libraries/stdlib/test/collections/IterableTests.kt +++ b/libraries/stdlib/test/collections/IterableTests.kt @@ -192,6 +192,17 @@ abstract class IterableTests>(val data: T, val empty: T) { assertEquals(6, count) } + @Test + fun onEach() { + var count = 0 + val newData = data.onEach { count += it.length } + assertEquals(6, count) + assertTrue(data === newData) + + // static types test + val list: ArrayList = arrayListOf(1, 2, 3).onEach { } + } + @Test fun contains() { assertTrue(data.contains("foo")) diff --git a/libraries/stdlib/test/collections/MapTest.kt b/libraries/stdlib/test/collections/MapTest.kt index 82af38456cc..a6126e1245b 100644 --- a/libraries/stdlib/test/collections/MapTest.kt +++ b/libraries/stdlib/test/collections/MapTest.kt @@ -98,6 +98,19 @@ class MapTest { assertEquals("beverage,beer,location,Mells,name,James", list.joinToString(",")) } + + @Test + fun onEach() { + val map = mutableMapOf("beverage" to "beer", "location" to "Mells") + val result = StringBuilder() + val newMap = map.onEach { result.append(it.key).append("=").append(it.value).append(";") } + assertEquals("beverage=beer;location=Mells;", result.toString()) + assertTrue(map === newMap) + + // static types test + val m: HashMap = hashMapOf("a" to "b").onEach { } + } + @Test fun stream() { val map = mapOf("beverage" to "beer", "location" to "Mells", "name" to "James") val named = map.asSequence().filter { it.key == "name" }.single() diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index e3fc5f12c06..e838c19fb0c 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -87,6 +87,22 @@ public class SequenceTest { assertEquals(listOf("f", "ba"), indexed) } + @Test + fun onEach() { + var count = 0 + val data = sequenceOf("foo", "bar") + val newData = data.onEach { count += it.length } + assertFalse(data === newData) + assertEquals(0, count, "onEach should be executed lazily") + + data.forEach { } + assertEquals(0, count, "onEach should be executed only when resulting sequence is iterated") + + val sum = newData.sumBy { it.length } + assertEquals(sum, count) + } + + @Test fun filterAndTakeWhileExtractTheElementsWithinRange() { assertEquals(listOf(144, 233, 377, 610, 987), fibonacci().filter { it > 100 }.takeWhile { it < 1000 }.toList()) } diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 03b2cf3b048..4d09e2b385f 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -753,6 +753,18 @@ class StringTest { assertEquals(data.toString(), sb.toString()) } + @Test + fun onEach() = withOneCharSequenceArg("abcd") { data -> + val result = StringBuilder() + val newData = data.onEach { result.append(it + 1) } + assertEquals("bcde", result.toString()) + assertTrue(data === newData) + + // static types test + val s: String = "x".onEach { } + val sb: StringBuilder = result.onEach { } + } + @Test fun filter() { assertEquals("acdca", ("abcdcba").filter { !it.equals('b') }) diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt index 2fc7456f751..8577d3194dd 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt @@ -1512,6 +1512,7 @@ public final class kotlin/collections/CollectionsKt { public static final fun mutableListOf ([Ljava/lang/Object;)Ljava/util/List; public static final fun none (Ljava/lang/Iterable;)Z public static final fun none (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Z + public static final fun onEach (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Iterable; public static final fun partition (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; public static final fun plus (Ljava/lang/Iterable;Ljava/lang/Iterable;)Ljava/util/List; public static final fun plus (Ljava/lang/Iterable;Ljava/lang/Object;)Ljava/util/List; @@ -1647,6 +1648,7 @@ public final class kotlin/collections/MapsKt { public static final fun mutableMapOf ([Lkotlin/Pair;)Ljava/util/Map; public static final fun none (Ljava/util/Map;)Z public static final fun none (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Z + public static final fun onEach (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun plus (Ljava/util/Map;Ljava/lang/Iterable;)Ljava/util/Map; public static final fun plus (Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map; public static final fun plus (Ljava/util/Map;Lkotlin/Pair;)Ljava/util/Map; @@ -2056,6 +2058,7 @@ public final class kotlin/sequences/SequencesKt { public static final fun minus (Lkotlin/sequences/Sequence;[Ljava/lang/Object;)Lkotlin/sequences/Sequence; public static final fun none (Lkotlin/sequences/Sequence;)Z public static final fun none (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Z + public static final fun onEach (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; public static final fun partition (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; public static final fun plus (Lkotlin/sequences/Sequence;Ljava/lang/Iterable;)Lkotlin/sequences/Sequence; public static final fun plus (Lkotlin/sequences/Sequence;Ljava/lang/Object;)Lkotlin/sequences/Sequence; @@ -2404,6 +2407,7 @@ public final class kotlin/text/StringsKt { public static final fun minWith (Ljava/lang/CharSequence;Ljava/util/Comparator;)Ljava/lang/Character; public static final fun none (Ljava/lang/CharSequence;)Z public static final fun none (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Z + public static final fun onEach (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Ljava/lang/CharSequence; public static final fun padEnd (Ljava/lang/CharSequence;IC)Ljava/lang/CharSequence; public static final fun padEnd (Ljava/lang/String;IC)Ljava/lang/String; public static synthetic fun padEnd$default (Ljava/lang/CharSequence;ICILjava/lang/Object;)Ljava/lang/CharSequence; diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt index dd53c6ae285..c0f8809cc55 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt @@ -719,27 +719,37 @@ fun aggregates(): List { } } - templates add f("onEach(action: (T) -> Unit)") { - inline(true) - doc { f -> "Performs the given [action] on each ${f.element} and returns the receiver afterwards." } - returns("SELF") + templates addAll listOf(Iterables, Maps, CharSequences).map { f -> f("onEach(action: (T) -> Unit)") { + only(f) + since("1.1") + inline(true) + doc { f -> "Performs the given [action] on each ${f.element} and returns the ${f.collection} itself afterwards." } + val collectionType = when(f) { + Maps -> "M" + CharSequences -> "S" + else -> "C" + } + customReceiver(collectionType) + returns(collectionType) + typeParam("$collectionType : SELF") + body { """ return apply { for (element in this) action(element) } """ } - include(Maps, CharSequences) + }} - exclude(Iterables) - typeParam(Generic) { "I: Iterable" } - typeParam(Generic) { "E" } - customReceiver(Generic) { "I" } - returns(Generic) { "I" } - - doc(Sequences) { f -> "Applies the supplied [action] on each ${f.element} of the Sequence as they pass through it." } - inline(false, Sequences) - returns(Sequences) { "Sequence" } + templates add f("onEach(action: (T) -> Unit)") { + only(Sequences) + since("1.1") + returns("SELF") + doc { f -> + """ + Returns a sequence which performs the given [action] on each ${f.element} of the original sequence as they pass though it. + """ + } body(Sequences) { """ return map {