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 }
Can you make put it in a single Interface module as a minimal reproduction and log an Issue please?
I'm not sure what the issue is specifically, sorry.
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
@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 } }
I suspect it's related to importing symbols or a naming issue. Maybe we need two modules to reproduce?
I'd start with the code you have that produces it, and remove things one at a time.
Any preference on whether I work backwards from the rust panic or Roc crash?
I dont have any preference, though they might be a similar issue. Maybe start with the first?
I can probably tinker with it later, just busy with some other things rn.
That was my instinct that they were related.
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.
^ dedicated branch of repo - linked to function location
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
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.
addHours
variations:# 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
variations: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
Updated git link with the 3 expects above
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.
Going to file an issue now.
Okay, I've filed an issue here. I've provided better detail there.
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.
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.
Ian McLerran has marked this topic as resolved.
Thanks for the thorough investigation @Ian McLerran :)
Last updated: Jul 06 2025 at 12:14 UTC