arrow-kt / arrow

Λrrow - Functional companion to Kotlin's Standard Library
http://arrow-kt.io
Other
6.18k stars 452 forks source link

@optics generates bad code when dealing with nested generic sealed hierarchies #3384

Closed sindrenm closed 5 months ago

sindrenm commented 8 months ago

Thanks a lot for getting fixes out for https://github.com/arrow-kt/arrow/issues/3380 and https://github.com/arrow-kt/arrow/issues/3381 so fast in version 1.2.3! Now that we're able to start playing around with the Raise DSL, we've discovered another snippet of code that generates bad Kotlin code from Optics. (I'm sorry-not-sorry for doing this!)

Inspired by https://arrow-kt.io/learn/typed-errors/own-error-types/, we did something like this:

@optics
sealed interface LoadingContentOrError<out Data> {
    data object Loading : LoadingContentOrError<Nothing>

    @optics
    sealed interface ContentOrError<out Data> : LoadingContentOrError<Data> {
        companion object
    }

    @optics
    data class Content<out Data>(val data: Data) : ContentOrError<Data> {
        companion object
    }

    @optics
    data class Error(val error: Throwable) : ContentOrError<Nothing> {
        companion object
    }

    companion object
}

(We needed a type that represented only Content or Error, but not Loading, whilst still being a LoadingContentOrError.)

However, unfortunately, @optics isn't too happy about the generic usage here, either, and gives generates the following code:

inline fun <Data> LoadingContentOrError.Companion.contentOrError(): Prism<LoadingContentOrError<Data>, LoadingContentOrError.ContentOrError> = Prism(
    getOrModify = { loadingContentOrError: LoadingContentOrError<Data> ->
        when (loadingContentOrError) {
            is LoadingContentOrError.ContentOrError -> loadingContentOrError.right()
            else -> loadingContentOrError.left()
        }
    },
    reverseGet = ::identity
)

inline fun <S,Data> Iso<S, LoadingContentOrError<Data>>.contentOrError(): Prism<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Lens<S, LoadingContentOrError<Data>>.contentOrError(): Optional<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Optional<S, LoadingContentOrError<Data>>.contentOrError(): Optional<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Prism<S, LoadingContentOrError<Data>>.contentOrError(): Prism<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Setter<S, LoadingContentOrError<Data>>.contentOrError(): Setter<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Traversal<S, LoadingContentOrError<Data>>.contentOrError(): Traversal<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Fold<S, LoadingContentOrError<Data>>.contentOrError(): Fold<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()
 inline fun <S,Data> Every<S, LoadingContentOrError<Data>>.contentOrError(): Every<S, LoadingContentOrError.ContentOrError> = this + LoadingContentOrError.contentOrError()

Here, it's missing the type parameter for LoadingContentOrError.ContentOrError, which should be LoadingContentOrError.ContentOrError<Data>, not just LoadingContentOrError.ContentOrError. The correctly generated code would look like this:

inline fun <Data> LoadingContentOrError.Companion.contentOrError(): Prism<LoadingContentOrError<Data>, LoadingContentOrError.ContentOrError<Data>> =
    Prism(
        getOrModify = { loadingContentOrError: LoadingContentOrError<Data> ->
            when (loadingContentOrError) {
                is LoadingContentOrError.ContentOrError -> loadingContentOrError.right()
                else -> loadingContentOrError.left()
            }
        },
        reverseGet = ::identity,
    )

inline fun <S, Data> Iso<S, LoadingContentOrError<Data>>.contentOrError(): Prism<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Lens<S, LoadingContentOrError<Data>>.contentOrError(): Optional<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Optional<S, LoadingContentOrError<Data>>.contentOrError(): Optional<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Prism<S, LoadingContentOrError<Data>>.contentOrError(): Prism<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Setter<S, LoadingContentOrError<Data>>.contentOrError(): Setter<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Traversal<S, LoadingContentOrError<Data>>.contentOrError(): Traversal<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Fold<S, LoadingContentOrError<Data>>.contentOrError(): Fold<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()
inline fun <S, Data> Every<S, LoadingContentOrError<Data>>.contentOrError(): Every<S, LoadingContentOrError.ContentOrError<Data>> = this + LoadingContentOrError.contentOrError()