diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 622c455f..714fa210 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1243,7 +1243,7 @@ pub enum Pattern { arguments: Vec>, module: Option, constructor: Constructor, - with_spread: bool, + spread_location: Option, tipo: Type, }, @@ -1273,6 +1273,15 @@ impl Pattern { } } + pub fn with_spread(&self) -> bool { + match self { + Pattern::Constructor { + spread_location, .. + } => spread_location.is_some(), + _ => false, + } + } + /// Returns `true` if the pattern is [`Discard`]. /// /// [`Discard`]: Pattern::Discard @@ -1295,7 +1304,7 @@ impl UntypedPattern { name: "True".to_string(), arguments: vec![], constructor: (), - with_spread: false, + spread_location: None, tipo: (), module: None, is_record: false, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 041c6049..938c3024 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1068,7 +1068,7 @@ impl<'comments> Formatter<'comments> { name: &'a str, args: &'a [CallArg], module: &'a Option, - with_spread: bool, + spread_location: Option, is_record: bool, ) -> Document<'a> { fn is_breakable(expr: &UntypedPattern) -> bool { @@ -1086,7 +1086,7 @@ impl<'comments> Formatter<'comments> { None => name.to_doc(), }; - if args.is_empty() && with_spread { + if args.is_empty() && spread_location.is_some() { if is_record { name.append(" { .. }") // TODO: not possible @@ -1095,11 +1095,16 @@ impl<'comments> Formatter<'comments> { } } else if args.is_empty() { name - } else if with_spread { + } else if let Some(spread_location) = spread_location { + let args = args + .iter() + .map(|a| self.pattern_call_arg(a)) + .collect::>(); + let wrapped_args = if is_record { - wrap_fields_with_spread(args.iter().map(|a| self.pattern_call_arg(a))) + self.wrap_fields_with_spread(args, spread_location) } else { - wrap_args_with_spread(args.iter().map(|a| self.pattern_call_arg(a))) + self.wrap_args_with_spread(args, spread_location) }; name.append(wrapped_args) @@ -1120,6 +1125,48 @@ impl<'comments> Formatter<'comments> { } } + pub fn wrap_fields_with_spread<'a, I>(&mut self, args: I, spread_location: Span) -> Document<'a> + where + I: IntoIterator>, + { + let mut args = args.into_iter().peekable(); + if args.peek().is_none() { + return "()".to_doc(); + } + + let comments = self.pop_comments(spread_location.start); + + break_(" {", " { ") + .append(join(args, break_(",", ", "))) + .append(break_(",", ", ")) + .append(commented("..".to_doc(), comments)) + .nest(INDENT) + .append(break_("", " ")) + .append("}") + .group() + } + + pub fn wrap_args_with_spread<'a, I>(&mut self, args: I, spread_location: Span) -> Document<'a> + where + I: IntoIterator>, + { + let mut args = args.into_iter().peekable(); + if args.peek().is_none() { + return "()".to_doc(); + } + + let comments = self.pop_comments(spread_location.start); + + break_("(", "(") + .append(join(args, break_(",", ", "))) + .append(break_(",", ", ")) + .append(commented("..".to_doc(), comments)) + .nest(INDENT) + .append(break_(",", "")) + .append(")") + .group() + } + fn call<'a>(&mut self, fun: &'a UntypedExpr, args: &'a [CallArg]) -> Document<'a> { let is_constr = match fun { UntypedExpr::Var { name, .. } => name[0..1].chars().all(|c| c.is_uppercase()), @@ -1821,26 +1868,31 @@ impl<'comments> Formatter<'comments> { name, arguments: args, module, - with_spread, + spread_location, is_record, .. - } => self.pattern_constructor(name, args, module, *with_spread, *is_record), + } => self.pattern_constructor(name, args, module, *spread_location, *is_record), }; commented(doc, comments) } fn pattern_call_arg<'a>(&mut self, arg: &'a CallArg) -> Document<'a> { + let comments = self.pop_comments(arg.location.start); + if let (UntypedPattern::Var { name, .. }, Some(label)) = (&arg.value, &arg.label) { if name == label { return self.pattern(&arg.value); } } - arg.label + let doc = arg + .label .as_ref() .map(|s| s.to_doc().append(": ")) .unwrap_or_else(nil) - .append(self.pattern(&arg.value)) + .append(self.pattern(&arg.value)); + + commented(doc, comments) } pub fn clause_guard_bin_op<'a>( @@ -2029,44 +2081,6 @@ where .append(line()) } -pub fn wrap_args_with_spread<'a, I>(args: I) -> Document<'a> -where - I: IntoIterator>, -{ - let mut args = args.into_iter().peekable(); - if args.peek().is_none() { - return "()".to_doc(); - } - - break_("(", "(") - .append(join(args, break_(",", ", "))) - .append(break_(",", ", ")) - .append("..") - .nest(INDENT) - .append(break_(",", "")) - .append(")") - .group() -} - -pub fn wrap_fields_with_spread<'a, I>(args: I) -> Document<'a> -where - I: IntoIterator>, -{ - let mut args = args.into_iter().peekable(); - if args.peek().is_none() { - return "()".to_doc(); - } - - break_(" {", " { ") - .append(join(args, break_(",", ", "))) - .append(break_(",", ", ")) - .append("..") - .nest(INDENT) - .append(break_("", " ")) - .append("}") - .group() -} - fn list<'a>(elements: Document<'a>, length: usize, tail: Option>) -> Document<'a> { if length == 0 { return match tail { 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 d3b30a4c..868e90ab 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 @@ -23,7 +23,7 @@ Test( arguments: [], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), }, 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 4588bfd5..d50d5ff0 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap @@ -30,7 +30,7 @@ Assignment { ], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), }, 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 e0e8e104..4911d759 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 @@ -29,7 +29,7 @@ Assignment { arguments: [], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), }, 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 98f8b175..6df9e432 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 @@ -41,7 +41,7 @@ Assignment { arguments: [], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), }, 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 cc3515dc..984e20e4 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 @@ -20,7 +20,7 @@ Assignment { arguments: [], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), }, annotation: None, diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap index b708e48a..18642888 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_double_todo.snap @@ -19,7 +19,9 @@ When { arguments: [], module: None, constructor: (), - with_spread: true, + spread_location: Some( + 21..23, + ), tipo: (), }, ], @@ -46,7 +48,9 @@ When { arguments: [], module: None, constructor: (), - with_spread: true, + spread_location: Some( + 40..42, + ), tipo: (), }, ], diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_solo_error.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_solo_error.snap index b904be80..d7bae6e4 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_solo_error.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_solo_error.snap @@ -19,7 +19,9 @@ When { arguments: [], module: None, constructor: (), - with_spread: true, + spread_location: Some( + 21..23, + ), tipo: (), }, ], diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap index 26c65eea..bd0197bc 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_clause_todo.snap @@ -19,7 +19,9 @@ When { arguments: [], module: None, constructor: (), - with_spread: true, + spread_location: Some( + 21..23, + ), tipo: (), }, ], @@ -39,7 +41,9 @@ When { arguments: [], module: None, constructor: (), - with_spread: true, + spread_location: Some( + 40..42, + ), tipo: (), }, ], diff --git a/crates/aiken-lang/src/parser/pattern/constructor.rs b/crates/aiken-lang/src/parser/pattern/constructor.rs index 03978b0c..46256286 100644 --- a/crates/aiken-lang/src/parser/pattern/constructor.rs +++ b/crates/aiken-lang/src/parser/pattern/constructor.rs @@ -1,7 +1,7 @@ use chumsky::prelude::*; use crate::{ - ast::{CallArg, UntypedPattern}, + ast::{CallArg, Span, UntypedPattern}, parser::{error::ParseError, token::Token}, }; @@ -10,23 +10,26 @@ pub fn parser( ) -> impl Parser + '_ { select! {Token::UpName { name } => name} .then(args(expression)) - .map_with_span(|(name, (arguments, with_spread, is_record)), location| { - UntypedPattern::Constructor { - is_record, - location, - name, - arguments, - module: None, - constructor: (), - with_spread, - tipo: (), - } - }) + .map_with_span( + |(name, (arguments, spread_location, is_record)), location| { + UntypedPattern::Constructor { + is_record, + location, + name, + arguments, + module: None, + constructor: (), + spread_location, + tipo: (), + } + }, + ) } pub(crate) fn args( expression: Recursive<'_, Token, UntypedPattern, ParseError>, -) -> impl Parser>, bool, bool), Error = ParseError> + '_ { +) -> impl Parser>, Option, bool), Error = ParseError> + '_ +{ let record_constructor_pattern_arg_parser = choice(( select! {Token::Name {name} => name} .then_ignore(just(Token::Colon)) @@ -49,8 +52,9 @@ pub(crate) fn args( .allow_trailing() .then( just(Token::DotDot) - .then_ignore(just(Token::Comma).or_not()) .ignored() + .map_with_span(|_spread, span| span) + .then_ignore(just(Token::Comma).or_not()) .or_not(), ) .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); @@ -66,8 +70,9 @@ pub(crate) fn args( .allow_trailing() .then( just(Token::DotDot) - .then_ignore(just(Token::Comma).or_not()) .ignored() + .map_with_span(|_spread, span| span) + .then_ignore(just(Token::Comma).or_not()) .or_not(), ) .delimited_by(just(Token::LeftParen), just(Token::RightParen)); @@ -79,8 +84,8 @@ pub(crate) fn args( .or_not() .map(|opt_args| { opt_args - .map(|((a, b), c)| (a, b.is_some(), c)) - .unwrap_or_else(|| (vec![], false, false)) + .map(|((a, b), c)| (a, b, c)) + .unwrap_or_else(|| (vec![], None, false)) }) } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap index a3018425..ca51c154 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_basic.snap @@ -9,6 +9,6 @@ Constructor { arguments: [], module: None, constructor: (), - with_spread: false, + spread_location: None, 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 index cc14a5d4..010bdda3 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/constructor_module_select.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_module_select.snap @@ -11,6 +11,6 @@ Constructor { "module", ), constructor: (), - with_spread: false, + spread_location: None, tipo: (), } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_labels.snap b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_labels.snap index ed12efb8..9b546da6 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_labels.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_labels.snap @@ -30,6 +30,6 @@ Constructor { ], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_no_labels.snap b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_no_labels.snap index 0d04a988..6559c9b2 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_no_labels.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_no_labels.snap @@ -26,6 +26,6 @@ Constructor { ], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_pair_interleaved.snap b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_pair_interleaved.snap index 78af775c..2e146e84 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_pair_interleaved.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_pair_interleaved.snap @@ -39,6 +39,6 @@ Constructor { ], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_spread.snap b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_spread.snap index 499d0e00..39621844 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_spread.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_constructor_spread.snap @@ -20,6 +20,8 @@ Constructor { ], module: None, constructor: (), - with_spread: true, + spread_location: Some( + 9..11, + ), tipo: (), } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_pair_explicit_depth_1.snap b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_pair_explicit_depth_1.snap index 1b7a751a..6bba30ac 100644 --- a/crates/aiken-lang/src/parser/pattern/snapshots/pattern_pair_explicit_depth_1.snap +++ b/crates/aiken-lang/src/parser/pattern/snapshots/pattern_pair_explicit_depth_1.snap @@ -18,7 +18,7 @@ Pair { arguments: [], module: None, constructor: (), - with_spread: false, + spread_location: None, tipo: (), }, } diff --git a/crates/aiken-lang/src/parser/pattern/var.rs b/crates/aiken-lang/src/parser/pattern/var.rs index 145a9ba4..c278f825 100644 --- a/crates/aiken-lang/src/parser/pattern/var.rs +++ b/crates/aiken-lang/src/parser/pattern/var.rs @@ -18,7 +18,7 @@ pub fn parser( .or_not(), ) .map_with_span(|(name, opt_pattern), span| { - if let Some((c_name, (arguments, with_spread, is_record))) = opt_pattern { + if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern { UntypedPattern::Constructor { is_record, location: span, @@ -26,7 +26,7 @@ pub fn parser( arguments, module: Some(name), constructor: (), - with_spread, + spread_location, tipo: (), } } else { diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index 2b4162dd..d82e0270 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -749,6 +749,24 @@ fn format_int_uint() { ); } +#[test] +fn preserve_comment_in_record() { + assert_format!( + r#" + fn foo() { + let Output { + // something + address: own_address, + // value: own_value, + .. + } = own_output + + own_address + } + "# + ); +} + #[test] fn fail_expr() { assert_format!( diff --git a/crates/aiken-lang/src/tests/snapshots/preserve_comment_in_record.snap b/crates/aiken-lang/src/tests/snapshots/preserve_comment_in_record.snap new file mode 100644 index 00000000..294faef4 --- /dev/null +++ b/crates/aiken-lang/src/tests/snapshots/preserve_comment_in_record.snap @@ -0,0 +1,14 @@ +--- +source: crates/aiken-lang/src/tests/format.rs +description: "Code:\n\nfn foo() {\n let Output {\n // something\n address: own_address,\n // value: own_value,\n ..\n } = own_output\n\n own_address\n}\n" +--- +fn foo() { + let Output { + // something + address: own_address, + // value: own_value, + .. + } = own_output + + own_address +} diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 9ff18658..b29219fa 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -732,7 +732,7 @@ Perhaps, try the following: , constructor = name .if_supports_color(Stdout, |s| s.bright_blue()) .if_supports_color(Stdout, |s| s.bold()) - , suggestion = suggest_constructor_pattern(name, args, module, *with_spread) + , suggestion = suggest_constructor_pattern(name, args, module, *spread_location) ))] UnexpectedLabeledArgInPattern { #[label] @@ -741,7 +741,7 @@ Perhaps, try the following: name: String, args: Vec>, module: Option, - with_spread: bool, + spread_location: Option, }, #[error("I discovered a regular let assignment with multiple patterns.\n")] @@ -1210,7 +1210,7 @@ fn suggest_pattern( Some(format!( "Try instead: {}", Formatter::new() - .pattern_constructor(name, given, module, true, is_record) + .pattern_constructor(name, given, module, Some(Span::empty()), is_record) .to_pretty_string(70), )) } else { @@ -1239,7 +1239,7 @@ fn suggest_constructor_pattern( name: &str, args: &[CallArg], module: &Option, - with_spread: bool, + spread_location: Option, ) -> String { let fixed_args = args .iter() @@ -1251,7 +1251,7 @@ fn suggest_constructor_pattern( .collect::>(); Formatter::new() - .pattern_constructor(name, &fixed_args, module, with_spread, false) + .pattern_constructor(name, &fixed_args, module, spread_location, false) .to_pretty_string(70) } diff --git a/crates/aiken-lang/src/tipo/exhaustive.rs b/crates/aiken-lang/src/tipo/exhaustive.rs index a69dbf99..6507f79c 100644 --- a/crates/aiken-lang/src/tipo/exhaustive.rs +++ b/crates/aiken-lang/src/tipo/exhaustive.rs @@ -558,7 +558,7 @@ pub(super) fn simplify( arguments, location, tipo, - with_spread, + spread_location, constructor: super::PatternConstructor::Record { name, .. }, .. } => { @@ -589,7 +589,7 @@ pub(super) fn simplify( args.push(simplify(environment, &argument.value)?); } - if *with_spread { + if spread_location.is_some() { for _ in 0..(arity - arguments.len()) { args.push(Pattern::Wildcard) } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index a3c8c93e..6c2a2ee9 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -2608,7 +2608,7 @@ fn assert_assignment(expr: TypedExpr) -> Result { }, arguments: vec![], module: None, - with_spread: false, + spread_location: None, tipo: void(), }, kind: AssignmentKind::let_(), diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index a601ff6c..4ca8d562 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -344,7 +344,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { module, name, arguments: mut pattern_args, - with_spread, + spread_location, is_record, .. } => { @@ -360,7 +360,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { match cons.field_map() { // The fun has a field map so labelled arguments may be present and need to be reordered. Some(field_map) => { - if with_spread { + if spread_location.is_some() { // Using the spread operator when you have already provided variables for all of the // record's fields throws an error if pattern_args.len() == field_map.arity { @@ -416,7 +416,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { name: name.clone(), args: pattern_args.clone(), module: module.clone(), - with_spread, + spread_location, }) }) .unwrap_or(Ok(()))?, @@ -445,7 +445,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { match instantiated_constructor_type.deref() { Type::Fn { args, ret, .. } => { - if with_spread && has_no_fields { + if spread_location.is_some() && has_no_fields { if pattern_args.len() == args.len() { return Err(Error::UnnecessarySpreadOperator { location: Span { @@ -502,7 +502,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { name, arguments: pattern_args, constructor, - with_spread, + spread_location, tipo: instantiated_constructor_type, is_record, }) @@ -533,7 +533,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { name, arguments: vec![], constructor, - with_spread, + spread_location, tipo: instantiated_constructor_type, is_record, })