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

Either.isRight() contract's seems to not works #3476

Open
mateuszjarzyna opened this issue Jul 20, 2024 · 2 comments
Open

Either.isRight() contract's seems to not works #3476

mateuszjarzyna opened this issue Jul 20, 2024 · 2 comments

Comments

@mateuszjarzyna
Copy link

I have extremely simple code, it's a function that returns Either<SomeError, CommandResult>

            val createdCourse: Either<OwlerError, CommandResult> =
                adminHub.createCourse(courseNameField(form), request.toRequestContext(instantSource.instant()))
            if (createdCourse.isRight()) {
                createdCourse.getOrNull() // it's still the either
            }

My case is that I liked to check if a course was created, but contract/smart cast seems to not work

The CommandResult is a pretty simple interface with two implementations

sealed interface CommandResult {

    val internalEvent: InternalEvent
    val publicEvents: List<PublicEvent>

    data class SuccessfulCommandResult(
        override val internalEvent: InternalEvent,
        override val publicEvents: List<PublicEvent>,
    ) : CommandResult

    data class EntityCreatedCommandResult<E>(
        override val internalEvent: InternalEvent,
        override val publicEvents: List<PublicEvent>,
        val createdEntity: E,
    ) : CommandResult
}

And OwlerError is another interface

sealed interface OwlerError {
    interface DomainError : OwlerError

    data class ValidationError(val errors: NonEmptySet<FieldErrors>) : OwlerError {
        companion object {
            fun from(violations: Set<ConstraintViolation>): ValidationError {
                return ValidationError(violations.mapErrors())
            }
        }
    }
}

I use arrow-kt in version "1.2.4"

And the screenshot from my IDE showing that smart casting does not work corretly.
Zrzut ekranu 2024-07-20 o 14 09 38

If it is relevant here is full list of dependencies

val http4kVersion: String by project
val http4kConnectVersion: String by project
val junitVersion: String by project
val kotlinVersion: String by project
val kotestVersion: String = "5.9.1"
val arrowVersion: String = "1.2.4"
val akkurateVersion: String = "0.8.0"

dependencies {
    implementation(platform("io.arrow-kt:arrow-stack:${arrowVersion}"))

    implementation("org.http4k:http4k-client-okhttp:${http4kVersion}")
    implementation("org.http4k:http4k-connect-ai-openai:${http4kConnectVersion}")
    implementation("org.http4k:http4k-contract:${http4kVersion}")
    implementation("org.http4k:http4k-core:${http4kVersion}")
    implementation("org.http4k:http4k-format-jackson:${http4kVersion}")
    implementation("org.http4k:http4k-opentelemetry:${http4kVersion}")
    implementation("org.http4k:http4k-template-handlebars:${http4kVersion}")
    implementation("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
    implementation("dev.nesk.akkurate:akkurate-core:${akkurateVersion}")
    implementation("dev.nesk.akkurate:akkurate-ksp-plugin:${akkurateVersion}")
    implementation("dev.nesk.akkurate:akkurate-arrow:${akkurateVersion}")
    implementation("io.arrow-kt:arrow-core")
    implementation("io.arrow-kt:arrow-fx-coroutines")
    implementation("io.kotest.extensions:kotest-assertions-arrow:1.4.0")
    implementation("com.github.ksuid:ksuid:1.1.2")
    implementation("com.github.slugify:slugify:3.0.7")

    ksp("dev.nesk.akkurate:akkurate-ksp-plugin:${akkurateVersion}")

    testImplementation("org.http4k:http4k-connect-ai-openai-fake:${http4kConnectVersion}")
    testImplementation("org.http4k:http4k-testing-approval:${http4kVersion}")
    testImplementation("org.http4k:http4k-testing-chaos:${http4kVersion}")
    testImplementation("org.http4k:http4k-testing-hamkrest:${http4kVersion}")
    testImplementation("org.http4k:http4k-testing-kotest:${http4kVersion}")
    testImplementation("org.http4k:http4k-testing-tracerbullet:${http4kVersion}")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
    testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
    testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
    testImplementation("io.kotest:kotest-property:$kotestVersion")
    testImplementation("io.kotest:kotest-framework-datatest:$kotestVersion")
}
@mateuszjarzyna
Copy link
Author

I've created isRight2 function

@OptIn(ExperimentalContracts::class)
private fun  <A, B>  Either<A, B>.isRight2(): Boolean {
    contract {
        returns(true) implies (this@isRight2 is Right<B>)
        returns(false) implies (this@isRight2 is Left<A>)
    }
    return this@isRight2 is Right<B>
}

that works as expected.

Zrzut ekranu 2024-07-20 o 14 23 28 Zrzut ekranu 2024-07-20 o 14 23 39

So it seems like there is some kind of bug in smart casting with isRight/isLeft methods

@serras
Copy link
Member

serras commented Aug 23, 2024

This is a known problem in Kotlin 1.x, where smart casting does not always work with generic types like Either.

@mateuszjarzyna Does this problem also reproduce with Kotlin 2.0?

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

No branches or pull requests

2 participants