hey guys, i posted this in <#C3PQML5NU|multiplatfo...
# javascript
j
hey guys, i posted this in #multiplatform yesterday, and i’m guessing that i’m running into issues here because js doesn’t support non-local returns? does anyone know how I can write this in a manner that will work on js? function:
Copy code
inline fun <reified T> withResult(
    method: Result<T>,
    onFailure: (errors: List<Throwable>) -> Unit
): T {
        @Suppress("IMPLICIT_CAST_TO_ANY")
        val methodResult = when (method) {
            is Result.Success -> { method.t }
            is Result.Failure -> { onFailure.invoke(method.errors) }
        }
        return methodResult as T
}
usage:
Copy code
val r = withResult(method) { return Result.Failure(it) }
i can get this function to work on jvm, however it does not work on js. it results in a
ClassCastException
the transpiled js appears to show that instead of invoking
onFailure
it is only assigned to
methodResult
. also, this is a custom sealed class
Result
not
kotlin.Result
t
Is there GitHub project with this code?
j
there is not (work-related). i can probably share some generic snippets
actually this seems to work, though it’s less desirable
Copy code
inline fun <reified T> withResult(method: Result<T>): Pair<T?, List<Throwable>> {
    var result: T? = null
    var errors: List<Throwable> = mutableListOf()
    when (method) {
        is Result.Success -> { result = method.t }
        is Result.Failure -> { errors = method.errors }
    }
    return Pair(result, errors)
}
r
I’ve had some success directly reading the outputted js code to figure out what’s going wrong flow wise. That said, because you’ve marked the withResult function as inline, it shouldn’t even exist in the output code, so I suspect its not related to the return… but that could be a poor read
j
i did read the output js, which is where noticed the return was being assigned to a value and returned from the function directly.
a
Could you try removing the implicit
invoke
, i.e.
Copy code
is Result.Failure -> { onFailure(method.errors) }
?
j
so i removed the reified param, this removed the cast issue, but the return was never executed and the code continued on and threw an NPE for a non-nullable val lol
@anton.bannykh i believe i did try that, but today is a new day, let me do that again and check
r
in the “val r=” line, is the return inside of the closure intended to be the return of the closure? Or a return to the function the “val r” line is contained in
(just making sure I’m reading this correctly)
r
why is
onFailure
parameter declared as returning
Unit
when you want to pass a lambda returning some kind of value?
r
I’m currently assuming its meant to be an early return so the “val r” never gets assigned
a
@Jess Brent unless I am misinterpreting something, removing
invoke
helps in a toy example: https://pl.kotl.in/iLjQkJkN8
r
I think what’s happening is that the early return is fine, but it can’t stop the withResult function from finishing naturally, and then you get the class cast exception when you try to cast Unit as T
r
@Jess Brent I've got a compiler error
Type inference failed. Expected type mismatch: inferred type is Result.Failure<...> but Unit was expected
j
removing the invoke throws CCE for me, hold on let me show my test
r
I think if you move the return inside of the Result.Success clause, and then indicate the failure case need not return somehow (maybe use the Nothing type?) you should be ok
j
@Robert Jaros i had
Result.Failure
there but it did not change anything for me
Copy code
@Test
    fun success() {
        when(val result = testSuccess()) {
            is Result.Success -> { assertEquals("success", result.t) }
            is Result.Failure -> { asserter.fail("reeeeee") }
        }
    }

    @Test
    fun test_result_failure() {
        when(val result = testFail()) {
            is Result.Success -> { asserter.fail("reeee") }
            is Result.Failure -> { assertEquals("fail", result.errors.first().message) }
        }

    }

    private fun testFail(): Result<String> {
        val s = withResult(test("failme")) { return Result.Failure(it) }

        return Result.Success(s)
    }

    private fun testSuccess(): Result<String> {
        val s = withResult(test("success")) { return Result.Failure(it) }

        return Result.Success(s)
    }

    private fun test(string: String): Result<String> {
        if (string == "failme") {
            return Result.Failure(Error("fail"))
        }
        return Result.Success(string)
    }
