Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
sindrenm opened this issue Feb 29, 2024 · 0 comments · Fixed by #3421
Closed

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

sindrenm opened this issue Feb 29, 2024 · 0 comments · Fixed by #3421

Comments

@sindrenm
Copy link

Thanks a lot for getting fixes out for #3380 and #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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant