Stream: compiler development

Topic: Num.powInt type mismatch


view this post on Zulip JRI98 (May 03 2024 at 10:15):

The following snippet:

app [main] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br",
}

exampleU8 : U8
exampleU8 = Num.powInt 256 0

main = crash ""

gives this error while building:

This powInt call produces:

    I16, U16, F32, I32, U32, F64, I64, U64, I128, Dec, or U128

But the type annotation on exampleU8 says it should be:

    U8

I can get around the error by replacing exampleU8 = Num.powInt 256 0 with exampleU8 = Num.toU8 (Num.powInt 256 0) but I am wondering why the first form is not allowed.

FYI this is a reduction from a bigger program that was crashing while building, giving the error ambient lambda set function import is not a function, found: Error.

view this post on Zulip Anton (May 03 2024 at 10:24):

Hi John,
U8 only goes to 255, so we start we something that's not a U8 which is why we end up with something that can't be U8. Because the signature of powInt is Int a, Int a -> Int a you need to use the same type in every "spot". The compiler does not do automatic downcasting (for good reasons probably).

view this post on Zulip Folkert de Vries (May 03 2024 at 10:27):

hmm this is kind of subtle. it's not obvious that the value of a literal is influencing type inference in this way

view this post on Zulip Anton (May 03 2024 at 10:32):

It does work with

exampleU8 : U8
exampleU8 = Num.powInt 255 0

view this post on Zulip JRI98 (May 03 2024 at 11:23):

This issue came up in an old codebase that I was updating to accommodate the new changes to the language. So this has worked in the past.
To give more context, I am converting bytes (List U8) to integers.
So, when previously I was doing

resultU8 : U8
resultU8 = byte1 * (Num.powInt 256 0)

resultU16 : U16
resultU16 = byte1 * (Num.powInt 256 1) + byte2 * (Num.powInt 256 0)

now I need to do

resultU8 : U8
resultU8 = byte1 * Num.toU8 (Num.powInt 256 0)

resultU16 : U16
resultU16 = Num.toU16 byte1 * (Num.powInt 256 1) + Num.toU16 byte2 * (Num.powInt 256 0)

(byte1 and byte2 are of type U8)
The new version is more verbose but arguably more correct as there are no hidden upcasts/downcasts.

view this post on Zulip Folkert de Vries (May 03 2024 at 11:26):

I think really you should be using bitshifts here

view this post on Zulip Folkert de Vries (May 03 2024 at 11:28):

and really really we should have "turn bytes into little/big/native endian number of a wider type" functions in the standard library

view this post on Zulip JRI98 (May 03 2024 at 11:29):

In an even earlier version of the code I was using Num.bytesToUxx but they went away a couple of months ago

view this post on Zulip Folkert de Vries (May 03 2024 at 11:45):

@Richard Feldman what's our plan here?

view this post on Zulip Brendan Hansknecht (May 03 2024 at 14:59):

I think the last discussion about this left it at: this is easy enough to do in userland/a library with bit shifts. So no need to do it in the standard.

view this post on Zulip Brendan Hansknecht (May 03 2024 at 15:00):

I think an annoyance with doing it in the standard is that you don't want to return a list (unnecessary allocation).

view this post on Zulip Brendan Hansknecht (May 03 2024 at 15:00):

Instead, you are stuck with N different functions that return different size tuples.

view this post on Zulip Brendan Hansknecht (May 03 2024 at 15:01):

Since that didn't seem like a great API, I think it was left for a user to just do the bitshifting.


Last updated: Jul 06 2025 at 12:14 UTC