feat: expect boolean sugar
This commit is contained in:
parent
d009358266
commit
1b8e94fe32
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## v1.0.13-alpha - unreleased
|
## v1.0.13-alpha - unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **aiken-lang**: `expect foo == bar` desugars into `expect True = foo == bar`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **aiken-lang**: fail, todo, and trace had issues with sequences and expressions
|
- **aiken-lang**: fail, todo, and trace had issues with sequences and expressions
|
||||||
|
|
|
@ -643,10 +643,17 @@ impl<'comments> Formatter<'comments> {
|
||||||
self.pop_empty_lines(pattern.location().end);
|
self.pop_empty_lines(pattern.location().end);
|
||||||
|
|
||||||
let keyword = match kind {
|
let keyword = match kind {
|
||||||
AssignmentKind::Let => "let ",
|
AssignmentKind::Let => "let",
|
||||||
AssignmentKind::Expect => "expect ",
|
AssignmentKind::Expect => "expect",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 pattern = self.pattern(pattern);
|
||||||
|
|
||||||
let annotation = annotation
|
let annotation = annotation
|
||||||
|
@ -655,10 +662,13 @@ impl<'comments> Formatter<'comments> {
|
||||||
|
|
||||||
keyword
|
keyword
|
||||||
.to_doc()
|
.to_doc()
|
||||||
|
.append(" ")
|
||||||
.append(pattern.append(annotation).group())
|
.append(pattern.append(annotation).group())
|
||||||
.append(" =")
|
.append(" =")
|
||||||
.append(self.case_clause_value(value))
|
.append(self.case_clause_value(value))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bytearray<'a>(
|
pub fn bytearray<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -9,25 +9,7 @@ use crate::{
|
||||||
pub fn let_(
|
pub fn let_(
|
||||||
r: Recursive<'_, Token, UntypedExpr, ParseError>,
|
r: Recursive<'_, Token, UntypedExpr, ParseError>,
|
||||||
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
||||||
assignment(r, ast::AssignmentKind::Let)
|
just(Token::Let)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expect(
|
|
||||||
r: Recursive<'_, Token, UntypedExpr, ParseError>,
|
|
||||||
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
|
||||||
assignment(r, ast::AssignmentKind::Expect)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assignment(
|
|
||||||
r: Recursive<'_, Token, UntypedExpr, ParseError>,
|
|
||||||
kind: ast::AssignmentKind,
|
|
||||||
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
|
||||||
let keyword = match kind {
|
|
||||||
ast::AssignmentKind::Let => Token::Let,
|
|
||||||
ast::AssignmentKind::Expect => Token::Expect,
|
|
||||||
};
|
|
||||||
|
|
||||||
just(keyword)
|
|
||||||
.ignore_then(pattern())
|
.ignore_then(pattern())
|
||||||
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
||||||
.then_ignore(just(Token::Equal))
|
.then_ignore(just(Token::Equal))
|
||||||
|
@ -37,12 +19,50 @@ fn assignment(
|
||||||
location: span,
|
location: span,
|
||||||
value: Box::new(value),
|
value: Box::new(value),
|
||||||
pattern,
|
pattern,
|
||||||
kind,
|
kind: ast::AssignmentKind::Let,
|
||||||
annotation,
|
annotation,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expect(
|
||||||
|
r: Recursive<'_, Token, UntypedExpr, ParseError>,
|
||||||
|
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::assert_expr;
|
use crate::assert_expr;
|
||||||
|
@ -56,4 +76,9 @@ mod tests {
|
||||||
fn expect() {
|
fn expect() {
|
||||||
assert_expr!("expect Some(x) = something.field");
|
assert_expr!("expect Some(x) = something.field");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expect_bool_sugar() {
|
||||||
|
assert_expr!("expect something.field == wow");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -83,3 +83,18 @@ pub(crate) fn args(
|
||||||
.unwrap_or_else(|| (vec![], false, false))
|
.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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: (),
|
||||||
|
}
|
|
@ -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: (),
|
||||||
|
}
|
|
@ -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_export]
|
||||||
macro_rules! assert_module {
|
macro_rules! assert_module {
|
||||||
($code:expr) => {
|
($code:expr) => {
|
||||||
|
|
|
@ -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]
|
#[test]
|
||||||
fn anonymous_function_scoping() {
|
fn anonymous_function_scoping() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
|
|
Loading…
Reference in New Issue