r
lol good assertion messages
j
the transpiled function is identical with invoke
Copy code
var Result$Success = _.com.shift4.virgil.Result.Success;
    var Result$Failure = _.com.shift4.virgil.Result.Failure;
    var throwCCE = Kotlin.throwCCE;
    return function (T_0, isT, method, onFailure) {
      var tmp$, tmp$_0;
      var result = method;
      if (Kotlin.isType(result, Result$Success))
        tmp$ = result.t;
      else if (Kotlin.isType(result, Result$Failure))
        tmp$ = onFailure(result.errors);
      else
        tmp$ = Kotlin.noWhenBranchMatched();
      var methodResult = tmp$;
      return isT(tmp$_0 = methodResult) ? tmp$_0 : throwCCE();
it will throw as
Result.Failure
is not
T
r
Is there any situation you imagine where the failure closure would not use an early return? Or is that something with withResult function requires
(meaning supporting both use cases… I’m not suggesting throwing away the thing you want 🙂)
r
can't you use explicit return?
Copy code
val r = withResult<Any>(method) { return@withResult Result.Failure(it) }
j
i can’t see that happening, in that case we’d probably be using a different sealed class for results
r
this seems to compile and work for me: https://pl.kotl.in/nyfpgFgsC
👍 1
r
the return@withResult is actually not the use case… that’s intended to be a return to the parent function
j
that wouldn’t work anyway, as
Any
is not what we want here and would be required for that lambda to not throw CCE
and returning
@withResult
is indeed not what we want
r
Is this code running on non-js platforms correctly?
j
yep
r
imho it's not working any differently
https://pl.kotl.in/-0hQIrbiO it prints
failure
on both JS and JVM
and
success
if i use return@withResult (on both JS and JVM)
j
with a type like
String
though?
r
I wonder if you have a kotlin js version related issue where it works in some and not others. I’ve run into those before. If you can get the error reproducing in the online REPL its pretty easy to try different versions
definitely an interesting problem though. I’m quite distracted 😅 Sorry to blow up the comments here
a
@Jess Brent The transpiled code for the function declaration is the same, but I'm getting different code for it's usages depending on the presence of the
invoke
. Internally the compiler uses JS AST, augmented with additional stuff, so it is possible the behaviour will be different. Here is basically your failing test, which works when there is no
invoke
, but fails when there is one: https://pl.kotl.in/7VxOUywui
j
i’m just going to sit here and be confused that this is working as i sincerely swear i tried this yesterday 😂
👍 2
will double check it works in the actual code
r
Should this be treated as Kotlin/JS bug?
a
Yes, definitely
r
yeah, my playground gets the same error - invoke is the issue, a simple call removes issue https://pl.kotl.in/0_LqijC1W … actually now I’m seeing inconsistent results. Hm. I was seeing it not change consistently (between failing / succeeding with invoke()) for a while, but now its back to consistent after switching between 1.3.70 and 1.3.71 a few times. I’m going to chalk that up to issues with the playground.
j
double checking confirms this works. thanks guys. i swear i tried it this way but i suppose all the different ways i tried are a bit of a blur now 😅
a
Great! Could you file a bug though? (https://youtrack.jetbrains.com/issues/KT)
j
yessir
🙏 1
@anton.bannykh before i create a new issue, could you confirm that this is not the same? https://youtrack.jetbrains.com/issue/KT-24442
this appears to be the exact same issue to me
a
Yeah, looks like KT-24680
j
okay, i’ll add a comment with a link to the discussion here and the playground link 🙂
a
Thanks!
j
and now it works in the playground but throws a CCE locally. i knew i wasn’t crazy 🤪
😞 1
now it works if i remove
reified
que pasando aqui 😂
i’ll collect more detail and add it to the issue
🙏 1