I ran into several issues, which I narrowed down to the fact that I'm trying to use a record with two default fields.
I'm defining a little dot-like DSL to build graphs (for the dot-dsl
exercism exercise). Here's the code:
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}
import pf.Stdout
Color : [Black, Red, Green, Blue]
Style : [Dotted, Solid]
DslCommand : [AddEdge Str Str { color : Color, style : Style }]
edge : Str, Str, { color ? Color, style ? Style } -> DslCommand
edge = \id1, id2, { color ? Black, style ? Solid } ->
AddEdge id1 id2 { color, style }
main =
result1 = edge "a" "b" {}
result1 |> Inspect.toStr |> Stdout.line!
result2 = edge "a" "b" { color: Blue }
result2 |> Inspect.toStr |> Stdout.line!
This causes an LLVM error (with a pretty scary :scream: emoji):
Call parameter type does not match function signature!
{} zeroinitializer
i8 call fastcc void @"#UserApp_edge_7988d89080438f51df37e0664fee86ae858164dcb95eaeb555d2849513259182"(ptr %const_str_store, ptr %const_str_store1, {} zeroinitializer, ptr %result_value), !dbg !1889
Function "#UserApp_main_ca98df76e744faeef21fb76918295997c9c1b552a2e623d6fc162a11de8fae" failed LLVM verification in NON-OPTIMIZED build. Its content was:
define internal fastcc { { %str.RocStr, {} }, {} } @"#UserApp_main_ca98df76e744faeef21fb76918295997c9c1b552a2e623d6fc162a11de8fae"() !dbg !1888 {
entry:
%result_value2 = alloca %str.RocStr, align 8
%result_value = alloca { %str.RocStr, %str.RocStr, { i8, i1 } }, align 8
%const_str_store1 = alloca %str.RocStr, align 8
%const_str_store = alloca %str.RocStr, align 8
store %str.RocStr { ptr inttoptr (i64 97 to ptr), i64 0, i64 -9151314442816847872 }, ptr %const_str_store, align 8, !dbg !1889
store %str.RocStr { ptr inttoptr (i64 98 to ptr), i64 0, i64 -9151314442816847872 }, ptr %const_str_store1, align 8, !dbg !1889
call fastcc void @"#UserApp_edge_7988d89080438f51df37e0664fee86ae858164dcb95eaeb555d2849513259182"(ptr %const_str_store, ptr %const_str_store1, {} zeroinitializer, ptr %result_value), !dbg !1889
call fastcc void @"#Attr_#dec_2"(ptr %const_str_store), !dbg !1889
call fastcc void @"#Attr_#dec_2"(ptr %const_str_store1), !dbg !1889
call fastcc void @Inspect_toStr_5fea3a382f6b6c4a2af77ea4365b5abbdda8b93d1f0b9b895dc2a48489fb2(ptr %result_value, ptr %result_value2), !dbg !1889
%call = call fastcc { %str.RocStr, {} } @Stdout_line_172247c57cf29182b738e1647bff697cbe655ff06cf0aee2ca31b6b397327385(ptr %result_value2), !dbg !1889
%call3 = call fastcc { { %str.RocStr, {} }, {} } @Task_await_47379a71f6fa75b326383965b5622141f57df12cc22d7140acdf38f0ac8dbc6d({ %str.RocStr, {} } %call, {} zeroinitializer), !dbg !1889
ret { { %str.RocStr, {} }, {} } %call3, !dbg !1889
}
thread 'main' panicked at crates/compiler/gen_llvm/src/llvm/build.rs:5810:21:
π± LLVM errors when defining function "#UserApp_main_ca98df76e744faeef21fb76918295997c9c1b552a2e623d6fc162a11de8fae"; I wrote the full LLVM IR to "bug-edge.ll"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
If you remove the two lines with result2
(i.e., the last two lines), then the code runs fine and outputs this:
(AddEdge "a" "b" {color: Black, style: Solid})
And similarly, if you keep the two lines with result2
and remove the two lines with result1
, then the code also runs fine and outputs this:
(AddEdge "a" "b" {color: Blue, style: Solid})
Next, I tried to keep result1
and result2
, and add result3
like this:
result3 = edge "a" "b" { color: Blue, style: Dotted }
result3 |> Inspect.toStr |> Stdout.line!
The program still panics, but with a different error message:
thread 'main' panicked at crates/compiler/gen_llvm/src/llvm/build.rs:5748:19:
Error in alias analysis: error in module ModName("UserApp"), function definition FuncName("\x11\x00\x00\x00\x00\x00\x00\x00\'\xae\xa4\xef\x8b\xd4\xa4\x87"), definition of value binding ValueId(5): expected type '((heap_cell,), (heap_cell,), ((), ()))', found type '((heap_cell,), (heap_cell,), ())'
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
And if I try to add the following result4
:
result4 = edge "a" "b" { style: Dotted }
result4 |> Inspect.toStr |> Stdout.line!
The code no longer compiles. I get the following error instead:
ββ TYPE MISMATCH in bug-edge.roc βββββββββββββββββββββββββββββββββββββββββββββββ
This 3rd argument to edge has an unexpected type:
26β result4 = edge "a" "b" { style: Dotted }
^^^^^^^^^^^^^^^^^
The argument is a record of type:
{ style : [β¦] }
But edge needs its 3rd argument to be:
{
color : [
Black,
Blue,
Green,
Red,
],
style : [β¦],
}
Tip: Looks like the color field is missing.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Interestingly, if I move the definition of the edge
function to a separate module, then everything works fine:
# Edge.roc
module [edge]
Color : [Black, Red, Green, Blue]
Style : [Dotted, Solid]
DslCommand : [AddEdge Str Str { color : Color, style : Style }]
edge : Str, Str, { color ? Color, style ? Style } -> DslCommand
edge = \id1, id2, { color ? Black, style ? Solid } ->
AddEdge id1 id2 { color, style }
and:
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}
import pf.Stdout
import Edge exposing [edge]
main =
result1 = edge "a" "b" {}
result1 |> Inspect.toStr |> Stdout.line!
result2 = edge "a" "b" { color: Blue }
result2 |> Inspect.toStr |> Stdout.line!
result3 = edge "a" "b" { color: Blue, style: Dotted }
result3 |> Inspect.toStr |> Stdout.line!
result4 = edge "a" "b" { style: Dotted }
result4 |> Inspect.toStr |> Stdout.line!
This program outputs:
(AddEdge "a" "b" {color: Black, style: Solid})
(AddEdge "a" "b" {color: Blue, style: Solid})
(AddEdge "a" "b" {color: Blue, style: Dotted})
(AddEdge "a" "b" {color: Black, style: Dotted})
Would you like me to file a GitHub issue with all this detail?
I think we have a funky bug around the default/optional record thing. Maybe that is the issue here. Can you try without the ?
?
I'm guessing it's something to do with specialization of the top level edge
. Maybe when it's in another module it can have multiple specialisations.
Sure, I'll try now
Like this maybe https://github.com/roc-lang/roc/issues/6423
Works like a charm.
However, I had to fully specify the records in result1
and result2
:
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}
import pf.Stdout
Color : [Black, Red, Green, Blue]
Style : [Dotted, Solid]
DslCommand : [AddEdge Str Str { color : Color, style : Style }]
edge : Str, Str, { color : Color, style : Style } -> DslCommand
edge = \id1, id2, { color, style } ->
AddEdge id1 id2 { color, style }
main =
result1 = edge "a" "b" { color: Black, style: Solid }
result1 |> Inspect.toStr |> Stdout.line!
result2 = edge "a" "b" { color: Blue, style: Solid }
result2 |> Inspect.toStr |> Stdout.line!
Yeah, I'm pretty convinced it's that bug. Would you mind adding a comment in there with your workaround.. using a separate module?
Yes, I tested issue #6423: putting the definition of the add
function in a separate module works around the issue. Here's my comment
Maybe that will help someone fix the bug. No idea why imported functions can be specialized multiple tiles but module local functions can't
But really useful workaround
Last updated: Jul 06 2025 at 12:14 UTC