From ff5b2404af5e51e4c3c89a63e5b7d183a570db4c Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Thu, 11 Mar 2021 18:08:26 +0300 Subject: [PATCH] Introduce firstNotNullOf and firstNotNullOfOrNull #KT-12109 --- .../stdlib/api/js-v1/kotlin.collections.kt | 24 +++++++++ .../stdlib/api/js-v1/kotlin.sequences.kt | 8 +++ libraries/stdlib/api/js-v1/kotlin.text.kt | 8 +++ libraries/stdlib/api/js/kotlin.collections.kt | 24 +++++++++ libraries/stdlib/api/js/kotlin.sequences.kt | 8 +++ libraries/stdlib/api/js/kotlin.text.kt | 8 +++ .../stdlib/common/src/generated/_Arrays.kt | 30 +++++++++++ .../common/src/generated/_Collections.kt | 30 +++++++++++ .../stdlib/common/src/generated/_Maps.kt | 30 +++++++++++ .../stdlib/common/src/generated/_Sequences.kt | 34 ++++++++++++ .../stdlib/common/src/generated/_Strings.kt | 30 +++++++++++ .../test/samples/collections/collections.kt | 26 ++++++++++ libraries/stdlib/test/collections/MapTest.kt | 16 ++++++ .../stdlib/test/collections/SequenceTest.kt | 14 +++++ .../src/templates/Elements.kt | 52 +++++++++++++++++++ 15 files changed, 342 insertions(+) diff --git a/libraries/stdlib/api/js-v1/kotlin.collections.kt b/libraries/stdlib/api/js-v1/kotlin.collections.kt index a8c35f86847..993b78346e9 100644 --- a/libraries/stdlib/api/js-v1/kotlin.collections.kt +++ b/libraries/stdlib/api/js-v1/kotlin.collections.kt @@ -2550,6 +2550,30 @@ public inline fun kotlin.collections.Iterable.first(predicate: (T) -> kot public fun kotlin.collections.List.first(): T +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.Array.firstNotNullOf(transform: (T) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Iterable.firstNotNullOf(transform: (T) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Map.firstNotNullOf(transform: (kotlin.collections.Map.Entry) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.Array.firstNotNullOfOrNull(transform: (T) -> R?): R? + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Iterable.firstNotNullOfOrNull(transform: (T) -> R?): R? + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Map.firstNotNullOfOrNull(transform: (kotlin.collections.Map.Entry) -> R?): R? + public fun kotlin.Array.firstOrNull(): T? public inline fun kotlin.Array.firstOrNull(predicate: (T) -> kotlin.Boolean): T? diff --git a/libraries/stdlib/api/js-v1/kotlin.sequences.kt b/libraries/stdlib/api/js-v1/kotlin.sequences.kt index 76bff5201f7..94b5cef412e 100644 --- a/libraries/stdlib/api/js-v1/kotlin.sequences.kt +++ b/libraries/stdlib/api/js-v1/kotlin.sequences.kt @@ -140,6 +140,14 @@ public fun kotlin.sequences.Sequence.first(): T public inline fun kotlin.sequences.Sequence.first(predicate: (T) -> kotlin.Boolean): T +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.sequences.Sequence.firstNotNullOf(transform: (T) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.sequences.Sequence.firstNotNullOfOrNull(transform: (T) -> R?): R? + public fun kotlin.sequences.Sequence.firstOrNull(): T? public inline fun kotlin.sequences.Sequence.firstOrNull(predicate: (T) -> kotlin.Boolean): T? diff --git a/libraries/stdlib/api/js-v1/kotlin.text.kt b/libraries/stdlib/api/js-v1/kotlin.text.kt index 0ad9e0793e6..9f91d38f483 100644 --- a/libraries/stdlib/api/js-v1/kotlin.text.kt +++ b/libraries/stdlib/api/js-v1/kotlin.text.kt @@ -290,6 +290,14 @@ public fun kotlin.CharSequence.first(): kotlin.Char public inline fun kotlin.CharSequence.first(predicate: (kotlin.Char) -> kotlin.Boolean): kotlin.Char +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.CharSequence.firstNotNullOf(transform: (kotlin.Char) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.CharSequence.firstNotNullOfOrNull(transform: (kotlin.Char) -> R?): R? + public fun kotlin.CharSequence.firstOrNull(): kotlin.Char? public inline fun kotlin.CharSequence.firstOrNull(predicate: (kotlin.Char) -> kotlin.Boolean): kotlin.Char? diff --git a/libraries/stdlib/api/js/kotlin.collections.kt b/libraries/stdlib/api/js/kotlin.collections.kt index a2e33caba22..473db302c12 100644 --- a/libraries/stdlib/api/js/kotlin.collections.kt +++ b/libraries/stdlib/api/js/kotlin.collections.kt @@ -2520,6 +2520,30 @@ public inline fun kotlin.collections.Iterable.first(predicate: (T) -> kot public fun kotlin.collections.List.first(): T +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.Array.firstNotNullOf(transform: (T) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Iterable.firstNotNullOf(transform: (T) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Map.firstNotNullOf(transform: (kotlin.collections.Map.Entry) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.Array.firstNotNullOfOrNull(transform: (T) -> R?): R? + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Iterable.firstNotNullOfOrNull(transform: (T) -> R?): R? + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.collections.Map.firstNotNullOfOrNull(transform: (kotlin.collections.Map.Entry) -> R?): R? + public fun kotlin.Array.firstOrNull(): T? public inline fun kotlin.Array.firstOrNull(predicate: (T) -> kotlin.Boolean): T? diff --git a/libraries/stdlib/api/js/kotlin.sequences.kt b/libraries/stdlib/api/js/kotlin.sequences.kt index 76bff5201f7..94b5cef412e 100644 --- a/libraries/stdlib/api/js/kotlin.sequences.kt +++ b/libraries/stdlib/api/js/kotlin.sequences.kt @@ -140,6 +140,14 @@ public fun kotlin.sequences.Sequence.first(): T public inline fun kotlin.sequences.Sequence.first(predicate: (T) -> kotlin.Boolean): T +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.sequences.Sequence.firstNotNullOf(transform: (T) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.sequences.Sequence.firstNotNullOfOrNull(transform: (T) -> R?): R? + public fun kotlin.sequences.Sequence.firstOrNull(): T? public inline fun kotlin.sequences.Sequence.firstOrNull(predicate: (T) -> kotlin.Boolean): T? diff --git a/libraries/stdlib/api/js/kotlin.text.kt b/libraries/stdlib/api/js/kotlin.text.kt index 0ad9e0793e6..9f91d38f483 100644 --- a/libraries/stdlib/api/js/kotlin.text.kt +++ b/libraries/stdlib/api/js/kotlin.text.kt @@ -290,6 +290,14 @@ public fun kotlin.CharSequence.first(): kotlin.Char public inline fun kotlin.CharSequence.first(predicate: (kotlin.Char) -> kotlin.Boolean): kotlin.Char +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.CharSequence.firstNotNullOf(transform: (kotlin.Char) -> R?): R + +@kotlin.SinceKotlin(version = "1.5") +@kotlin.internal.InlineOnly +public inline fun kotlin.CharSequence.firstNotNullOfOrNull(transform: (kotlin.Char) -> R?): R? + public fun kotlin.CharSequence.firstOrNull(): kotlin.Char? public inline fun kotlin.CharSequence.firstOrNull(predicate: (kotlin.Char) -> kotlin.Boolean): kotlin.Char? diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 12343bbf70f..81871ef410e 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -1175,6 +1175,36 @@ public inline fun CharArray.first(predicate: (Char) -> Boolean): Char { throw NoSuchElementException("Array contains no element matching the predicate.") } +/** + * Returns the first non-null value produced by [transform] function being applied to elements of this array in iteration order, + * or throws [NoSuchElementException] if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Array.firstNotNullOf(transform: (T) -> R?): R { + return firstNotNullOfOrNull(transform) ?: throw NoSuchElementException("No element of the array was transformed to a non-null value.") +} + +/** + * Returns the first non-null value produced by [transform] function being applied to elements of this array in iteration order, + * or `null` if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Array.firstNotNullOfOrNull(transform: (T) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null +} + /** * Returns the first element, or `null` if the array is empty. */ diff --git a/libraries/stdlib/common/src/generated/_Collections.kt b/libraries/stdlib/common/src/generated/_Collections.kt index 02c3bbf3c7b..f668b1ba4b4 100644 --- a/libraries/stdlib/common/src/generated/_Collections.kt +++ b/libraries/stdlib/common/src/generated/_Collections.kt @@ -222,6 +222,36 @@ public inline fun Iterable.first(predicate: (T) -> Boolean): T { throw NoSuchElementException("Collection contains no element matching the predicate.") } +/** + * Returns the first non-null value produced by [transform] function being applied to elements of this collection in iteration order, + * or throws [NoSuchElementException] if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Iterable.firstNotNullOf(transform: (T) -> R?): R { + return firstNotNullOfOrNull(transform) ?: throw NoSuchElementException("No element of the collection was transformed to a non-null value.") +} + +/** + * Returns the first non-null value produced by [transform] function being applied to elements of this collection in iteration order, + * or `null` if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Iterable.firstNotNullOfOrNull(transform: (T) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null +} + /** * Returns the first element, or `null` if the collection is empty. */ diff --git a/libraries/stdlib/common/src/generated/_Maps.kt b/libraries/stdlib/common/src/generated/_Maps.kt index c8fdf62bd7a..5aa0b731807 100644 --- a/libraries/stdlib/common/src/generated/_Maps.kt +++ b/libraries/stdlib/common/src/generated/_Maps.kt @@ -17,6 +17,36 @@ import kotlin.random.* import kotlin.ranges.contains import kotlin.ranges.reversed +/** + * Returns the first non-null value produced by [transform] function being applied to entries of this map in iteration order, + * or throws [NoSuchElementException] if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Map.firstNotNullOf(transform: (Map.Entry) -> R?): R { + return firstNotNullOfOrNull(transform) ?: throw NoSuchElementException("No element of the map was transformed to a non-null value.") +} + +/** + * Returns the first non-null value produced by [transform] function being applied to entries of this map in iteration order, + * or `null` if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Map.firstNotNullOfOrNull(transform: (Map.Entry) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null +} + /** * Returns a [List] containing all key-value pairs. */ diff --git a/libraries/stdlib/common/src/generated/_Sequences.kt b/libraries/stdlib/common/src/generated/_Sequences.kt index 5076ae68c56..7970cf65e7b 100644 --- a/libraries/stdlib/common/src/generated/_Sequences.kt +++ b/libraries/stdlib/common/src/generated/_Sequences.kt @@ -123,6 +123,40 @@ public inline fun Sequence.first(predicate: (T) -> Boolean): T { throw NoSuchElementException("Sequence contains no element matching the predicate.") } +/** + * Returns the first non-null value produced by [transform] function being applied to elements of this sequence in iteration order, + * or throws [NoSuchElementException] if no non-null value was produced. + * + * The operation is _terminal_. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Sequence.firstNotNullOf(transform: (T) -> R?): R { + return firstNotNullOfOrNull(transform) ?: throw NoSuchElementException("No element of the sequence was transformed to a non-null value.") +} + +/** + * Returns the first non-null value produced by [transform] function being applied to elements of this sequence in iteration order, + * or `null` if no non-null value was produced. + * + * The operation is _terminal_. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun Sequence.firstNotNullOfOrNull(transform: (T) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null +} + /** * Returns the first element, or `null` if the sequence is empty. * diff --git a/libraries/stdlib/common/src/generated/_Strings.kt b/libraries/stdlib/common/src/generated/_Strings.kt index 8df986d7804..f9cf1c7264e 100644 --- a/libraries/stdlib/common/src/generated/_Strings.kt +++ b/libraries/stdlib/common/src/generated/_Strings.kt @@ -81,6 +81,36 @@ public inline fun CharSequence.first(predicate: (Char) -> Boolean): Char { throw NoSuchElementException("Char sequence contains no character matching the predicate.") } +/** + * Returns the first non-null value produced by [transform] function being applied to characters of this char sequence in iteration order, + * or throws [NoSuchElementException] if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun CharSequence.firstNotNullOf(transform: (Char) -> R?): R { + return firstNotNullOfOrNull(transform) ?: throw NoSuchElementException("No element of the char sequence was transformed to a non-null value.") +} + +/** + * Returns the first non-null value produced by [transform] function being applied to characters of this char sequence in iteration order, + * or `null` if no non-null value was produced. + * + * @sample samples.collections.Collections.Transformations.firstNotNullOf + */ +@SinceKotlin("1.5") +@kotlin.internal.InlineOnly +public inline fun CharSequence.firstNotNullOfOrNull(transform: (Char) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null +} + /** * Returns the first character, or `null` if the char sequence is empty. */ diff --git a/libraries/stdlib/samples/test/samples/collections/collections.kt b/libraries/stdlib/samples/test/samples/collections/collections.kt index 509680a3027..97e78d1314b 100644 --- a/libraries/stdlib/samples/test/samples/collections/collections.kt +++ b/libraries/stdlib/samples/test/samples/collections/collections.kt @@ -597,6 +597,32 @@ class Collections { assertPrints(deltas, "[3, 5, 7, 9, 11]") } + + @Sample + fun firstNotNullOf() { + data class Rectangle(val height: Int, val width: Int) { + val area: Int get() = height * width + } + + val rectangles = listOf( + Rectangle(3, 4), + Rectangle(1, 8), + Rectangle(6, 3), + Rectangle(4, 3), + Rectangle(5, 7) + ) + + val largeArea = rectangles.firstNotNullOf { it.area.takeIf { area -> area >= 15 } } + val largeAreaOrNull = rectangles.firstNotNullOfOrNull { it.area.takeIf { area -> area >= 15 } } + + assertPrints(largeArea, "18") + assertPrints(largeAreaOrNull, "18") + + assertFailsWith { val evenLargerArea = rectangles.firstNotNullOf { it.area.takeIf { area -> area >= 50 } } } + val evenLargerAreaOrNull = rectangles.firstNotNullOfOrNull { it.area.takeIf { area -> area >= 50 } } + + assertPrints(evenLargerAreaOrNull, "null") + } } class Aggregates { diff --git a/libraries/stdlib/test/collections/MapTest.kt b/libraries/stdlib/test/collections/MapTest.kt index 2daa4c1b168..3d57501c20b 100644 --- a/libraries/stdlib/test/collections/MapTest.kt +++ b/libraries/stdlib/test/collections/MapTest.kt @@ -436,6 +436,22 @@ class MapTest { doTest("built Map", builtMap, "y", 24) } + @Test + fun firstNotNullOf() { + val map = mapOf("Alice" to 20, "Tom" to 13, "Bob" to 18) + + val firstAdult = map.firstNotNullOf { (name, age) -> name.takeIf { age >= 18 } } + val firstAdultOrNull = map.firstNotNullOfOrNull { (name, age) -> name.takeIf { age >= 18 } } + + assertEquals("Alice", firstAdult) + assertEquals("Alice", firstAdultOrNull) + + assertFailsWith { val firstChild = map.firstNotNullOf { (name, age) -> name.takeIf { age <= 11 } } } + val firstChildOrNull = map.firstNotNullOfOrNull { (name, age) -> name.takeIf { age <= 11 } } + + assertNull(firstChildOrNull) + } + fun testPlusAssign(doPlusAssign: (MutableMap) -> Unit) { val map = hashMapOf("a" to 1, "b" to 2) diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index 4effb0962c5..2f57633aaf1 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -731,6 +731,20 @@ public class SequenceTest { assertEquals(s2, s2.orEmpty()) } + @Test + fun firstNotNullOf() { + fun Int.isMonodigit(): Boolean = toString().toHashSet().size == 1 + fun Int.doubleIfNotMonodigit(): Int? = if (this > 9 && this.isMonodigit()) this * 2 else null + + assertEquals(110, fibonacci().firstNotNullOf { it.doubleIfNotMonodigit() }) + assertEquals(110, fibonacci().firstNotNullOfOrNull { it.doubleIfNotMonodigit() }) + + assertFailsWith { + fibonacci().take(10).firstNotNullOf { it.doubleIfNotMonodigit() } + } + assertNull(fibonacci().take(10).firstNotNullOfOrNull { it.doubleIfNotMonodigit() }) + } + /* test fun pairIterator() { val pairStr = (fibonacci() zip fibonacci().map { i -> i*2 }).joinToString(limit = 10) diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt index 4fc603b84af..ff7d9a0fd64 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt @@ -1081,4 +1081,56 @@ object Elements : TemplateGroupBase() { } } + val f_firstNotNullOfOrNull = fn("firstNotNullOfOrNull(transform: (T) -> R?)") { + include(Iterables, Sequences, Maps, CharSequences, ArraysOfObjects) + } builder { + inlineOnly() + since("1.5") + typeParam("R : Any") + returns("R?") + + sample("samples.collections.Collections.Transformations.firstNotNullOf") + + doc { + """ + Returns the first non-null value produced by [transform] function being applied to ${f.element.pluralize()} of this ${f.collection} in iteration order, + or `null` if no non-null value was produced. + """ + } + body { + """ + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null + """ + } + } + + val f_firstNotNullOf = fn("firstNotNullOf(transform: (T) -> R?)") { + include(Iterables, Sequences, Maps, CharSequences, ArraysOfObjects) + } builder { + inlineOnly() + since("1.5") + typeParam("R : Any") + returns("R") + + sample("samples.collections.Collections.Transformations.firstNotNullOf") + + doc { + """ + Returns the first non-null value produced by [transform] function being applied to ${f.element.pluralize()} of this ${f.collection} in iteration order, + or throws [NoSuchElementException] if no non-null value was produced. + """ + } + body { + """ + return firstNotNullOfOrNull(transform) ?: throw NoSuchElementException("No element of the ${f.collection} was transformed to a non-null value.") + """ + } + } + }