Open jeffnm opened 6 months ago
DISREGARD FOR BETTER ANALYSIS IN BELOW COMMENT
I also ran into this issue while doing library dev in Roc quite a few times. This is the simplest I can get the above example down to:
app "panic"
packages {
cli: "https://github.com/roc-lang/basic-cli/releases/download/0.9.0/oKWkaruh2zXxin_xfsYsCJobH1tO8_JvNkFzDwwzNUQ.tar.br"
}
imports [
cli.Stdout,
cli.Task.{ Task },
]
provides [main] to cli
# This type annotation doesn't change whether the compiler panics
main : Task {} I32
main =
when nextColor Red is
Red -> Stdout.line "The next color is red."
# This also fixes the issue by making Blue a valid variant
# Blue -> Stdout.line "The next color is blue."
# This type annotation fixes the issue, in that the compiler knows what's wrong
# nextColor : [Red] -> [Red]
nextColor = \color ->
when color is
Red -> Blue
It seems that when we don't tell the compiler that Red
is the only option that nextColor
can return via the type annotation, then it assumes that Blue
must be a valid output variant. This constraint is imposed in the "Closed Union" matching in main
, and adding a blue arm to the when
clause also fixes the issue.
An easy patch would be to throw an actual compilation error that links this GitHub issue instead of panicking, but then the issue remains.
Better would be to somehow reconcile unannotated (e.g. inferred) function return types with exhaustive when
matches. My expected user experience would assume correctness at nextColor
(being an atomic code unit), whose inferred type is [Red] -> [Blue]
and say that the matched [Red]
variant doesn't match nextColor
s [Blue]
. I expect that may collide with the expected Roc user experience, so I'd like to hear the team's opinion before we move forward with fixing this.
If the above proposed solution is good for the team, then I nominate myself to try fixing this issue. I don't have experience making changes to the Roc compiler, so if you'd rather leave this to a more experienced contributor, I take no offense.
DISREGARD FOR BETTER ANALYSIS IN BELOW COMMENT
I also ran into this issue while doing library dev in Roc quite a few times. This is the simplest I can get the above example down to:
app "panic"
packages {
cli: "https://github.com/roc-lang/basic-cli/releases/download/0.9.0/oKWkaruh2zXxin_xfsYsCJobH1tO8_JvNkFzDwwzNUQ.tar.br"
}
imports [
cli.Stdout,
cli.Task.{ Task },
]
provides [main] to cli
# This type annotation doesn't change whether the compiler panics
main : Task {} I32
main =
# This fixes the issue by (I think) constraining what the `when` expects
# color : [Blue]
color = Blue
when color is
Red -> Stdout.line "The color is red."
# This fixes the issue by making Blue a valid variant,
# though we still get a compiler error since `Red` isn't expected
# Blue -> Stdout.line "The next color is blue."
It seems that when we don't tell the compiler what variants color
can be, it gets confused between the constraints that:
1) color
is inferred to at least be [Blue]a
.
2) The when
exhaustively matches only [Red]
.
While finding Red
's constructor (during reification?), it assumes it will be present, but only sees [Blue]
, so it panics.
The two possible fixes outline the two possible compiler errors we could add here:
color
lets the compiler know that the when
's [Red]
mismatches with color
's [Blue]
. If we wanted to throw the compiler error here, we'd need to prioritize the when
clause's exhaustive variants, and if we can't find a variant, we throw an error at the source of the issue, a.k.a. the definition of color
in this example. This moves the error away from what seems to be the real location, so I prefer the second compiler error.when
clause with either a Blue
case or a catch-all (e.g. _other ->
) means the when
is matching over an open union [Red]*
, and won't fail when it sees blue. I suggest that when we fail to find a constructor for a when
enum variant, we show an error saying:
when
clause here exhaustively checks the type [Red]
, but the value it matches over is at least [Blue]
. Consider adding either cases for the missing variants or a catch-all arm _ -> ...
to handle the other variants."If the second proposed solution is good for the team, then I nominate myself to try fixing this issue. I don't have experience making changes to the Roc compiler, so if you'd rather leave this to a more experienced contributor, I take no offense.
I think the second solution is ideal. The fix for this particular instance should be pretty straightforward; in particular, I think it should be sufficient to remove the assertion
and gracefully handle the missing constructor.
In general I believe the modeling of this could be better (#4440), but that is probably a story for another day. If it would be helpful any time during your implementation, feel free to message on Zulip!
Thanks for the great analysis and volunteering!
I got the same bug with the following 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
table = Dict.fromList [
("AUG", Methionine),
("UAA", Stop),
("UUC", Phenylalanine),
]
translate = \codons ->
codons
|> List.walkUntil [] \protein, codon ->
when table |> Dict.get codon is
Ok Stop -> Break protein
Ok aminoAcid -> Continue (protein |> List.append aminoAcid)
Err NotFound -> Break []
main =
translate ["AUG", "AUG", "UUC", "UAA", "UUC", "AUG"]
|> Inspect.toStr
|> Stdout.line!
The error message is:
thread '<unnamed>' panicked at crates/compiler/can/src/exhaustive.rs:211:41:
constructor must be known in the indexable type if we are exhautiveness checking
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
If you replace NotFound
with KeyNotFound
in my code, then everything works fine. But it took me an hour to figure it out: instead of a panic, there should have been a clear error message.
I've found a compiler panic with roc_nightly-linux_x86_64-2024-03-16-49862da
The following code reproduces the issue, along with two comments with changes that fix the panic and get back to normal compiler errors for bad code like this.
My understanding of what's happening is that the second pattern match
when command is
has a pattern that can't exist in the definition ofcommand
and there are a bunch of patterns in the definition ofcommand
that aren't covered in the pattern matching. The combination of both issues is what is breaking the compiler. Fixing either issue (via the commented changes) allows the compiler to give useful feedback again.I'd hope this isn't a normal situation. I got into this situation by copying and pasting code and only modifying part of it. A more helpful error message here would certainly help newcomers who make the same mistake.
Let me know if you need any more information.