-
-
Notifications
You must be signed in to change notification settings - Fork 370
Description
Summary
Pattern matching on an opaque type crashes at runtime with "reached unreachable code" when:
- The opaque type is a platform module
- The variant being matched has an opaque type as payload
- The element is retrieved from a List via iteration (
List.for_each!) - The match body uses hosted effects (like
Stdout.line!)
Variants with primitive payloads (like Str) work correctly in the same context.
Error Message
panic: reached unreachable code
Reproduction Steps
cd test/fx
roc list_opaque_pattern_match_bug.rocExpected Behavior
Both tests should complete successfully and print "Done!".
Actual Behavior
Test 1 (Text variant with Str payload) works. Test 2 (Label variant with opaque NodeB payload) crashes:
Test 1: Text elements in list (should work)
Div branch
iterating child
Text branch: Hello
iterating child
Text branch: World
Test 2: Label element with opaque payload in list
panic: reached unreachable code
Minimal Reproduction
The reproduction consists of 4 files added to the fx test platform:
1. platform/NodeA.roc - A simple self-referential opaque type
NodeA := [
SourceA,
MappedA({ source : NodeA }),
].{
source : {} -> NodeA
source = |{}| SourceA
}2. platform/NodeB.roc - An opaque type containing another opaque type
import NodeA exposing [NodeA]
NodeB := [
ConstB(Str),
FoldB({ initial : Str, event : NodeA }),
].{
const : Str -> NodeB
const = |s| ConstB(s)
fold : Str, NodeA -> NodeB
fold = |initial, event| FoldB({ initial, event })
}3. platform/Element.roc - The main opaque type with List and pattern matching
import NodeB exposing [NodeB]
import Stdout
Element := [
Div(List(Element)),
Label(NodeB), # <-- Opaque payload - CRASHES when matched from list
Text(Str), # <-- Primitive payload - works fine
].{
div : List(Element) -> Element
div = |children| Div(children)
label : NodeB -> Element
label = |node| Label(node)
text : Str -> Element
text = |s| Text(s)
process! : Element => {}
process! = |elem| {
match elem {
Div(children) => {
Stdout.line!("Div branch")
List.for_each!(children, |child| {
Stdout.line!(" iterating child")
Element.process!(child) # Recursive call on list element
})
}
Label(_node) => {
Stdout.line!("Label branch") # Never reached - crashes before
}
Text(s) => {
Stdout.line!("Text branch: ${s}") # Works fine
}
}
}
}4. platform/main.roc - Updated to expose the new modules
Added to exposes: NodeA, NodeB, Element
Added to imports: import NodeA, import NodeB, import Element
5. list_opaque_pattern_match_bug.roc - Test app
app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
import pf.NodeA
import pf.NodeB
import pf.Element
main! = || {
Stdout.line!("Test 1: Text elements in list (should work)")
text_elem = Element.div([
Element.text("Hello"),
Element.text("World"),
])
Element.process!(text_elem)
Stdout.line!("")
Stdout.line!("Test 2: Label element with opaque payload in list")
event = NodeA.source({})
signal = NodeB.fold("initial", event)
label_elem = Element.div([
Element.label(signal),
])
Element.process!(label_elem)
Stdout.line!("Done!")
}Key Observations
| Scenario | Result |
|---|---|
Text(Str) in list, pattern matched |
✅ Works |
Label(NodeB) in list, pattern matched |
❌ Crashes |
Label(NodeB) NOT in list, pattern matched |
✅ Works |
| Opaque types as user modules (not platform) | ✅ Works |
| Without hosted effects in match body | ✅ Works |
Conditions Required to Trigger Bug
All of these must be true:
- Platform modules: The opaque types must be defined in the
platform/directory and exposed viamain.roc - Nested opaque types: The crashing variant contains an opaque type that itself contains another opaque type (
Element→NodeB→NodeA) - List iteration: Elements must be retrieved from a
ListusingList.for_each! - Hosted effects: The match body must call hosted effects like
Stdout.line!
If any of these conditions is removed, the bug does not occur.
Environment
- Roc: Built from source
- Platform: macOS (arm64)
- Test platform:
test/fx
Notes
- The code type-checks successfully with no errors
- The crash occurs at runtime during pattern matching
- The crash message "reached unreachable code" suggests the compiler-generated switch statement is hitting a default case that shouldn't be reachable
- The tag discriminant may be incorrect when elements are retrieved from lists in this specific configuration
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels