From 676d4ca73260783c96f21f7ab95bfde95e0d8774 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Wed, 24 Jul 2024 17:18:05 -0300 Subject: [PATCH 1/5] Improve error messages of branching cases --- src/imp/parser.rs | 22 +++++++++++++------ .../parse_file/bend_missing_else.bend | 15 +++++++++++++ .../parse_file/fold_missing_case.bend | 3 +++ .../parse_file/if_missing_else.bend | 4 ++++ .../parse_file/match_missing_case.bend | 3 +++ .../compile_file__elif_no_else.bend.snap | 3 ++- .../parse_file__bend_missing_else.bend.snap | 9 ++++++++ .../parse_file__fold_missing_case.bend.snap | 9 ++++++++ .../parse_file__if_missing_else.bend.snap | 9 ++++++++ .../parse_file__match_missing_case.bend.snap | 9 ++++++++ 10 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 tests/golden_tests/parse_file/bend_missing_else.bend create mode 100644 tests/golden_tests/parse_file/fold_missing_case.bend create mode 100644 tests/golden_tests/parse_file/if_missing_else.bend create mode 100644 tests/golden_tests/parse_file/match_missing_case.bend create mode 100644 tests/snapshots/parse_file__bend_missing_else.bend.snap create mode 100644 tests/snapshots/parse_file__fold_missing_case.bend.snap create mode 100644 tests/snapshots/parse_file__if_missing_else.bend.snap create mode 100644 tests/snapshots/parse_file__match_missing_case.bend.snap diff --git a/src/imp/parser.rs b/src/imp/parser.rs index 82f9dc92a..79d5871be 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/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..4d243bf36 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..02a51b3a2 --- /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..6722c5bba --- /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..f812d550a --- /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..ed631ebed --- /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 |   From f53f6a697387379b37a8f53fadc25fb59a2af98b Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Thu, 25 Jul 2024 10:26:31 -0300 Subject: [PATCH 2/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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 From 83a536e5eb032b883f625b16b25c58bb9a5233c1 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Thu, 25 Jul 2024 10:56:09 -0300 Subject: [PATCH 3/5] False positive mutable key type --- src/fun/transform/float_combinators.rs | 1 + src/lib.rs | 2 ++ 2 files changed, 3 insertions(+) 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/lib.rs b/src/lib.rs index e5dfe9a33..f28e219a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use diagnostics::{Diagnostics, DiagnosticsConfig, ERR_INDENT_SIZE}; use net::hvm_to_net::hvm_to_net; pub mod diagnostics; +#[allow(clippy::mutable_key_type)] pub mod fun; pub mod hvm; pub mod imp; @@ -197,6 +198,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)] let recursive_defs = book.recursive_defs(); term.expand_generated(book, &recursive_defs); term.resugar_strings(adt_encoding); From 360b0446e6ccc9ef16551f70fce0bc38963d3dc6 Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Thu, 25 Jul 2024 17:06:33 -0300 Subject: [PATCH 4/5] Update error messages and test snapshots --- src/imp/parser.rs | 14 +++++++------- .../snapshots/compile_file__elif_no_else.bend.snap | 2 +- .../parse_file__bend_missing_else.bend.snap | 2 +- .../parse_file__fold_missing_case.bend.snap | 2 +- .../parse_file__if_missing_else.bend.snap | 2 +- .../parse_file__match_missing_case.bend.snap | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/imp/parser.rs b/src/imp/parser.rs index 79d5871be..c757a5142 100644 --- a/src/imp/parser.rs +++ b/src/imp/parser.rs @@ -577,7 +577,7 @@ impl<'a> PyParser<'a> { if nxt_indent != *indent { return self .expected_indent(*indent, nxt_indent) - .or(self.expected_spanned("else or elif", self.index..self.index + 1)); + .or(self.expected_spanned("'else' or 'elif'", self.index..self.index + 1)); } let mut elifs = Vec::new(); while self.try_parse_keyword("elif") { @@ -592,7 +592,7 @@ impl<'a> PyParser<'a> { if nxt_indent != *indent { return self .expected_indent(*indent, nxt_indent) - .or(self.expected_spanned("else or elif", self.index..self.index + 1)); + .or(self.expected_spanned("'else' or 'elif'", self.index..self.index + 1)); } elifs.push((cond, then)); } @@ -634,7 +634,7 @@ impl<'a> PyParser<'a> { self.consume_new_line()?; indent.enter_level(); - self.consume_indent_exactly(*indent).or(self.expected_spanned("case", self.index..self.index + 1))?; + 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 { @@ -734,7 +734,7 @@ impl<'a> PyParser<'a> { if nxt_indent != *indent { return self .expected_indent(*indent, nxt_indent) - .or(self.expected_spanned("case", self.index..self.index + 1)); + .or(self.expected_spanned("'case'", self.index..self.index + 1)); } let (case, stmt, nxt_indent_) = self.parse_switch_case(indent)?; nxt_indent = nxt_indent_; @@ -799,7 +799,7 @@ impl<'a> PyParser<'a> { self.consume_new_line()?; indent.enter_level(); - self.consume_indent_exactly(*indent).or(self.expected_spanned("case", self.index..self.index + 1))?; + 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 { @@ -830,7 +830,7 @@ impl<'a> PyParser<'a> { self.consume_new_line()?; indent.enter_level(); - self.consume_indent_exactly(*indent).or(self.expected_spanned("when", self.index..self.index + 1))?; + 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()?; @@ -845,7 +845,7 @@ impl<'a> PyParser<'a> { if nxt_indent != *indent { return self .expected_indent(*indent, nxt_indent) - .or(self.expected_spanned("else", self.index..self.index + 1)); + .or(self.expected_spanned("'else'", self.index..self.index + 1)); } self.parse_keyword("else")?; self.skip_trivia_inline()?; diff --git a/tests/snapshots/compile_file__elif_no_else.bend.snap b/tests/snapshots/compile_file__elif_no_else.bend.snap index 4d243bf36..b812d1cb3 100644 --- a/tests/snapshots/compile_file__elif_no_else.bend.snap +++ b/tests/snapshots/compile_file__elif_no_else.bend.snap @@ -4,6 +4,6 @@ input_file: tests/golden_tests/compile_file/elif_no_else.bend --- Errors: In tests/golden_tests/compile_file/elif_no_else.bend : -- expected: else or elif +- 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 index 02a51b3a2..e00a8c9fd 100644 --- a/tests/snapshots/parse_file__bend_missing_else.bend.snap +++ b/tests/snapshots/parse_file__bend_missing_else.bend.snap @@ -4,6 +4,6 @@ input_file: tests/golden_tests/parse_file/bend_missing_else.bend --- Errors: In tests/golden_tests/parse_file/bend_missing_else.bend : -- expected: else +- 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 index 6722c5bba..81926ecc5 100644 --- a/tests/snapshots/parse_file__fold_missing_case.bend.snap +++ b/tests/snapshots/parse_file__fold_missing_case.bend.snap @@ -4,6 +4,6 @@ input_file: tests/golden_tests/parse_file/fold_missing_case.bend --- Errors: In tests/golden_tests/parse_file/fold_missing_case.bend : -- expected: case +- 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 index f812d550a..8756e211d 100644 --- a/tests/snapshots/parse_file__if_missing_else.bend.snap +++ b/tests/snapshots/parse_file__if_missing_else.bend.snap @@ -4,6 +4,6 @@ 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 +- 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 index ed631ebed..5c7ad9e29 100644 --- a/tests/snapshots/parse_file__match_missing_case.bend.snap +++ b/tests/snapshots/parse_file__match_missing_case.bend.snap @@ -4,6 +4,6 @@ input_file: tests/golden_tests/parse_file/match_missing_case.bend --- Errors: In tests/golden_tests/parse_file/match_missing_case.bend : -- expected: case +- expected: 'case' - detected: end of input  4 |   From 477f143077971b9deb2d9b53da6146d19314c4eb Mon Sep 17 00:00:00 2001 From: imaqtkatt Date: Thu, 25 Jul 2024 17:28:55 -0300 Subject: [PATCH 5/5] Add comments about the clippy allow --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f28e219a9..2af6bf988 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ 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; @@ -198,7 +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)] + #[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);