From 1b8e94fe322fa012cf2411d9bddce5a69f85a41f Mon Sep 17 00:00:00 2001 From: rvcas Date: Sat, 15 Jul 2023 20:50:02 -0400 Subject: [PATCH] feat: expect boolean sugar --- CHANGELOG.md | 4 ++ crates/aiken-lang/src/format.rs | 32 +++++---- .../aiken-lang/src/parser/expr/assignment.rs | 65 +++++++++++++------ .../expr/snapshots/expect_bool_sugar.snap | 35 ++++++++++ .../src/parser/pattern/constructor.rs | 15 +++++ .../pattern/snapshots/constructor_basic.snap | 14 ++++ .../snapshots/constructor_module_select.snap | 16 +++++ crates/aiken-lang/src/parser/utils.rs | 21 ++++++ crates/aiken-lang/src/tests/check.rs | 27 ++++++++ 9 files changed, 198 insertions(+), 31 deletions(-) create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap create mode 100644 crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap create mode 100644 crates/aiken-lang/src/parser/pattern/snapshots/constructor_module_select.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5fbc1d..2ec48fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## v1.0.13-alpha - unreleased +### Added + +- **aiken-lang**: `expect foo == bar` desugars into `expect True = foo == bar` + ### Fixed - **aiken-lang**: fail, todo, and trace had issues with sequences and expressions diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 2b8ea754..db1159fe 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -643,21 +643,31 @@ impl<'comments> Formatter<'comments> { self.pop_empty_lines(pattern.location().end); let keyword = match kind { - AssignmentKind::Let => "let ", - AssignmentKind::Expect => "expect ", + AssignmentKind::Let => "let", + AssignmentKind::Expect => "expect", }; - let pattern = self.pattern(pattern); + match pattern { + UntypedPattern::Constructor { + name, module: None, .. + } if name == "True" && annotation.is_none() && kind.is_expect() => { + keyword.to_doc().append(self.case_clause_value(value)) + } + _ => { + let pattern = self.pattern(pattern); - let annotation = annotation - .as_ref() - .map(|a| ": ".to_doc().append(self.annotation(a))); + let annotation = annotation + .as_ref() + .map(|a| ": ".to_doc().append(self.annotation(a))); - keyword - .to_doc() - .append(pattern.append(annotation).group()) - .append(" =") - .append(self.case_clause_value(value)) + keyword + .to_doc() + .append(" ") + .append(pattern.append(annotation).group()) + .append(" =") + .append(self.case_clause_value(value)) + } + } } pub fn bytearray<'a>( diff --git a/crates/aiken-lang/src/parser/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs index 8e86ae16..03bbc0bb 100644 --- a/crates/aiken-lang/src/parser/expr/assignment.rs +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -9,25 +9,7 @@ use crate::{ pub fn let_( r: Recursive<'_, Token, UntypedExpr, ParseError>, ) -> impl Parser + '_ { - assignment(r, ast::AssignmentKind::Let) -} - -pub fn expect( - r: Recursive<'_, Token, UntypedExpr, ParseError>, -) -> impl Parser + '_ { - assignment(r, ast::AssignmentKind::Expect) -} - -fn assignment( - r: Recursive<'_, Token, UntypedExpr, ParseError>, - kind: ast::AssignmentKind, -) -> impl Parser + '_ { - let keyword = match kind { - ast::AssignmentKind::Let => Token::Let, - ast::AssignmentKind::Expect => Token::Expect, - }; - - just(keyword) + just(Token::Let) .ignore_then(pattern()) .then(just(Token::Colon).ignore_then(annotation()).or_not()) .then_ignore(just(Token::Equal)) @@ -37,12 +19,50 @@ fn assignment( location: span, value: Box::new(value), pattern, - kind, + kind: ast::AssignmentKind::Let, annotation, }, ) } +pub fn expect( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + just(Token::Expect) + .ignore_then( + pattern() + .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .then_ignore(just(Token::Equal)) + .or_not(), + ) + .then(r.clone()) + .map_with_span(move |(opt_pattern, value), span| { + let (pattern, annotation) = opt_pattern.unwrap_or_else(|| { + ( + ast::UntypedPattern::Constructor { + is_record: false, + location: span, + name: "True".to_string(), + arguments: vec![], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + None, + ) + }); + + UntypedExpr::Assignment { + location: span, + value: Box::new(value), + pattern, + kind: ast::AssignmentKind::Expect, + annotation, + } + }) +} + #[cfg(test)] mod tests { use crate::assert_expr; @@ -56,4 +76,9 @@ mod tests { fn expect() { assert_expr!("expect Some(x) = something.field"); } + + #[test] + fn expect_bool_sugar() { + assert_expr!("expect something.field == wow"); + } } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap new file mode 100644 index 00000000..3f50b4f9 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap @@ -0,0 +1,35 @@ +--- +source: crates/aiken-lang/src/parser/expr/assignment.rs +description: "Code:\n\nexpect something.field == wow" +--- +Assignment { + location: 0..29, + value: BinOp { + location: 7..29, + name: Eq, + left: FieldAccess { + location: 7..22, + label: "field", + container: Var { + location: 7..16, + name: "something", + }, + }, + right: Var { + location: 26..29, + name: "wow", + }, + }, + pattern: Constructor { + is_record: false, + location: 0..29, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + kind: Expect, + annotation: None, +} diff --git a/crates/aiken-lang/src/parser/pattern/constructor.rs b/crates/aiken-lang/src/parser/pattern/constructor.rs index b18b07c9..03978b0c 100644 --- a/crates/aiken-lang/src/parser/pattern/constructor.rs +++ b/crates/aiken-lang/src/parser/pattern/constructor.rs @@ -83,3 +83,18 @@ pub(crate) fn args( .unwrap_or_else(|| (vec![], false, false)) }) } + +#[cfg(test)] +mod tests { + use crate::assert_pattern; + + #[test] + fn constructor_basic() { + assert_pattern!("True"); + } + + #[test] + fn constructor_module_select() { + assert_pattern!("module.Foo"); + } +} diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap new file mode 100644 index 00000000..a3018425 --- /dev/null +++ b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap @@ -0,0 +1,14 @@ +--- +source: crates/aiken-lang/src/parser/pattern/constructor.rs +description: "Code:\n\nTrue" +--- +Constructor { + is_record: false, + location: 0..4, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), +} diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/constructor_module_select.snap b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_module_select.snap new file mode 100644 index 00000000..cc14a5d4 --- /dev/null +++ b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_module_select.snap @@ -0,0 +1,16 @@ +--- +source: crates/aiken-lang/src/parser/pattern/constructor.rs +description: "Code:\n\nmodule.Foo" +--- +Constructor { + is_record: false, + location: 0..10, + name: "Foo", + arguments: [], + module: Some( + "module", + ), + constructor: (), + with_spread: false, + tipo: (), +} diff --git a/crates/aiken-lang/src/parser/utils.rs b/crates/aiken-lang/src/parser/utils.rs index 2c5b1a04..75d63e92 100644 --- a/crates/aiken-lang/src/parser/utils.rs +++ b/crates/aiken-lang/src/parser/utils.rs @@ -61,6 +61,27 @@ macro_rules! assert_annotation { }; } +#[macro_export] +macro_rules! assert_pattern { + ($code:expr) => { + use chumsky::Parser; + + let $crate::parser::lexer::LexInfo { tokens, .. } = $crate::parser::lexer::run(indoc::indoc! { $code }).unwrap(); + + let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter()); + + let result = $crate::parser::pattern().parse(stream).unwrap(); + + insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + prepend_module_to_snapshot => false, + omit_expression => true + }, { + insta::assert_debug_snapshot!(result); + }); + }; +} + #[macro_export] macro_rules! assert_module { ($code:expr) => { diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index efd93598..52df87a2 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -154,6 +154,33 @@ fn multi_validator_warning() { )) } +#[test] +fn expect_sugar_correct_type() { + let source_code = r#" + fn foo() { + expect 1 == 1 + 2 + } + "#; + + assert!(matches!(check(parse(source_code)), Ok(_))) +} + +#[test] +fn expect_sugar_incorrect_type() { + let source_code = r#" + fn foo() { + expect 1 + 2 + } + "#; + + assert!(matches!( + dbg!(check(parse(source_code))), + Err((_, Error::CouldNotUnify { .. })) + )) +} + #[test] fn anonymous_function_scoping() { let source_code = r#"