diff --git a/CHANGELOG.md b/CHANGELOG.md index 4596a183e..3f60ae636 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project does not currently adhere to a particular versioning scheme. ### Changed - Change tuple syntax to not require parentheses in some cases. ([#554][gh-554]) +- Improve error messages in branching statements. ([#464][gh-464]) ## [0.2.36] - 2024-07-04 @@ -369,6 +370,7 @@ and this project does not currently adhere to a particular versioning scheme. [gh-444]: https://github.com/HigherOrderCO/Bend/issues/444 [gh-451]: https://github.com/HigherOrderCO/Bend/issues/451 [gh-463]: https://github.com/HigherOrderCO/Bend/issues/463 +[gh-464]: https://github.com/HigherOrderCO/Bend/issues/464 [gh-465]: https://github.com/HigherOrderCO/Bend/issues/465 [gh-466]: https://github.com/HigherOrderCO/Bend/issues/466 [gh-467]: https://github.com/HigherOrderCO/Bend/issues/467 diff --git a/src/fun/transform/float_combinators.rs b/src/fun/transform/float_combinators.rs index 2bef4d381..30bdb0134 100644 --- a/src/fun/transform/float_combinators.rs +++ b/src/fun/transform/float_combinators.rs @@ -131,6 +131,7 @@ impl Term { /// - An application or numeric operation where all arguments are safe. /// - A safe Lambda, e.g. a nullary constructor or a lambda with safe body. /// - A Reference with a safe body. + /// /// A reference to a recursive definition (or mutually recursive) is not safe. fn is_safe(&self, ctx: &mut FloatCombinatorsCtx) -> bool { maybe_grow(|| match self { diff --git a/src/imp/parser.rs b/src/imp/parser.rs index 82f9dc92a..c757a5142 100644 --- a/src/imp/parser.rs +++ b/src/imp/parser.rs @@ -575,7 +575,9 @@ impl<'a> PyParser<'a> { indent.exit_level(); if nxt_indent != *indent { - return self.expected_indent(*indent, nxt_indent); + return self + .expected_indent(*indent, nxt_indent) + .or(self.expected_spanned("'else' or 'elif'", self.index..self.index + 1)); } let mut elifs = Vec::new(); while self.try_parse_keyword("elif") { @@ -588,7 +590,9 @@ impl<'a> PyParser<'a> { indent.exit_level(); if nxt_indent != *indent { - return self.expected_indent(*indent, nxt_indent); + return self + .expected_indent(*indent, nxt_indent) + .or(self.expected_spanned("'else' or 'elif'", self.index..self.index + 1)); } elifs.push((cond, then)); } @@ -630,7 +634,7 @@ impl<'a> PyParser<'a> { self.consume_new_line()?; indent.enter_level(); - self.consume_indent_exactly(*indent)?; + self.consume_indent_exactly(*indent).or(self.expected_spanned("'case'", self.index..self.index + 1))?; let (case, mut nxt_indent) = self.parse_match_case(indent)?; let mut arms = vec![case]; while nxt_indent == *indent { @@ -728,7 +732,9 @@ impl<'a> PyParser<'a> { let mut expected_num = 1; while should_continue { if nxt_indent != *indent { - return self.expected_indent(*indent, nxt_indent); + return self + .expected_indent(*indent, nxt_indent) + .or(self.expected_spanned("'case'", self.index..self.index + 1)); } let (case, stmt, nxt_indent_) = self.parse_switch_case(indent)?; nxt_indent = nxt_indent_; @@ -793,7 +799,7 @@ impl<'a> PyParser<'a> { self.consume_new_line()?; indent.enter_level(); - self.consume_indent_exactly(*indent)?; + self.consume_indent_exactly(*indent).or(self.expected_spanned("'case'", self.index..self.index + 1))?; let (case, mut nxt_indent) = self.parse_match_case(indent)?; let mut arms = vec![case]; while nxt_indent == *indent { @@ -824,7 +830,7 @@ impl<'a> PyParser<'a> { self.consume_new_line()?; indent.enter_level(); - self.consume_indent_exactly(*indent)?; + self.consume_indent_exactly(*indent).or(self.expected_spanned("'when'", self.index..self.index + 1))?; self.parse_keyword("when")?; let cond = self.parse_expr(true, false)?; self.skip_trivia_inline()?; @@ -837,7 +843,9 @@ impl<'a> PyParser<'a> { indent.exit_level(); if nxt_indent != *indent { - return self.expected_indent(*indent, nxt_indent); + return self + .expected_indent(*indent, nxt_indent) + .or(self.expected_spanned("'else'", self.index..self.index + 1)); } self.parse_keyword("else")?; self.skip_trivia_inline()?; diff --git a/src/lib.rs b/src/lib.rs index e5dfe9a33..2af6bf988 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ use diagnostics::{Diagnostics, DiagnosticsConfig, ERR_INDENT_SIZE}; use net::hvm_to_net::hvm_to_net; pub mod diagnostics; +// `Name` triggers this warning, but it's safe because we're not using its internal mutability. +#[allow(clippy::mutable_key_type)] pub mod fun; pub mod hvm; pub mod imp; @@ -197,6 +199,7 @@ pub fn readback_hvm_net( let mut diags = Diagnostics::default(); let net = hvm_to_net(net); let mut term = net_to_term(&net, book, labels, linear, &mut diags); + #[allow(clippy::mutable_key_type)] // Safe to allow, we know how `Name` works. let recursive_defs = book.recursive_defs(); term.expand_generated(book, &recursive_defs); term.resugar_strings(adt_encoding); diff --git a/tests/golden_tests/parse_file/bend_missing_else.bend b/tests/golden_tests/parse_file/bend_missing_else.bend new file mode 100644 index 000000000..f72aea128 --- /dev/null +++ b/tests/golden_tests/parse_file/bend_missing_else.bend @@ -0,0 +1,15 @@ +def is_even(n): + if n % 2 == 0: + return 1 + else: + return 0 + +def get_even_sum_under_if(this_number): + bend current = 0: + when current < this_number: + new_num = fork(current + is_even(current)) + + return new_num + +def main(): + return get_even_sum_under_if(1000) diff --git a/tests/golden_tests/parse_file/fold_missing_case.bend b/tests/golden_tests/parse_file/fold_missing_case.bend new file mode 100644 index 000000000..4668e45e2 --- /dev/null +++ b/tests/golden_tests/parse_file/fold_missing_case.bend @@ -0,0 +1,3 @@ +def main: + fold [] with x = 1: + diff --git a/tests/golden_tests/parse_file/if_missing_else.bend b/tests/golden_tests/parse_file/if_missing_else.bend new file mode 100644 index 000000000..2196cd091 --- /dev/null +++ b/tests/golden_tests/parse_file/if_missing_else.bend @@ -0,0 +1,4 @@ +def main: + if 1 == 1: + return "true" + diff --git a/tests/golden_tests/parse_file/match_missing_case.bend b/tests/golden_tests/parse_file/match_missing_case.bend new file mode 100644 index 000000000..b2ae4c4c2 --- /dev/null +++ b/tests/golden_tests/parse_file/match_missing_case.bend @@ -0,0 +1,3 @@ +def main: + match []: + diff --git a/tests/snapshots/compile_file__elif_no_else.bend.snap b/tests/snapshots/compile_file__elif_no_else.bend.snap index 4a120f89d..b812d1cb3 100644 --- a/tests/snapshots/compile_file__elif_no_else.bend.snap +++ b/tests/snapshots/compile_file__elif_no_else.bend.snap @@ -4,5 +4,6 @@ input_file: tests/golden_tests/compile_file/elif_no_else.bend --- Errors: In tests/golden_tests/compile_file/elif_no_else.bend : -Indentation error. Expected 2 spaces, got end-of-input. +- expected: 'else' or 'elif' +- detected: end of input  6 |   diff --git a/tests/snapshots/parse_file__bend_missing_else.bend.snap b/tests/snapshots/parse_file__bend_missing_else.bend.snap new file mode 100644 index 000000000..e00a8c9fd --- /dev/null +++ b/tests/snapshots/parse_file__bend_missing_else.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/parse_file/bend_missing_else.bend +--- +Errors: +In tests/golden_tests/parse_file/bend_missing_else.bend : +- expected: 'else' +- detected: + 14 | def main(): diff --git a/tests/snapshots/parse_file__fold_missing_case.bend.snap b/tests/snapshots/parse_file__fold_missing_case.bend.snap new file mode 100644 index 000000000..81926ecc5 --- /dev/null +++ b/tests/snapshots/parse_file__fold_missing_case.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/parse_file/fold_missing_case.bend +--- +Errors: +In tests/golden_tests/parse_file/fold_missing_case.bend : +- expected: 'case' +- detected: end of input + 4 |   diff --git a/tests/snapshots/parse_file__if_missing_else.bend.snap b/tests/snapshots/parse_file__if_missing_else.bend.snap new file mode 100644 index 000000000..8756e211d --- /dev/null +++ b/tests/snapshots/parse_file__if_missing_else.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/parse_file/if_missing_else.bend +--- +Errors: +In tests/golden_tests/parse_file/if_missing_else.bend : +- expected: 'else' or 'elif' +- detected: end of input + 5 |   diff --git a/tests/snapshots/parse_file__match_missing_case.bend.snap b/tests/snapshots/parse_file__match_missing_case.bend.snap new file mode 100644 index 000000000..5c7ad9e29 --- /dev/null +++ b/tests/snapshots/parse_file__match_missing_case.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/parse_file/match_missing_case.bend +--- +Errors: +In tests/golden_tests/parse_file/match_missing_case.bend : +- expected: 'case' +- detected: end of input + 4 |