Stream: beginners

Topic: ✔ Opaque error message


view this post on Zulip Ian McLerran (Apr 25 2024 at 17:40):

I hit a pretty opaque error message today, and not sure what to do with it. Any help would be much appreciated! The error is as follows:

This expectation crashed while running:
51  expect normalize (fromYmdhms 1970 1 2 -12 0 0) == fromYmdhms 1970 1 1 12 0 0
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The crash reported this message:
Hit an erroneous type when creating a layout for `4.IdentId(21)`

Here is the relevant code (note that all functions listed here have been tested and seem to be working prior to addition of DateTime.normalize):

# from DateTime.roc
DateTime : { date : Date.Date, time : Time.Time }

normalize : DateTime -> DateTime
normalize = \dateTime ->
    addHours {
        date: dateTime.date,
        time: Time.fromHmsn 0 dateTime.time.minute dateTime.time.second dateTime.time.nanosecond
    } dateTime.time.hour

expect normalize (fromYmdhms 1970 1 2 -12 0 0) == fromYmdhms 1970 1 1 12 0 0

addHours : DateTime, Int * -> DateTime
addHours = \dateTime, hours -> addNanoseconds dateTime (hours * Const.nanosPerHour)

addNanoseconds : DateTime, Int * -> DateTime
addNanoseconds = \dateTime, nanos ->
    timeNanos = Time.toNanosSinceMidnight dateTime.time + Num.toI64 nanos
    days = if timeNanos >= 0
        then timeNanos // Const.nanosPerDay |> Num.toI64
        else timeNanos // Const.nanosPerDay |> Num.add (if timeNanos % Const.nanosPerDay < 0 then -1 else 0) |> Num.toI64
    { date:  Date.addDays dateTime.date days, time: Time.fromNanosSinceMidnight timeNanos |> Time.normalize }
# from Time.roc
Time : { hour : I8, minute : U8, second : U8, nanosecond : U32 }
# from Date.roc
Date : { year: I64, month: U8, dayOfMonth: U8, dayOfYear: U16 }

view this post on Zulip Luke Boswell (Apr 25 2024 at 20:51):

Can you make put it in a single Interface module as a minimal reproduction and log an Issue please?

view this post on Zulip Luke Boswell (Apr 25 2024 at 20:51):

I'm not sure what the issue is specifically, sorry.

view this post on Zulip Ian McLerran (Apr 25 2024 at 20:52):

Update: I tried adjusting my normalize function to see if I could work around this error and encountered a new crash:

thread 'main' panicked at crates/compiler/alias_analysis/src/lib.rs:612:38:
no entry found for key
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

All of the below variations crash the same way

normalize : DateTime -> DateTime
normalize = \dateTime ->
    { date: dateTime.date, time: Time.midnight }
    |> addHours dateTime.time.hour
    |> addMinutes dateTime.time.minute
    |> addSeconds dateTime.time.second
    |> addNanoseconds dateTime.time.nanosecond
normalize : DateTime -> DateTime
normalize = \dateTime ->
    { date: (Date.fromYd dateTime.date.year dateTime.date.dayOfYear), time: Time.midnight }
    |> addHours dateTime.time.hour
    |> addMinutes dateTime.time.minute
    |> addSeconds dateTime.time.second
    |> addNanoseconds dateTime.time.nanosecond
normalize : DateTime -> DateTime
normalize = \dateTime ->
    fromYd dateTime.date.year dateTime.date.dayOfYear
    |> addHours dateTime.time.hour
    |> addMinutes dateTime.time.minute
    |> addSeconds dateTime.time.second
    |> addNanoseconds dateTime.time.nanosecond

view this post on Zulip Ian McLerran (Apr 25 2024 at 21:13):

@Luke Boswell So far I have not been able to create a min repro. Here's what I have thus far. Any suggestions on a direction I should look?

interface MinRepro
    exposes []
    imports []

T1: { a: U8 }
T2: { b: U8 }
T3: { t1: T1, t2: T2 }

foo : T3 -> T3
foo = \t3 ->
    bar { t1: t3.t1, t2: { b: 0 } } 1

bar : T3, U8 -> T3
bar = \t3, n ->
    { t1: { a: t3.t1.a + n }, t2: t3.t2 }

expect foo { t1: { a: 1 }, t2: { b: 2 } } == { t1: { a: 2 }, t2: { b: 0 } }

view this post on Zulip Luke Boswell (Apr 25 2024 at 21:28):

I suspect it's related to importing symbols or a naming issue. Maybe we need two modules to reproduce?

view this post on Zulip Luke Boswell (Apr 25 2024 at 21:28):

I'd start with the code you have that produces it, and remove things one at a time.

view this post on Zulip Ian McLerran (Apr 25 2024 at 21:30):

Any preference on whether I work backwards from the rust panic or Roc crash?

view this post on Zulip Luke Boswell (Apr 25 2024 at 21:31):

I dont have any preference, though they might be a similar issue. Maybe start with the first?

view this post on Zulip Luke Boswell (Apr 25 2024 at 21:32):

I can probably tinker with it later, just busy with some other things rn.

view this post on Zulip Ian McLerran (Apr 25 2024 at 21:32):

That was my instinct that they were related.

view this post on Zulip Ian McLerran (Apr 25 2024 at 21:34):

I'll get a branch of my repo up with my latest (borked) code, if you happen to take a look. I'm gonna be signing off for the day soon, but I'll keep working on this tomorrow.

