mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Improve non-exhaustive typechecking errors
We will replace this with proper exhaustiveness checking when we have that in the typechecking phase, but for now this should do.
This commit is contained in:
parent
87245def0a
commit
9a341e3d75
3 changed files with 99 additions and 60 deletions
|
@ -708,7 +708,9 @@ pub fn constrain_expr(
|
||||||
|
|
||||||
// After solving the condition variable with what's expected from the branch patterns,
|
// After solving the condition variable with what's expected from the branch patterns,
|
||||||
// check it against the condition expression.
|
// check it against the condition expression.
|
||||||
// This is basically exhaustiveness checking, but doesn't check for redundancy.
|
// TODO: when we have exhaustiveness checking during the typechecking phase, perform
|
||||||
|
// exhaustiveness checking when this expectation fails. That will produce better error
|
||||||
|
// messages.
|
||||||
let cond_constraint = constrain_expr(
|
let cond_constraint = constrain_expr(
|
||||||
constraints,
|
constraints,
|
||||||
env,
|
env,
|
||||||
|
|
|
@ -1151,34 +1151,50 @@ fn to_expr_report<'b>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Reason::WhenBranches => report_mismatch(
|
Reason::WhenBranches => {
|
||||||
alloc,
|
let snippet = alloc.region_with_subregion(
|
||||||
lines,
|
lines.convert_region(region),
|
||||||
filename,
|
lines.convert_region(expr_region),
|
||||||
&category,
|
);
|
||||||
found,
|
|
||||||
expected_type,
|
let this_is = alloc.concat([
|
||||||
// TODO: these should be flipped but `report_mismatch` takes the wrong arguments to
|
|
||||||
// `region_with_subregion`
|
|
||||||
expr_region,
|
|
||||||
Some(region),
|
|
||||||
alloc.concat([
|
|
||||||
alloc.reflow("The branches of this "),
|
|
||||||
alloc.keyword("when"),
|
|
||||||
alloc.reflow(" expression don't match the condition:"),
|
|
||||||
]),
|
|
||||||
alloc.concat([
|
|
||||||
alloc.reflow("The "),
|
alloc.reflow("The "),
|
||||||
alloc.keyword("when"),
|
alloc.keyword("when"),
|
||||||
alloc.reflow(" condition is"),
|
alloc.reflow(" condition is"),
|
||||||
]),
|
]);
|
||||||
alloc.reflow("But the branch patterns have type:"),
|
|
||||||
Some(alloc.concat([
|
let wanted = alloc.reflow("But the branch patterns have type:");
|
||||||
|
let details = Some(alloc.concat([
|
||||||
alloc.reflow("The branches must be cases of the "),
|
alloc.reflow("The branches must be cases of the "),
|
||||||
alloc.keyword("when"),
|
alloc.keyword("when"),
|
||||||
alloc.reflow(" condition's type!"),
|
alloc.reflow(" condition's type!"),
|
||||||
])),
|
]));
|
||||||
),
|
|
||||||
|
let lines = [
|
||||||
|
alloc.concat([
|
||||||
|
alloc.reflow("The branches of this "),
|
||||||
|
alloc.keyword("when"),
|
||||||
|
alloc.reflow(" expression don't match the condition:"),
|
||||||
|
]),
|
||||||
|
snippet,
|
||||||
|
type_comparison(
|
||||||
|
alloc,
|
||||||
|
found,
|
||||||
|
expected_type,
|
||||||
|
ExpectationContext::WhenCondition,
|
||||||
|
add_category(alloc, this_is, &category),
|
||||||
|
wanted,
|
||||||
|
details,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Report {
|
||||||
|
title: "TYPE MISMATCH".to_string(),
|
||||||
|
filename,
|
||||||
|
doc: alloc.stack(lines),
|
||||||
|
severity: Severity::RuntimeError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reason::LowLevelOpArg { op, arg_index } => {
|
Reason::LowLevelOpArg { op, arg_index } => {
|
||||||
panic!(
|
panic!(
|
||||||
|
@ -1253,7 +1269,10 @@ fn count_arguments(tipe: &ErrorType) -> usize {
|
||||||
enum ExpectationContext<'a> {
|
enum ExpectationContext<'a> {
|
||||||
/// An expected type was discovered from a type annotation. Corresponds to
|
/// An expected type was discovered from a type annotation. Corresponds to
|
||||||
/// [`Expected::FromAnnotation`](Expected::FromAnnotation).
|
/// [`Expected::FromAnnotation`](Expected::FromAnnotation).
|
||||||
Annotation { on: RocDocBuilder<'a> },
|
Annotation {
|
||||||
|
on: RocDocBuilder<'a>,
|
||||||
|
},
|
||||||
|
WhenCondition,
|
||||||
/// When we don't know the context, or it's not relevant.
|
/// When we don't know the context, or it's not relevant.
|
||||||
Arbitrary,
|
Arbitrary,
|
||||||
}
|
}
|
||||||
|
@ -1262,6 +1281,7 @@ impl<'a> std::fmt::Debug for ExpectationContext<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ExpectationContext::Annotation { .. } => f.write_str("Annotation"),
|
ExpectationContext::Annotation { .. } => f.write_str("Annotation"),
|
||||||
|
ExpectationContext::WhenCondition => f.write_str("WhenCondition"),
|
||||||
ExpectationContext::Arbitrary => f.write_str("Arbitrary"),
|
ExpectationContext::Arbitrary => f.write_str("Arbitrary"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2679,22 +2699,24 @@ fn diff_tag_union<'b>(
|
||||||
let all_fields_shared = left.peek().is_none() && right.peek().is_none();
|
let all_fields_shared = left.peek().is_none() && right.peek().is_none();
|
||||||
|
|
||||||
let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) {
|
let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) {
|
||||||
(true, true) => match left.peek() {
|
(true, true) => match (left.peek(), right.peek()) {
|
||||||
Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
|
(Some((f, _, _, _)), Some(_)) => Status::Different(vec![Problem::TagTypo(
|
||||||
f.clone(),
|
f.clone(),
|
||||||
fields2.keys().cloned().collect(),
|
fields2.keys().cloned().collect(),
|
||||||
)]),
|
)]),
|
||||||
None => {
|
(Some(_), None) => {
|
||||||
if right.peek().is_none() {
|
let status =
|
||||||
Status::Similar
|
Status::Different(vec![Problem::TagsMissing(left.map(|v| v.0).collect())]);
|
||||||
} else {
|
left = left_keys.iter().map(to_unknown_docs).peekable();
|
||||||
let result =
|
status
|
||||||
Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]);
|
|
||||||
// we just used the values in `right`. in
|
|
||||||
right = right_keys.iter().map(to_unknown_docs).peekable();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
let status =
|
||||||
|
Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]);
|
||||||
|
right = right_keys.iter().map(to_unknown_docs).peekable();
|
||||||
|
status
|
||||||
|
}
|
||||||
|
(None, None) => Status::Similar,
|
||||||
},
|
},
|
||||||
(false, true) => match left.peek() {
|
(false, true) => match left.peek() {
|
||||||
Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
|
Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||||
|
@ -3294,6 +3316,33 @@ fn type_problem_to_pretty<'b>(
|
||||||
alloc.reflow("."),
|
alloc.reflow("."),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
(TagsMissing(missing), ExpectationContext::WhenCondition) => match missing.split_last() {
|
||||||
|
None => alloc.nil(),
|
||||||
|
Some(split) => {
|
||||||
|
let missing_tags = match split {
|
||||||
|
(f1, []) => alloc.tag_name(f1.clone()).append(alloc.reflow(" tag.")),
|
||||||
|
(last, init) => alloc
|
||||||
|
.intersperse(init.iter().map(|v| alloc.tag_name(v.clone())), ", ")
|
||||||
|
.append(alloc.reflow(" and "))
|
||||||
|
.append(alloc.tag_name(last.clone()))
|
||||||
|
.append(alloc.reflow(" tags.")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tip1 = alloc
|
||||||
|
.tip()
|
||||||
|
.append(alloc.reflow("Looks like the branches are missing coverage of the "))
|
||||||
|
.append(missing_tags);
|
||||||
|
|
||||||
|
let tip2 = alloc
|
||||||
|
.tip()
|
||||||
|
.append(alloc.reflow("Maybe you need to add a catch-all branch, like "))
|
||||||
|
.append(alloc.keyword("_"))
|
||||||
|
.append(alloc.reflow("?"));
|
||||||
|
|
||||||
|
alloc.stack([tip1, tip2])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
(TagsMissing(missing), _) => match missing.split_last() {
|
(TagsMissing(missing), _) => match missing.split_last() {
|
||||||
None => alloc.nil(),
|
None => alloc.nil(),
|
||||||
Some((f1, [])) => {
|
Some((f1, [])) => {
|
||||||
|
|
|
@ -2740,11 +2740,10 @@ mod test_reporting {
|
||||||
|
|
||||||
[ Left a ]
|
[ Left a ]
|
||||||
|
|
||||||
Tip: Seems like a tag typo. Maybe `Right` should be `Left`?
|
Tip: Looks like a closed tag union does not have the `Right` tag.
|
||||||
|
|
||||||
Tip: Can more type annotations be added? Type annotations always help
|
Tip: Closed tag unions can't grow, because that might change the size
|
||||||
me give more specific messages, and I think they could help a lot in
|
in memory. Can you use an open tag union?
|
||||||
this case
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2790,7 +2789,6 @@ mod test_reporting {
|
||||||
Red -> 3
|
Red -> 3
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// TODO(2903): improve tag typo quality
|
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
@ -2810,11 +2808,9 @@ mod test_reporting {
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
The branches must be cases of the `when` condition's type!
|
||||||
|
|
||||||
Tip: Seems like a tag typo. Maybe `Green` should be `Red`?
|
Tip: Looks like the branches are missing coverage of the `Green` tag.
|
||||||
|
|
||||||
Tip: Can more type annotations be added? Type annotations always help
|
Tip: Maybe you need to add a catch-all branch, like `_`?
|
||||||
me give more specific messages, and I think they could help a lot in
|
|
||||||
this case
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2833,7 +2829,6 @@ mod test_reporting {
|
||||||
Green -> 1
|
Green -> 1
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// TODO(2903): improve tag typo quality
|
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
@ -2854,11 +2849,9 @@ mod test_reporting {
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
The branches must be cases of the `when` condition's type!
|
||||||
|
|
||||||
Tip: Seems like a tag typo. Maybe `Blue` should be `Red`?
|
Tip: Looks like the branches are missing coverage of the `Blue` tag.
|
||||||
|
|
||||||
Tip: Can more type annotations be added? Type annotations always help
|
Tip: Maybe you need to add a catch-all branch, like `_`?
|
||||||
me give more specific messages, and I think they could help a lot in
|
|
||||||
this case
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2877,7 +2870,6 @@ mod test_reporting {
|
||||||
NotAsked -> 3
|
NotAsked -> 3
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// TODO(2903): improve tag typo quality
|
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
@ -2897,11 +2889,10 @@ mod test_reporting {
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
The branches must be cases of the `when` condition's type!
|
||||||
|
|
||||||
Tip: Seems like a tag typo. Maybe `Success` should be `NotAsked`?
|
Tip: Looks like the branches are missing coverage of the
|
||||||
|
`Success`, `Failure` and `Loading` tags.
|
||||||
|
|
||||||
Tip: Can more type annotations be added? Type annotations always help
|
Tip: Maybe you need to add a catch-all branch, like `_`?
|
||||||
me give more specific messages, and I think they could help a lot in
|
|
||||||
this case
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -8714,7 +8705,6 @@ I need all branches in an `if` to have the same type!
|
||||||
$F B -> ""
|
$F B -> ""
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// TODO(2903): improve tag typo quality
|
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
@ -8735,11 +8725,9 @@ I need all branches in an `if` to have the same type!
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
The branches must be cases of the `when` condition's type!
|
||||||
|
|
||||||
Tip: Seems like a tag typo. Maybe `C` should be `A`?
|
Tip: Looks like the branches are missing coverage of the `C` tag.
|
||||||
|
|
||||||
Tip: Can more type annotations be added? Type annotations always help
|
Tip: Maybe you need to add a catch-all branch, like `_`?
|
||||||
me give more specific messages, and I think they could help a lot in
|
|
||||||
this case
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue