diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 1760ec53..d8902507 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1782,6 +1782,47 @@ impl Span { } } + /// Map the current start and end of the Span to new values. + /// + /// # Examples + /// + /// ``` + /// use aiken_lang::ast::Span; + /// + /// let span = Span { start: 0, end: 1 }; + /// + /// let other = span.map(|start, end| (start + 2, end + 4)); + /// + /// assert_eq!(other.start, 2); + /// assert_eq!(other.end, 5); + /// ``` + pub fn map (usize, usize)>(&self, f: F) -> Self { + let (start, end) = f(self.start, self.end); + + Self { start, end } + } + + /// Map the current end of the Span to a new value. + /// + /// # Examples + /// + /// ``` + /// use aiken_lang::ast::Span; + /// + /// let span = Span { start: 0, end: 1 }; + /// + /// let other = span.map_end(|end| end + 1); + /// + /// assert_eq!(other.start, 0); + /// assert_eq!(other.end, 2); + /// ``` + pub fn map_end usize>(&self, f: F) -> Self { + Self { + start: self.start, + end: f(self.end), + } + } + pub fn contains(&self, byte_index: usize) -> bool { byte_index >= self.start && byte_index < self.end } diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 815bcfa3..7dbf771c 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -4,7 +4,7 @@ use crate::{ Curve, DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, LogicalOpChainKind, ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypedAssignmentKind, TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp, - UntypedAssignmentKind, UntypedClause, UntypedRecordUpdateArg, + UntypedAssignmentKind, UntypedClause, UntypedPattern, UntypedRecordUpdateArg, }, builtins::void, parser::token::Base, @@ -518,9 +518,8 @@ pub enum UntypedExpr { Assignment { location: Span, value: Box, - pattern: Pattern<(), ()>, + patterns: Vec1<(UntypedPattern, Option)>, kind: UntypedAssignmentKind, - annotation: Option, }, Trace { @@ -1300,22 +1299,25 @@ impl UntypedExpr { ) } - pub fn lambda(name: String, expressions: Vec, location: Span) -> Self { + pub fn lambda(names: Vec, expressions: Vec, location: Span) -> Self { Self::Fn { location, fn_style: FnStyle::Plain, - arguments: vec![Arg { - location, - doc: None, - annotation: None, - tipo: (), - arg_name: ArgName::Named { - label: name.clone(), - name, + arguments: names + .into_iter() + .map(|name| Arg { location, - is_validator_param: false, - }, - }], + doc: None, + annotation: None, + tipo: (), + arg_name: ArgName::Named { + label: name.clone(), + name, + location, + is_validator_param: false, + }, + }) + .collect(), body: Self::Sequence { location, expressions, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 7217b138..8ca3e68b 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -679,10 +679,9 @@ impl<'comments> Formatter<'comments> { fn assignment<'a>( &mut self, - pattern: &'a UntypedPattern, + patterns: &'a Vec1<(UntypedPattern, Option)>, value: &'a UntypedExpr, kind: UntypedAssignmentKind, - annotation: &'a Option, ) -> Document<'a> { let keyword = match kind { AssignmentKind::Let { .. } => "let", @@ -691,26 +690,39 @@ impl<'comments> Formatter<'comments> { let symbol = if kind.is_backpassing() { "<-" } else { "=" }; - match pattern { - UntypedPattern::Constructor { - name, module: None, .. - } if name == "True" && annotation.is_none() && kind.is_expect() => { + match patterns.first() { + ( + UntypedPattern::Constructor { + name, module: None, .. + }, + annotation, + ) if name == "True" + && annotation.is_none() + && kind.is_expect() + && patterns.len() == 1 => + { keyword.to_doc().append(self.case_clause_value(value)) } _ => { - self.pop_empty_lines(pattern.location().end); + let patterns = patterns.into_iter().map(|(pattern, annotation)| { + self.pop_empty_lines(pattern.location().end); - let pattern = self.pattern(pattern); + 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))); + + pattern.append(annotation).group() + }); keyword .to_doc() - .append(" ") - .append(pattern.append(annotation).group()) - .append(" ") + .append(break_("", " ")) + .append(join(patterns, break_(",", ", "))) + .nest(INDENT) + .append(break_(",", "")) + .append(break_("", " ")) .append(symbol) .append(self.case_clause_value(value)) } @@ -907,11 +919,10 @@ impl<'comments> Formatter<'comments> { UntypedExpr::Assignment { value, - pattern, - annotation, + patterns, kind, .. - } => self.assignment(pattern, value, *kind, annotation), + } => self.assignment(patterns, value, *kind), UntypedExpr::Trace { kind, text, then, .. diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap index ca43eda4..297d70a5 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap @@ -14,20 +14,24 @@ Test( location: 45..50, name: "False", }, - pattern: Constructor { - is_record: false, - location: 38..42, - name: "True", - arguments: [], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, + patterns: [ + ( + Constructor { + is_record: false, + location: 38..42, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + None, + ), + ], kind: Expect { backpassing: false, }, - annotation: None, }, Var { location: 54..59, diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap index f01c224c..c506c61a 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap @@ -25,14 +25,18 @@ Fn( }, }, }, - pattern: Var { - location: 17..18, - name: "x", - }, + patterns: [ + ( + Var { + location: 17..18, + name: "x", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, doc: None, location: 0..8, diff --git a/crates/aiken-lang/src/parser/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs index 8435e38f..065232a5 100644 --- a/crates/aiken-lang/src/parser/expr/assignment.rs +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -9,23 +9,30 @@ pub fn let_( r: Recursive<'_, Token, UntypedExpr, ParseError>, ) -> impl Parser + '_ { just(Token::Let) - .ignore_then(pattern()) - .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .ignore_then( + pattern() + .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .separated_by(just(Token::Comma)) + .at_least(1), + ) .then(choice((just(Token::Equal), just(Token::LArrow)))) .then(r.clone()) - .validate(move |(((pattern, annotation), kind), value), span, emit| { + .validate(move |((patterns, kind), value), span, emit| { if matches!(value, UntypedExpr::Assignment { .. }) { emit(ParseError::invalid_assignment_right_hand_side(span)) } + let patterns = patterns + .try_into() + .expect("We use at_least(1) so this should never be empty"); + UntypedExpr::Assignment { location: span, value: Box::new(value), - pattern, + patterns, kind: ast::AssignmentKind::Let { backpassing: kind == Token::LArrow, }, - annotation, } }) } @@ -37,14 +44,20 @@ pub fn expect( .ignore_then( pattern() .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .separated_by(just(Token::Comma)) + .at_least(1) .then(choice((just(Token::Equal), just(Token::LArrow)))) .or_not(), ) .then(r.clone()) .validate(move |(opt_pattern, value), span, emit| { - let ((pattern, annotation), kind) = opt_pattern.unwrap_or_else(|| { + if matches!(value, UntypedExpr::Assignment { .. }) { + emit(ParseError::invalid_assignment_right_hand_side(span)) + } + + let (patterns, kind) = opt_pattern.unwrap_or_else(|| { ( - ( + vec![( ast::UntypedPattern::Constructor { is_record: false, location: span, @@ -56,23 +69,22 @@ pub fn expect( tipo: (), }, None, - ), + )], Token::Equal, ) }); - if matches!(value, UntypedExpr::Assignment { .. }) { - emit(ParseError::invalid_assignment_right_hand_side(span)) - } + let patterns = patterns + .try_into() + .expect("We use at_least(1) so this should never be empty"); UntypedExpr::Assignment { location: span, + patterns, value: Box::new(value), - pattern, kind: ast::AssignmentKind::Expect { backpassing: kind == Token::LArrow, }, - annotation, } }) } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap index 4d930184..6444967a 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap @@ -16,14 +16,18 @@ Assignment { numeric_underscore: false, }, }, - pattern: Var { - location: 16..17, - name: "x", - }, + patterns: [ + ( + Var { + location: 16..17, + name: "x", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, BinOp { location: 24..29, @@ -42,12 +46,16 @@ Assignment { }, ], }, - pattern: Var { - location: 4..5, - name: "b", - }, + patterns: [ + ( + Var { + location: 4..5, + name: "b", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap index db440339..21df69fc 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap @@ -12,27 +12,31 @@ Assignment { name: "something", }, }, - pattern: Constructor { - is_record: false, - location: 7..14, - name: "Some", - arguments: [ - CallArg { - label: None, - location: 12..13, - value: Var { - location: 12..13, - name: "x", - }, + patterns: [ + ( + Constructor { + is_record: false, + location: 7..14, + name: "Some", + arguments: [ + CallArg { + label: None, + location: 12..13, + value: Var { + location: 12..13, + name: "x", + }, + }, + ], + module: None, + constructor: (), + with_spread: false, + tipo: (), }, - ], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, + None, + ), + ], kind: Expect { backpassing: false, }, - annotation: None, } 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 index 17011ba0..27e7a77b 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap @@ -20,18 +20,22 @@ Assignment { name: "wow", }, }, - pattern: Constructor { - is_record: false, - location: 0..29, - name: "True", - arguments: [], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, + patterns: [ + ( + Constructor { + is_record: false, + location: 0..29, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + None, + ), + ], kind: Expect { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap index b5bba606..5df2fcab 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap @@ -16,29 +16,37 @@ Assignment { numeric_underscore: false, }, }, - pattern: Var { - location: 13..14, - name: "a", - }, + patterns: [ + ( + Var { + location: 13..14, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, ], }, - pattern: Constructor { - is_record: false, - location: 0..21, - name: "True", - arguments: [], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, + patterns: [ + ( + Constructor { + is_record: false, + location: 0..21, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + None, + ), + ], kind: Expect { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap index a4556c2c..5b26c139 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap @@ -16,23 +16,31 @@ Assignment { numeric_underscore: false, }, }, - pattern: Var { - location: 14..15, - name: "b", - }, + patterns: [ + ( + Var { + location: 14..15, + name: "b", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, ], }, - pattern: Var { - location: 4..5, - name: "a", - }, + patterns: [ + ( + Var { + location: 4..5, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap index 62f0b244..12a355bd 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap @@ -16,23 +16,31 @@ Assignment { numeric_underscore: false, }, }, - pattern: Var { - location: 14..15, - name: "b", - }, + patterns: [ + ( + Var { + location: 14..15, + name: "b", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, ], }, - pattern: Var { - location: 4..5, - name: "a", - }, + patterns: [ + ( + Var { + location: 4..5, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap index 5127c302..5fb0c2eb 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap @@ -16,14 +16,18 @@ Assignment { numeric_underscore: false, }, }, - pattern: Var { - location: 16..17, - name: "b", - }, + patterns: [ + ( + Var { + location: 16..17, + name: "b", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Var { location: 25..26, @@ -31,12 +35,16 @@ Assignment { }, ], }, - pattern: Var { - location: 4..5, - name: "a", - }, + patterns: [ + ( + Var { + location: 4..5, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap index 9417ee98..a0300807 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap @@ -11,18 +11,22 @@ Assignment { name: "foo", }, }, - pattern: Constructor { - is_record: false, - location: 0..11, - name: "True", - arguments: [], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, + patterns: [ + ( + Constructor { + is_record: false, + location: 0..11, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + None, + ), + ], kind: Expect { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap index d968e56b..72540983 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap @@ -27,14 +27,18 @@ Sequence { }, location: 8..18, }, - pattern: Var { - location: 4..5, - name: "x", - }, + patterns: [ + ( + Var { + location: 4..5, + name: "x", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Assignment { location: 20..65, @@ -113,14 +117,18 @@ Sequence { }, return_annotation: None, }, - pattern: Var { - location: 24..33, - name: "map_add_x", - }, + patterns: [ + ( + Var { + location: 24..33, + name: "map_add_x", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Call { arguments: [ diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap index ed6577f4..8042af2d 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap @@ -14,14 +14,18 @@ Sequence { numeric_underscore: true, }, }, - pattern: Var { - location: 8..9, - name: "i", - }, + patterns: [ + ( + Var { + location: 8..9, + name: "i", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Assignment { location: 24..41, @@ -32,14 +36,18 @@ Sequence { numeric_underscore: true, }, }, - pattern: Var { - location: 28..29, - name: "j", - }, + patterns: [ + ( + Var { + location: 28..29, + name: "j", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Assignment { location: 44..59, @@ -54,14 +62,18 @@ Sequence { }, }, }, - pattern: Var { - location: 48..49, - name: "k", - }, + patterns: [ + ( + Var { + location: 48..49, + name: "k", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, ], } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap b/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap index 57c91fa6..91d6b566 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap @@ -28,12 +28,16 @@ Assignment { ], tail: None, }, - pattern: Var { - location: 4..9, - name: "thing", - }, + patterns: [ + ( + Var { + location: 4..9, + name: "thing", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap index 426f9ae4..5fd34da5 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap @@ -40,14 +40,18 @@ Sequence { }, ], }, - pattern: Var { - location: 4..9, - name: "tuple", - }, + patterns: [ + ( + Var { + location: 4..9, + name: "tuple", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, BinOp { location: 25..70, diff --git a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap index f020b9d1..8ac60cc7 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap @@ -27,14 +27,18 @@ Sequence { }, location: 8..15, }, - pattern: Var { - location: 4..5, - name: "a", - }, + patterns: [ + ( + Var { + location: 4..5, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Tuple { location: 16..23, diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap index 57450895..4be8bf53 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap @@ -85,14 +85,18 @@ When { numeric_underscore: false, }, }, - pattern: Var { - location: 55..62, - name: "amazing", - }, + patterns: [ + ( + Var { + location: 55..62, + name: "amazing", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Var { location: 71..78, diff --git a/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap b/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap index 0c317312..4d8808b3 100644 --- a/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap +++ b/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap @@ -19,14 +19,18 @@ Module { location: 23..26, name: "bar", }, - pattern: Var { - location: 19..20, - name: "a", - }, + patterns: [ + ( + Var { + location: 19..20, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, UInt { location: 30..32, @@ -59,14 +63,18 @@ Module { location: 60..63, name: "bar", }, - pattern: Var { - location: 56..57, - name: "a", - }, + patterns: [ + ( + Var { + location: 56..57, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, UInt { location: 67..69, @@ -110,14 +118,18 @@ Module { }, }, }, - pattern: Var { - location: 93..94, - name: "a", - }, + patterns: [ + ( + Var { + location: 93..94, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, doc: None, location: 74..84, @@ -157,14 +169,18 @@ Module { }, location: 130..137, }, - pattern: Var { - location: 126..127, - name: "a", - }, + patterns: [ + ( + Var { + location: 126..127, + name: "a", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, BinOp { location: 141..153, diff --git a/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap b/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap index 9d78faff..73d9ba10 100644 --- a/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap +++ b/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap @@ -24,14 +24,18 @@ Module { ], preferred_format: Utf8String, }, - pattern: Var { - location: 17..18, - name: "x", - }, + patterns: [ + ( + Var { + location: 17..18, + name: "x", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Var { location: 29..30, diff --git a/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap b/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap index a4dd2337..dbf759f5 100644 --- a/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap +++ b/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap @@ -22,14 +22,18 @@ Module { ], preferred_format: Utf8String, }, - pattern: Var { - location: 17..18, - name: "x", - }, + patterns: [ + ( + Var { + location: 17..18, + name: "x", + }, + None, + ), + ], kind: Let { backpassing: false, }, - annotation: None, }, Var { location: 27..28, diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 11a5c861..670ca9b6 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1383,6 +1383,74 @@ fn backpassing_expect_type_mismatch() { )) } +#[test] +fn backpassing_multi_args() { + let source_code = r#" + fn fold(list: List, init: b, then: fn(a, b) -> b) -> b { + when list is { + [] -> init + [x, ..rest] -> fold(rest, then(x, init), then) + } + } + + fn backpassing() -> Int { + let elem, acc <- fold([1, 2, 3], 0) + + elem + acc + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn backpassing_multi_args_expect() { + let source_code = r#" + pub type Bar { + Foo(Int) + Wow(Int) + } + + fn fold(list: List, init: b, then: fn(a, b) -> b) -> b { + when list is { + [] -> init + [x, ..rest] -> fold(rest, then(x, init), then) + } + } + + pub fn backpassing() -> Bar { + expect Foo(elem), Wow(acc) <- fold([Foo(1), Foo(2), Foo(3)], Wow(0)) + + Wow(elem + acc) + } + "#; + + assert!(matches!(check(parse(source_code)), Ok((warnings, _)) if warnings.is_empty())) +} + +#[test] +fn backpassing_multi_args_using_equals() { + let source_code = r#" + fn fold(list: List, init: b, then: fn(a, b) -> b) -> b { + when list is { + [] -> init + [x, ..rest] -> fold(rest, then(x, init), then) + } + } + + fn backpassing() -> Int { + let elem, acc = fold([1, 2, 3], 0, fn(elem, acc) { elem + acc }) + + elem + acc + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::UnexpectedMultiPatternAssignment { .. })) + )) +} + #[test] fn trace_if_false_ko() { let source_code = r#" diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 19c56c96..ca1fbe7c 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -707,6 +707,19 @@ Perhaps, try the following: with_spread: bool, }, + #[error("I discovered a regular let assignment with multiple patterns.\n")] + #[diagnostic(code("unexpected::multi_pattern_assignment"))] + #[diagnostic(help( + "Did you mean to use backpassing syntax with {}?", + "<-".if_supports_color(Stdout, |s| s.purple()) + ))] + UnexpectedMultiPatternAssignment { + #[label("unexpected")] + location: Span, + #[label("<-")] + arrow: Span, + }, + #[error("I tripped over some unknown labels in a pattern or function.\n")] #[diagnostic(code("unknown::labels"))] UnknownLabels(#[related] Vec), @@ -1031,6 +1044,7 @@ impl ExtraData for Error { | Error::ValidatorImported { .. } | Error::IncorrectTestArity { .. } | Error::GenericLeftAtBoundary { .. } + | Error::UnexpectedMultiPatternAssignment { .. } | Error::ValidatorMustReturnBool { .. } => None, Error::UnknownType { name, .. } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index b9a298e4..62c182d6 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -262,12 +262,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> { UntypedExpr::Assignment { location, - pattern, + patterns, value, kind, - annotation, .. - } => self.infer_assignment(pattern, *value, kind, &annotation, location), + } => { + // at this point due to backpassing rewrites, + // patterns is guaranteed to have one item + let (pattern, annotation) = patterns.into_vec().swap_remove(0); + + self.infer_assignment(pattern, *value, kind, &annotation, location) + } UntypedExpr::Trace { location, @@ -340,7 +345,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, value, op, - } => self.infer_un_op(location, value, op), + } => self.infer_un_op(location, *value, op), UntypedExpr::TraceIfFalse { value, location } => { self.infer_trace_if_false(*value, location) @@ -663,10 +668,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { fn infer_un_op( &mut self, location: Span, - value: Box, + value: UntypedExpr, op: UnOp, ) -> Result { - let value = self.infer(*value)?; + let value = self.infer(value)?; let tipo = match op { UnOp::Not => bool(), @@ -949,19 +954,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> { )? } else { if value_is_data && !untyped_pattern.is_var() && !untyped_pattern.is_discard() { + let ann = Annotation::Constructor { + location: Span::empty(), + module: None, + name: "Type".to_string(), + arguments: vec![], + }; + return Err(Error::CastDataNoAnn { location, value: UntypedExpr::Assignment { location, value: untyped_value.into(), - pattern: untyped_pattern, + patterns: Vec1::new((untyped_pattern, Some(ann))), kind, - annotation: Some(Annotation::Constructor { - location: Span::empty(), - module: None, - name: "Type".to_string(), - arguments: vec![], - }), }, }); } @@ -1007,17 +1013,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { name: "...".to_string(), location: Span::empty(), }), - pattern: untyped_pattern, + patterns: Vec1::new((untyped_pattern, None)), kind: AssignmentKind::Let { backpassing: true }, - annotation: None, } } _ => UntypedExpr::Assignment { location: Span::empty(), value: Box::new(untyped_value), - pattern: untyped_pattern, + patterns: Vec1::new((untyped_pattern, None)), kind: AssignmentKind::let_(), - annotation: None, }, }, }); @@ -1721,17 +1725,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> { PipeTyper::infer(self, expressions) } - fn backpass(&mut self, breakpoint: UntypedExpr, continuation: Vec) -> UntypedExpr { - let (value, pattern, annotation, kind, assign_location) = match breakpoint { - UntypedExpr::Assignment { - location, - value, - pattern, - annotation, - kind, - .. - } => (value, pattern, annotation, kind, location), - _ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"), + fn backpass( + &mut self, + breakpoint: UntypedExpr, + mut continuation: Vec, + ) -> UntypedExpr { + let UntypedExpr::Assignment { + location: assign_location, + value, + kind, + patterns, + .. + } = breakpoint + else { + unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"); }; let value_location = value.location(); @@ -1744,33 +1751,41 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .unwrap_or_else(|| value_location.end), }; - // In case where we have a Pattern that isn't simply a let-binding to a name, we do insert an extra let-binding - // in front of the continuation sequence. This is because we do not support patterns in function argument - // (which is perhaps something we should support?). - let (name, continuation) = match pattern { - Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => { - (name.clone(), continuation) + let mut names = Vec::new(); + + for (index, (pattern, annotation)) in patterns.into_iter().enumerate() { + // In case where we have a Pattern that isn't simply a let-binding to a name, we do insert an extra let-binding + // in front of the continuation sequence. This is because we do not support patterns in function argument + // (which is perhaps something we should support?). + match pattern { + Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => { + names.push(name.clone()); + } + _ => { + let name = format!("{}_{}", ast::BACKPASS_VARIABLE, index); + + continuation.insert( + 0, + UntypedExpr::Assignment { + location: assign_location, + value: UntypedExpr::Var { + location: value_location, + name: name.clone(), + } + .into(), + patterns: Vec1::new((pattern, annotation)), + // erase backpassing while preserving assignment kind. + kind: match kind { + AssignmentKind::Let { .. } => AssignmentKind::let_(), + AssignmentKind::Expect { .. } => AssignmentKind::expect(), + }, + }, + ); + + names.push(name); + } } - _ => { - let mut with_assignment = vec![UntypedExpr::Assignment { - location: assign_location, - value: UntypedExpr::Var { - location: value_location, - name: ast::BACKPASS_VARIABLE.to_string(), - } - .into(), - pattern, - // Erase backpassing while preserving assignment kind. - kind: match kind { - AssignmentKind::Let { .. } => AssignmentKind::let_(), - AssignmentKind::Expect { .. } => AssignmentKind::expect(), - }, - annotation, - }]; - with_assignment.extend(continuation); - (ast::BACKPASS_VARIABLE.to_string(), with_assignment) - } - }; + } match *value { UntypedExpr::Call { fun, arguments, .. } => { @@ -1779,7 +1794,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { new_arguments.push(CallArg { location: call_location, label: None, - value: UntypedExpr::lambda(name, continuation, call_location), + value: UntypedExpr::lambda(names, continuation, call_location), }); UntypedExpr::Call { @@ -1811,7 +1826,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { arguments: vec![CallArg { location: call_location, label: None, - value: UntypedExpr::lambda(name, continuation, call_location), + value: UntypedExpr::lambda(names, continuation, call_location), }], }; @@ -1838,7 +1853,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { arguments: vec![CallArg { location: call_location, label: None, - value: UntypedExpr::lambda(name, continuation, call_location), + value: UntypedExpr::lambda(names, continuation, call_location), }], }, } @@ -1857,10 +1872,28 @@ impl<'a, 'b> ExprTyper<'a, 'b> { UntypedExpr::Assignment { kind, .. } if kind.is_backpassing() => { breakpoint = Some(expression); } + UntypedExpr::Assignment { + patterns, location, .. + } if patterns.len() > 1 => { + return Err(Error::UnexpectedMultiPatternAssignment { + arrow: patterns + .last() + .0 + .location() + .map(|_, c_end| (c_end + 1, c_end + 1)), + location: patterns[1..] + .iter() + .map(|(p, _)| p.location()) + .reduce(|acc, loc| acc.union(loc)) + .unwrap_or(location) + .map_end(|current| current - 1), + }); + } _ => prefix.push(expression), } } } + if let Some(breakpoint) = breakpoint { prefix.push(self.backpass(breakpoint, suffix)); return self.infer_seq(location, prefix); @@ -2149,9 +2182,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { sample: UntypedExpr::Assignment { location: Span::empty(), value: Box::new(subject.clone()), - pattern: clauses[0].patterns[0].clone(), + patterns: Vec1::new((clauses[0].patterns[0].clone(), None)), kind: AssignmentKind::let_(), - annotation: None, }, }); }