From 152469fd44bae69acfadf8f79a9fb4e20bb5c309 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Fri, 31 Mar 2023 17:07:37 +0200 Subject: [PATCH 1/3] Result & Sequence missing deprecation and revert post-fixed names (#3015) Co-authored-by: franciscodr Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Jose Gutierrez --- arrow-libs/core/arrow-core/api/arrow-core.api | 6 - .../commonMain/kotlin/arrow/core/Result.kt | 17 +- .../commonMain/kotlin/arrow/core/Sequence.kt | 149 +++++------------- .../kotlin/arrow/core/SequenceKTest.kt | 37 +---- .../kotlin/examples/example-sequence-12.kt | 4 +- .../kotlin/examples/example-sequence-13.kt | 4 +- .../kotlin/examples/example-sequence-15.kt | 4 +- .../kotlin/examples/example-sequence-16.kt | 4 +- 8 files changed, 63 insertions(+), 162 deletions(-) diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index ad62d06ea76..ccc693931c7 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -1575,7 +1575,6 @@ public final class arrow/core/SequenceKt { public static final fun crosswalk (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; public static final fun crosswalkMap (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun crosswalkNull (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; - public static final fun crosswalkNullList (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun crosswalkT (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun filterOption (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun flatten (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; @@ -1598,7 +1597,6 @@ public final class arrow/core/SequenceKt { public static final fun salign (Lkotlin/sequences/Sequence;Larrow/typeclasses/Semigroup;Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun salign (Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function2;)Lkotlin/sequences/Sequence; public static final fun separateEither (Lkotlin/sequences/Sequence;)Lkotlin/Pair; - public static final fun separateEitherToPair (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun separateValidated (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun sequence (Lkotlin/sequences/Sequence;)Larrow/core/Either; public static final fun sequence (Lkotlin/sequences/Sequence;)Larrow/core/Option; @@ -1617,15 +1615,11 @@ public final class arrow/core/SequenceKt { public static final fun traverseValidated (Lkotlin/sequences/Sequence;Larrow/typeclasses/Semigroup;Lkotlin/jvm/functions/Function1;)Larrow/core/Validated; public static final fun unalign (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun unalign (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; - public static final fun unalignToPair (Lkotlin/sequences/Sequence;)Lkotlin/Pair; - public static final fun unalignToPair (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; public static final fun uniteEither (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun uniteValidated (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun unweave (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; public static final fun unzip (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun unzip (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; - public static final fun unzipToPair (Lkotlin/sequences/Sequence;)Lkotlin/Pair; - public static final fun unzipToPair (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; public static final fun void (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun widen (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun zip (Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function3;)Lkotlin/sequences/Sequence; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt index c64f4dea181..521e04d78e0 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt @@ -29,13 +29,12 @@ public inline fun Result.flatMap(transform: (value: A) -> Result): * Compose a recovering [transform] operation on the failure value [Throwable] whilst flattening [Result]. * @see recoverCatching if you want run a function that catches and maps recovers with `(Throwable) -> A`. */ -public inline fun Result.handleErrorWith(transform: (throwable: Throwable) -> Result): Result { - contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (val exception = exceptionOrNull()) { - null -> this - else -> transform(exception) - } -} +@Deprecated( + "Prefer Kotlin Std Result.recoverCatching instead of handleErrorWith", + ReplaceWith("recoverCatching { transform(it).getOrThrow() }") +) +public inline fun Result.handleErrorWith(transform: (throwable: Throwable) -> Result): Result = + recoverCatching { transform(it).getOrThrow() } /** * Compose both: @@ -44,6 +43,10 @@ public inline fun Result.handleErrorWith(transform: (throwable: Throwable * * Combining the powers of [flatMap] and [handleErrorWith]. */ +@Deprecated( + "Prefer Kotlin Std Result.fold instead of redeemWith", + ReplaceWith("fold(transform, handleErrorWith)") +) public inline fun Result.redeemWith( handleErrorWith: (throwable: Throwable) -> Result, transform: (value: A) -> Result diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt index f5c200e325e..1395500faf1 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt @@ -8,7 +8,9 @@ package arrow.core import arrow.core.Either.Left import arrow.core.Either.Right import arrow.core.raise.RaiseAccumulate +import arrow.core.raise.either import arrow.core.raise.fold +import arrow.core.raise.option import arrow.typeclasses.Monoid import arrow.typeclasses.MonoidDeprecation import arrow.typeclasses.Semigroup @@ -389,10 +391,6 @@ public fun Sequence.crosswalkMap(f: (A) -> Map): Map f(a).toList()}") -) public fun Sequence.crosswalkNull(f: (A) -> B?): Sequence? = fold?>(emptySequence()) { bs, a -> Ior.fromNullables(f(a), bs)?.fold( @@ -402,15 +400,6 @@ public fun Sequence.crosswalkNull(f: (A) -> B?): Sequence? = ) } -public fun Sequence.crosswalkNullList(f: (A) -> B?): List? = - fold?>(emptyList()) { bs, a -> - Ior.fromNullables(f(a), bs)?.fold( - { listOf(it) }, - ::identity, - { l, r -> listOf(l) + r } - ) - } - public fun Sequence>.flatten(): Sequence = flatMap(::identity) @@ -695,10 +684,6 @@ public fun Sequence.salign( * @receiver Iterable of [Either] * @return a tuple containing Sequence with [Either.Left] and another Sequence with its [Either.Right] values. */ -@Deprecated( - "This function is actually terminal. Use separateEitherToPair instead.", - ReplaceWith("separateEitherToPair()") -) public fun Sequence>.separateEither(): Pair, Sequence> = fold(sequenceOf() to sequenceOf()) { (lefts, rights), either -> when (either) { @@ -707,15 +692,6 @@ public fun Sequence>.separateEither(): Pair, Seq } } -public fun Sequence>.separateEitherToPair(): Pair, List> = - fold(listOf() to listOf()) { (lefts, rights), either -> - when (either) { - is Left -> lefts + either.value to rights - is Right -> lefts to rights + either.value - } - } - - /** * Separate the inner [Validated] values into the [Validated.Invalid] and [Validated.Valid]. * @@ -734,8 +710,12 @@ public fun Sequence>.separateValidated(): Pair>, Either>> { s -> either> { s.map, A> { it.bind() }.toList() } }", "arrow.core.raise.either") +) public fun Sequence>.sequence(): Either> = - traverse(::identity) + let { s -> either { s.map { it.bind() }.toList() } } @Deprecated( "sequenceEither is being renamed to sequence to simplify the Arrow API", @@ -744,8 +724,12 @@ public fun Sequence>.sequence(): Either> = public fun Sequence>.sequenceEither(): Either> = sequence().map { it.asSequence() } +@Deprecated( + "The sequence extension function is being deprecated in favor of the Option DSL.\n$NicheAPI", + ReplaceWith("let>, Option>> { s -> option> { s.map, A> { it.bind() }.toList() } }", "arrow.core.raise.option") +) public fun Sequence>.sequence(): Option> = - traverse(::identity) + let { s -> option { s.map { it.bind() }.toList() } } @Deprecated( "sequenceOption is being renamed to sequence to simplify the Arrow API", @@ -803,21 +787,14 @@ public fun Sequence.split(): Pair, A>? = public fun Sequence.tail(): Sequence = drop(1) +@Deprecated( + "Traverse for Sequence is being deprecated in favor of Either DSL.\n$NicheAPI", + ReplaceWith("let, Either>> { s -> either> { s.map { f(it).bind() }.toList() } }", "arrow.core.raise.either") +) @OptIn(ExperimentalTypeInference::class) @OverloadResolutionByLambdaReturnType -public fun Sequence.traverse(f: (A) -> Either): Either> { - // Note: Using a mutable list here avoids the stackoverflows one can accidentally create when using - // Sequence.plus instead. But we don't convert the sequence to a list beforehand to avoid - // forcing too much of the sequence to be evaluated. - val acc = mutableListOf() - forEach { a -> - when (val res = f(a)) { - is Right -> acc.add(res.value) - is Left -> return@traverse res - } - } - return acc.toList().right() -} +public fun Sequence.traverse(f: (A) -> Either): Either> = + let { s -> either { s.map { f(it).bind() }.toList() } } @Deprecated( "traverseEither is being renamed to traverse to simplify the Arrow API", @@ -826,21 +803,14 @@ public fun Sequence.traverse(f: (A) -> Either): Either Sequence.traverseEither(f: (A) -> Either): Either> = traverse(f).map { it.asSequence() } +@Deprecated( + "Traverse for Sequence is being deprecated in favor of Either DSL.\n$NicheAPI", + ReplaceWith("let, Option>> { s -> option> { s.map { f(it).bind() }.toList() } }", "arrow.core.raise.option") +) @OptIn(ExperimentalTypeInference::class) @OverloadResolutionByLambdaReturnType -public fun Sequence.traverse(f: (A) -> Option): Option> { - // Note: Using a mutable list here avoids the stackoverflows one can accidentally create when using - // Sequence.plus instead. But we don't convert the sequence to a list beforehand to avoid - // forcing too much of the sequence to be evaluated. - val acc = mutableListOf() - forEach { a -> - when (val res = f(a)) { - is Some -> acc.add(res.value) - is None -> return@traverse res - } - } - return Some(acc) -} +public fun Sequence.traverse(f: (A) -> Option): Option> = + let { s -> option { s.map { f(it).bind() }.toList() } } @Deprecated( "traverseOption is being renamed to traverse to simplify the Arrow API", @@ -898,38 +868,25 @@ public fun Sequence.traverseValidated( ): Validated> = traverse(semigroup, f).map { it.asSequence() } -@Deprecated( - "This function is actually terminal. Use unalignToPair instead.", - ReplaceWith("unalignToPair()") -) -public fun Sequence>.unalign(): Pair, Sequence> = - fold(emptySequence() to emptySequence()) { (l, r), x -> - x.fold( - { l + it to r }, - { l to r + it }, - { a, b -> l + a to r + b } - ) - } - /** * splits an union into its component parts. * * ```kotlin * import arrow.core.bothIor * import arrow.core.leftIor - * import arrow.core.unalignToPair + * import arrow.core.unalign * * fun main(args: Array) { * //sampleStart - * val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalignToPair() + * val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalign() * //sampleEnd * println("(${result.first}, ${result.second})") * } * ``` * */ -public fun Sequence>.unalignToPair(): Pair, List> = - fold(emptyList() to emptyList()) { (l, r), x -> +public fun Sequence>.unalign(): Pair, Sequence> = + fold(emptySequence() to emptySequence()) { (l, r), x -> x.fold( { l + it to r }, { l to r + it }, @@ -937,32 +894,24 @@ public fun Sequence>.unalignToPair(): Pair, List> = ) } - -@Deprecated( - "This function is actually terminal. Use unalignToPair instead.", - ReplaceWith("unalignToPair(fa)") -) -public fun Sequence.unalign(fa: (C) -> Ior): Pair, Sequence> = - map(fa).unalign() - /** * after applying the given function, splits the resulting union shaped structure into its components parts * * ```kotlin * import arrow.core.leftIor - * import arrow.core.unalignToPair + * import arrow.core.unalign * * fun main(args: Array) { * //sampleStart - * val result = sequenceOf(1, 2, 3).unalignToPair { it.leftIor() } + * val result = sequenceOf(1, 2, 3).unalign { it.leftIor() } * //sampleEnd * println("(${result.first.toList()}, ${result.second.toList()})") * } * ``` * */ -public fun Sequence.unalignToPair(fa: (C) -> Ior): Pair, List> = - map(fa).unalignToPair() +public fun Sequence.unalign(fa: (C) -> Ior): Pair, Sequence> = + map(fa).unalign() @Deprecated( NicheAPI + "Prefer using flatMap + fold", @@ -1007,53 +956,36 @@ public fun Sequence.unweave(ffa: (A) -> Sequence): Sequence = ffa(a).interleave(fa.unweave(ffa)) } ?: emptySequence() -@Deprecated( - "This function is actually terminal. Use unzipToPair instead.", - ReplaceWith("unzipToPair()") -) -public fun Sequence>.unzip(): Pair, Sequence> = - fold(emptySequence() to emptySequence()) { (l, r), x -> - l + x.first to r + x.second - } - /** * unzips the structure holding the resulting elements in an `Pair` * * ```kotlin - * import arrow.core.unzipToPair + * import arrow.core.unzip * * fun main(args: Array) { * //sampleStart - * val result = sequenceOf("A" to 1, "B" to 2).unzipToPair() + * val result = sequenceOf("A" to 1, "B" to 2).unzip() * //sampleEnd * println("(${result.first}, ${result.second})") * } * ``` * */ -public fun Sequence>.unzipToPair(): Pair, List> = - fold(emptyList() to emptyList()) { (l, r), x -> +public fun Sequence>.unzip(): Pair, Sequence> = + fold(emptySequence() to emptySequence()) { (l, r), x -> l + x.first to r + x.second } - -@Deprecated( - "This function is actually terminal. Use unzipToPair instead.", - ReplaceWith("unzipToPair(fc)") -) -public fun Sequence.unzip(fc: (C) -> Pair): Pair, Sequence> = - map(fc).unzip() - /** * after applying the given function unzip the resulting structure into its elements. * * ```kotlin - * import arrow.core.unzipToPair + * import arrow.core.unzip * * fun main(args: Array) { * //sampleStart * val result = - * sequenceOf("A:1", "B:2", "C:3").unzipToPair { e -> + * sequenceOf("A:1", "B:2", "C:3").unzip { e -> * e.split(":").let { * it.first() to it.last() * } @@ -1064,9 +996,8 @@ public fun Sequence.unzip(fc: (C) -> Pair): Pair, * ``` * */ -public fun Sequence.unzipToPair(fc: (C) -> Pair): Pair, List> = - map(fc).unzipToPair() - +public fun Sequence.unzip(fc: (C) -> Pair): Pair, Sequence> = + map(fc).unzip() @Deprecated( "void is being deprecated in favor of simple Iterable.map.\n$NicheAPI", diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt index 5903550d201..e9d8c4a2d72 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt @@ -248,16 +248,6 @@ class SequenceKTest : StringSpec({ } } - "crosswalk the sequence to a nullable function" { - checkAll(Arb.list(Arb.int())){ list -> - fun nullEvens(i: Int): Int? = if(i % 2 == 0) i else null - - val obtained = list.asSequence().crosswalkNullList { nullEvens(it) } - val expected = list.map { nullEvens(it) } - obtained?.size shouldBe expected.filterNotNull().size - } - } - "can align sequences - 1" { checkAll(Arb.sequence(Arb.unit()), Arb.sequence(Arb.unit())) { a, b -> a.align(b).toList().size shouldBe max(a.toList().size, b.toList().size) @@ -322,23 +312,6 @@ class SequenceKTest : StringSpec({ } } - "unzipToPair should unzip values in a Pair in a Sequence of Pairs" { - checkAll(Arb.list(Arb.pair(Arb.int(), Arb.int()))){ pairList -> - val obtained = pairList.asSequence().unzipToPair() - val expected = pairList.unzip() - obtained shouldBe expected - } - } - - "unzipToPair should unzip values in a Pair in a Sequence" { - checkAll(Arb.list(Arb.int())){ list -> - val obtained = list.asSequence().unzipToPair { n -> Pair(n, n)} - val expected = list.unzip { n -> Pair(n, n) } - obtained shouldBe expected - } - } - - "filterOption should filter None" { checkAll(Arb.list(Arb.option(Arb.int()))) { ints -> ints.asSequence().filterOption().toList() shouldBe ints.filterOption() @@ -352,20 +325,20 @@ class SequenceKTest : StringSpec({ else it.right() } - val (lefts, rights) = sequence.separateEitherToPair() + val (lefts, rights) = sequence.separateEither() - lefts to rights shouldBe ints.partition { it % 2 == 0 } + lefts.toList() to rights.toList() shouldBe ints.partition { it % 2 == 0 } } } "separateValidated" { checkAll(Arb.sequence(Arb.int())) { ints -> val sequence = ints.map { - if (it % 2 == 0) it.left() - else it.right() + if (it % 2 == 0) it.invalid() + else it.valid() } - val (invalids, valids) = sequence.separateEitherToPair() + val (invalids, valids) = sequence.separateValidated() invalids.toList() to valids.toList() shouldBe ints.partition { it % 2 == 0 } } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt index 4d3194f9f85..d23efa81b24 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt @@ -3,11 +3,11 @@ package arrow.core.examples.exampleSequence12 import arrow.core.bothIor import arrow.core.leftIor -import arrow.core.unalignToPair +import arrow.core.unalign fun main(args: Array) { //sampleStart - val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalignToPair() + val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalign() //sampleEnd println("(${result.first}, ${result.second})") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt index 6c530ffda5e..5f18e813bc1 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt @@ -2,11 +2,11 @@ package arrow.core.examples.exampleSequence13 import arrow.core.leftIor -import arrow.core.unalignToPair +import arrow.core.unalign fun main(args: Array) { //sampleStart - val result = sequenceOf(1, 2, 3).unalignToPair { it.leftIor() } + val result = sequenceOf(1, 2, 3).unalign { it.leftIor() } //sampleEnd println("(${result.first.toList()}, ${result.second.toList()})") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt index e84c1ad1a05..f565abbb968 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt @@ -1,11 +1,11 @@ // This file was automatically generated from Sequence.kt by Knit tool. Do not edit. package arrow.core.examples.exampleSequence15 -import arrow.core.unzipToPair +import arrow.core.unzip fun main(args: Array) { //sampleStart - val result = sequenceOf("A" to 1, "B" to 2).unzipToPair() + val result = sequenceOf("A" to 1, "B" to 2).unzip() //sampleEnd println("(${result.first}, ${result.second})") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt index 6baf2ab5141..41dba1d23d2 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt @@ -1,12 +1,12 @@ // This file was automatically generated from Sequence.kt by Knit tool. Do not edit. package arrow.core.examples.exampleSequence16 -import arrow.core.unzipToPair +import arrow.core.unzip fun main(args: Array) { //sampleStart val result = - sequenceOf("A:1", "B:2", "C:3").unzipToPair { e -> + sequenceOf("A:1", "B:2", "C:3").unzip { e -> e.split(":").let { it.first() to it.last() } From d766fa9af767394fdc6dfd768792541909931898 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Mon, 3 Apr 2023 09:18:58 +0200 Subject: [PATCH 2/3] Update BOM (#3018) --- arrow-libs/stack/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arrow-libs/stack/build.gradle.kts b/arrow-libs/stack/build.gradle.kts index 4435af4134a..200af1a15fb 100644 --- a/arrow-libs/stack/build.gradle.kts +++ b/arrow-libs/stack/build.gradle.kts @@ -11,14 +11,14 @@ dependencies { constraints { api("io.arrow-kt:arrow-annotations:$version") api("io.arrow-kt:arrow-continuations:$version") + api("io.arrow-kt:arrow-atomic:$version") api("io.arrow-kt:arrow-core:$version") api("io.arrow-kt:arrow-core-retrofit:$version") - api("io.arrow-kt:arrow-core-test:$version") api("io.arrow-kt:arrow-fx-coroutines:$version") - api("io.arrow-kt:arrow-fx-coroutines-test:$version") api("io.arrow-kt:arrow-fx-stm:$version") - api("io.arrow-kt:arrow-meta:$version") + api("io.arrow-kt:arrow-resilience:$version") api("io.arrow-kt:arrow-optics:$version") - api("io.arrow-kt:arrow-optics-test:$version") + api("io.arrow-kt:arrow-optics-reflect:$version") + api("io.arrow-kt:arrow-optics-ksp-plugin:$version") } } From 246ec9819c967d3b778e00b973d527846ba4a250 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Mon, 3 Apr 2023 11:47:44 +0200 Subject: [PATCH 3/3] Backport creation of Isos for value classes (#3021) --- .../arrow/optics/plugin/DeclarationUtils.kt | 3 ++ .../arrow/optics/plugin/OpticsProcessor.kt | 2 +- .../arrow/optics/plugin/internals/domain.kt | 5 +++ .../arrow/optics/plugin/internals/dsl.kt | 44 +++++++++++++++++++ .../arrow/optics/plugin/internals/errors.kt | 4 +- .../arrow/optics/plugin/internals/isos.kt | 7 ++- .../optics/plugin/internals/processor.kt | 34 +++++++++++--- .../arrow/optics/plugin/internals/snippets.kt | 1 + .../kotlin/arrow/optics/plugin/IsoTests.kt | 27 ++++++++++++ 9 files changed, 115 insertions(+), 12 deletions(-) diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt index a126ce33857..bf2e2946650 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt @@ -11,3 +11,6 @@ val KSClassDeclaration.isSealed val KSClassDeclaration.isData get() = modifiers.contains(Modifier.DATA) + +val KSClassDeclaration.isValue + get() = modifiers.contains(Modifier.VALUE) diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt index 8571b26df52..a19014bcafb 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt @@ -32,7 +32,7 @@ class OpticsProcessor(private val codegen: CodeGenerator, private val logger: KS private fun processClass(klass: KSClassDeclaration) { // check that it is sealed or data - if (!klass.isSealed && !klass.isData) { + if (!klass.isSealed && !klass.isData && !klass.isValue) { logger.error(klass.qualifiedNameOrSimpleName.otherClassTypeErrorMessage, klass) return } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt index 69d4f052206..159358715d1 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt @@ -50,6 +50,8 @@ typealias SealedClassDsl = Target.SealedClassDsl typealias DataClassDsl = Target.DataClassDsl +typealias ValueClassDsl = Target.ValueClassDsl + sealed class Target { abstract val foci: List @@ -59,6 +61,9 @@ sealed class Target { data class Optional(override val foci: List) : Target() data class SealedClassDsl(override val foci: List) : Target() data class DataClassDsl(override val foci: List) : Target() + data class ValueClassDsl(val focus: Focus) : Target() { + override val foci = listOf(focus) + } } typealias NonNullFocus = Focus.NonNull diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt index 35aca64ed08..4ff9a1dd189 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt @@ -32,6 +32,16 @@ fun generatePrismDsl(ele: ADT, isoOptic: SealedClassDsl): Snippet { ) } +fun generateIsoDsl(ele: ADT, isoOptic: ValueClassDsl): Snippet { + val (className, import) = resolveClassName(ele) + return Snippet( + `package` = ele.packageName, + name = ele.simpleName, + content = processIsoSyntax(ele, isoOptic, className), + imports = setOf(import) + ) +} + private fun processLensSyntax(ele: ADT, foci: List, className: String): String { return if (ele.typeParameters.isEmpty()) { foci.joinToString(separator = "\n") { focus -> @@ -137,6 +147,40 @@ private fun processPrismSyntax(ele: ADT, dsl: SealedClassDsl, className: String) } } +private fun processIsoSyntax(ele: ADT, dsl: ValueClassDsl, className: String): String = + if (ele.typeParameters.isEmpty()) { + dsl.foci.joinToString(separator = "\n\n") { focus -> + """ + |${ele.visibilityModifierName} inline val $Iso.${focus.paramName}: $Iso inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Lens.${focus.paramName}: $Lens inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Optional.${focus.paramName}: $Optional inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Prism.${focus.paramName}: $Prism inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Setter.${focus.paramName}: $Setter inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Traversal.${focus.paramName}: $Traversal inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Fold.${focus.paramName}: $Fold inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val $Every.${focus.paramName}: $Every inline get() = this + ${className}.${focus.paramName} + |""".trimMargin() + } + } else { + dsl.foci.joinToString(separator = "\n\n") { focus -> + val sourceClassNameWithParams = focus.refinedType?.qualifiedString() ?: "${ele.sourceClassName}${ele.angledTypeParameters}" + val joinedTypeParams = when { + focus.refinedArguments.isEmpty() -> "" + else -> focus.refinedArguments.joinToString(separator=",") + } + """ + |${ele.visibilityModifierName} inline fun $Iso.${focus.paramName}(): $Iso = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Lens.${focus.paramName}(): $Lens = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Optional.${focus.paramName}(): $Optional = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Prism.${focus.paramName}(): $Prism = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Setter.${focus.paramName}(): $Setter = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Traversal.${focus.paramName}(): $Traversal = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Fold.${focus.paramName}(): $Fold = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun $Every.${focus.paramName}(): $Every = this + ${className}.${focus.paramName}() + |""".trimMargin() + } + } + private fun resolveClassName(ele: ADT): Pair = if (hasPackageCollisions(ele)) { val classNameAlias = ele.sourceClassName.replace(".", "") val aliasImport = "import ${ele.sourceClassName} as $classNameAlias" diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt index 1c9b65fd979..7d634ac6e21 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt @@ -5,7 +5,7 @@ val String.otherClassTypeErrorMessage """ |$this cannot be annotated with @optics | ^ - |Only data and sealed classes can be annotated with @optics""".trimMargin() + |Only data, sealed, and value classes can be annotated with @optics""".trimMargin() val String.typeParametersErrorMessage get() = @@ -47,7 +47,7 @@ val String.isoErrorMessage |Cannot generate arrow.optics.Iso for $this | ^ |arrow.optics.OpticsTarget.ISO is an invalid @optics argument for $this. - |It is only valid for data classes. + |It is only valid for data and value classes. """.trimMargin() val String.isoTooBigErrorMessage diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt index dce33e5b3e0..64a89d6a746 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt @@ -1,5 +1,7 @@ package arrow.optics.plugin.internals +import arrow.optics.plugin.isValue + internal fun generateIsos(ele: ADT, target: IsoTarget) = Snippet(`package` = ele.packageName, name = ele.simpleName, content = processElement(ele, target)) @@ -63,12 +65,13 @@ private fun processElement(iso: ADT, target: Target): String { "tuple: ${focusType()} -> ${(foci.indices).joinToString(prefix = "${iso.sourceClassName}(", postfix = ")", transform = { "tuple.${letters[it]}" })}" } + val isoName = if (iso.declaration.isValue) target.foci.first().paramName else "iso" val sourceClassNameWithParams = "${iso.sourceClassName}${iso.angledTypeParameters}" val firstLine = when { iso.typeParameters.isEmpty() -> - "${iso.visibilityModifierName} inline val ${iso.sourceClassName}.Companion.iso: $Iso<${iso.sourceClassName}, ${focusType()}> inline get()" + "${iso.visibilityModifierName} inline val ${iso.sourceClassName}.Companion.$isoName: $Iso<${iso.sourceClassName}, ${focusType()}> inline get()" else -> - "${iso.visibilityModifierName} inline fun ${iso.angledTypeParameters} ${iso.sourceClassName}.Companion.iso(): $Iso<$sourceClassNameWithParams, ${focusType()}>" + "${iso.visibilityModifierName} inline fun ${iso.angledTypeParameters} ${iso.sourceClassName}.Companion.$isoName(): $Iso<$sourceClassNameWithParams, ${focusType()}>" } return """ diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt index 4ef66b4dce9..9ef7f4f2809 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt @@ -2,6 +2,7 @@ package arrow.optics.plugin.internals import arrow.optics.plugin.isData import arrow.optics.plugin.isSealed +import arrow.optics.plugin.isValue import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.* import java.util.Locale @@ -18,8 +19,12 @@ internal fun adt(c: KSClassDeclaration, logger: KSPLogger): ADT = OpticsTarget.OPTIONAL -> evalAnnotatedDataClass(c, c.qualifiedNameOrSimpleName.optionalErrorMessage, logger) .let(::OptionalTarget) - OpticsTarget.ISO -> evalAnnotatedIsoElement(c, logger).let(::IsoTarget) - OpticsTarget.PRISM -> evalAnnotatedPrismElement(c, logger).let(::PrismTarget) + OpticsTarget.ISO -> + evalAnnotatedIsoElement(c, c.qualifiedNameOrSimpleName.isoErrorMessage, logger) + .let(::IsoTarget) + OpticsTarget.PRISM -> + evalAnnotatedPrismElement(c, c.qualifiedNameOrSimpleName.prismErrorMessage, logger) + .let(::PrismTarget) OpticsTarget.DSL -> evalAnnotatedDslElement(c, logger) } } @@ -32,6 +37,9 @@ internal fun KSClassDeclaration.targets(): List = if (targets.isEmpty()) listOf(OpticsTarget.PRISM, OpticsTarget.DSL) else targets.filter { it == OpticsTarget.PRISM || it == OpticsTarget.DSL } + isValue -> + listOf(OpticsTarget.ISO, OpticsTarget.DSL) + .filter { targets.isEmpty() || it in targets } else -> if (targets.isEmpty()) listOf(OpticsTarget.ISO, OpticsTarget.LENS, OpticsTarget.OPTIONAL, OpticsTarget.DSL) @@ -62,6 +70,7 @@ internal fun KSClassDeclaration.targetsFromOpticsAnnotation(): List = when { @@ -74,7 +83,7 @@ internal fun evalAnnotatedPrismElement( ) }.toList() else -> { - logger.error(element.qualifiedNameOrSimpleName.prismErrorMessage, element) + logger.error(errorMessage, element) emptyList() } } @@ -109,11 +118,20 @@ internal fun evalAnnotatedDslElement(element: KSClassDeclaration, logger: KSPLog .getConstructorTypesNames() .zip(element.getConstructorParamNames(), Focus.Companion::invoke) ) - element.isSealed -> SealedClassDsl(evalAnnotatedPrismElement(element, logger)) - else -> throw IllegalStateException("should only be sealed or data by now") + element.isValue -> + ValueClassDsl( + Focus(element.getConstructorTypesNames().first(), element.getConstructorParamNames().first()) + ) + element.isSealed -> + SealedClassDsl(evalAnnotatedPrismElement(element, element.qualifiedNameOrSimpleName.prismErrorMessage, logger)) + else -> throw IllegalStateException("should only be sealed, data, or value by now") } -internal fun evalAnnotatedIsoElement(element: KSClassDeclaration, logger: KSPLogger): List = +internal fun evalAnnotatedIsoElement( + element: KSClassDeclaration, + errorMessage: String, + logger: KSPLogger +): List = when { element.isData -> element @@ -124,8 +142,10 @@ internal fun evalAnnotatedIsoElement(element: KSClassDeclaration, logger: KSPLog logger.error(element.qualifiedNameOrSimpleName.isoTooBigErrorMessage, element) emptyList() } + element.isValue -> + listOf(Focus(element.getConstructorTypesNames().first(), element.getConstructorParamNames().first())) else -> { - logger.error(element.qualifiedNameOrSimpleName.isoErrorMessage, element) + logger.error(errorMessage, element) emptyList() } } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt index 6252cb46990..ebb6924bf75 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt @@ -9,6 +9,7 @@ internal fun ADT.snippets(): List = is OptionalTarget -> generateOptionals(this, it) is SealedClassDsl -> generatePrismDsl(this, it) is DataClassDsl -> generateOptionalDsl(this, it) + generateLensDsl(this, it) + is ValueClassDsl -> generateIsoDsl(this, it) } } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt index 5ed4b3d6556..3c4885a69b6 100755 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt @@ -99,4 +99,31 @@ class IsoTests { |} """.failsWith { it.contains("${`package`.removePrefix("package ")}.IsoXXL".isoTooBigErrorMessage) } } + + @Test + fun `Isos will be generated for value class`() { + """ + |$`package` + |$imports + |@optics @JvmInline + |value class IsoData( + | val field1: String + |) { companion object } + | + |val i: Iso = IsoData.field1 + |val r = i != null + """.evals("r" to true) + } + + @Test + fun `Iso generation requires companion object declaration, value class`() { + """ + |$`package` + |$imports + |@optics @JvmInline + |value class IsoNoCompanion( + | val field1: String + |) + """.failsWith { it.contains("IsoNoCompanion".noCompanion) } + } }