view this post on Zulip Ian McLerran (Apr 25 2024 at 21:47):

https://github.com/imclerran/Roc-IsoDate/blob/637fdc6135e802d4fbebbed022ed96243639a6f7/package/DateTime.roc#L48-L77

view this post on Zulip Ian McLerran (Apr 25 2024 at 21:47):

^ dedicated branch of repo - linked to function location

view this post on Zulip Ian McLerran (Apr 26 2024 at 13:22):

Still working on narrowing down the source of this bug, but a quick update. I've narrowed the issue down to addHours and addNanoseconds - when using a self referential argument as the int argument to addHours.

A dbg statement inside addHours prior to calling addNanoseconds will successfully print before the crash. A first line dbg inside addNanoseconds will never print. However, making a direct call to addNanoseconds with a self referential argument for the int argument does not trigger the crash.

expect # CRASH
    dt = fromYmdhms 1970 1 1 1 0 0
    dt2 = addHours dt dt.time.hour
    dt2 == fromYmdhmsn 1970 1 1 2 0 0 0

expect  # NO CRASH
    dt = fromYmdhms 1970 1 1 1 0 0
    dt2 = addHours 1
    dt2 == fromYmdhmsn 1970 1 1 2 0 0 0

expect #  NO CRASH
    dt = fromYmdhmsn 1970 1 1 1 0 0 1
    dt2 = addNanoseconds dt dt.time.nanosecond #
    dt2 == fromYmdhmsn 1970 1 1 1 0 0 2

view this post on Zulip Ian McLerran (Apr 26 2024 at 13:36):

Okay, I seem to have found the issue. I discovered I had an integer overflow in addHours if I passed any number type smaller than a 64-bit int into addHours. Adding a typecast to I64 on hours before multiplying by nanosPerHour solved both the overflow and the Hit an erroneous type when creating a layout for '4.IdentId(21)' crash.

Gonna be switching gears for a little while, but when I come back, should have a good direction to look for a min repro. Looks like an int overflow is the base issue, then just need to identify the modifier which is unhandled to trigger the layout crash.

view this post on Zulip Ian McLerran (Apr 26 2024 at 13:51):

# WILL CRASH:
addHours : DateTime, Int * -> DateTime
addHours = \dateTime, hours -> addNanoseconds dateTime (hours * Const.nanosPerHour)

# NO CRASH:
addHours = \dateTime, hours -> addNanoseconds dateTime (Num.toI64 hours * Const.nanosPerHour)
expect # NO CRASH
    dt = fromYmdhms 1970 1 1 0 0 0
    addHours dt 1 == fromYmdhms 1970 1 1 2 0 0

expect # CRASH: INT OVERFLOW
    dt = fromYmdhms 1970 1 1 0 0 0
    addHours dt 1u32 == fromYmdhms 1970 1 1 2 0 0

expect # CRASH: ERRONEOUS TYPE FOR LAYOUT `4.IdentId(21)`
    dt = fromYmdhms 1970 1 1 0 0 0
    addHours dt dt.time.hour == fromYmdhms 1970 1 1 2 0 0

view this post on Zulip Ian McLerran (Apr 26 2024 at 14:05):

Updated git link with the 3 expects above

view this post on Zulip Ian McLerran (Apr 26 2024 at 22:17):

Okay, I've got the min repro. Its based on an integer overflow, when a mathematical operation is performed on an Int *, along with a constant defined in an external module. The constant must a derived value, where both the original value and the derivation are external to the current module, and the constant's value exceeds the maximum value of the type which is used as the Int *.

# MinRepro.roc
interface MinRepro
    exposes []
    imports [
        External.{ externalDerivedConst }
    ]

foo : Int * -> U16
foo = \n -> Num.toU16 (n + externalDerivedConst)

expect foo 1u8 == 1 + externalDerivedConst
# External.roc
interface External
    exposes [
        externalDerivedConst,
    ]
    imports []

externalBaseConst = 256
externalDerivedConst = 1 * externalBaseConst

Crashes with:

This expectation crashed while running:

10  expect foo 1u8 == 1 + externalDerivedConst
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The crash reported this message:

Hit an erroneous type when creating a layout for `4.IdentId(21)`
1 failed and 0 passed in 333 ms.

view this post on Zulip Ian McLerran (Apr 26 2024 at 22:20):

Going to file an issue now.

view this post on Zulip Ian McLerran (Apr 26 2024 at 23:10):

Okay, I've filed an issue here. I've provided better detail there.

view this post on Zulip Ian McLerran (Apr 26 2024 at 23:11):

Additionally, I've found that for 4 different ways to define the same constant, I get 3 distinct failure modes. Only one of four produces the expected result, which is a type mismatch error.

view this post on Zulip Ian McLerran (Apr 26 2024 at 23:13):

Depending on how I define the constant I get:
1) successful compilation, and a mathematically incorrect result.
2) Rust panic and crash.
3) Roc crash with opaque error described above.

I've also described how to reproduce all 3 of these results (and the success mode) in my issue comments.

view this post on Zulip Notification Bot (Apr 26 2024 at 23:15):

Ian McLerran has marked this topic as resolved.

view this post on Zulip Anton (Apr 27 2024 at 09:24):

Thanks for the thorough investigation @Ian McLerran :)


Last updated: Jul 06 2025 at 12:14 UTC