diff --git a/src/fmt/expr.rs b/src/fmt/expr.rs index d6aa509572..0ee15c89ed 100644 --- a/src/fmt/expr.rs +++ b/src/fmt/expr.rs @@ -173,6 +173,25 @@ pub fn fmt_expr<'a>( } } } + List(loc_items) => { + buf.push('['); + + let mut iter = loc_items.iter().peekable(); + + while let Some(item) = iter.next() { + buf.push(' '); + fmt_expr(buf, &item.value, indent, false, true); + + if iter.peek().is_some() { + buf.push(','); + } + } + + if !loc_items.is_empty() { + buf.push(' '); + } + buf.push(']'); + } other => panic!("TODO implement Display for AST variant {:?}", other), } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index a2bf54a922..423fc3b5ba 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -652,17 +652,28 @@ macro_rules! skip_second { #[macro_export] macro_rules! collection { ($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr) => { - // TODO allow trailing commas before the closing delimiter, *but* without - // losing any comments or newlines! This will require parsing them and then, - // if they are present, merging them into the final Spaceable. skip_first!( $opening_brace, - skip_second!( - $crate::parse::parser::sep_by0( - $delimiter, - $crate::parse::blankspace::space0_around($elem, $min_indent) - ), - $closing_brace + skip_first!( + // We specifically allow space characters inside here, so that + // `[ ]` can be successfully parsed as an empty list, and then + // changed by the formatter back into `[]`. + // + // We don't allow newlines or comments in the middle of empty + // collections because those are normally stored in an Expr, + // and there's no Expr in which to store them in an empty collection! + // + // We could change the AST to add extra storage specifically to + // support empty literals containing newlines or comments, but this + // does not seem worth even the tiniest regression in compiler performance. + zero_or_more!(char(' ')), + skip_second!( + $crate::parse::parser::sep_by0( + $delimiter, + $crate::parse::blankspace::space0_around($elem, $min_indent) + ), + $closing_brace + ) ) ) }; diff --git a/src/subs.rs b/src/subs.rs index e9855f10ad..14ccdba33b 100644 --- a/src/subs.rs +++ b/src/subs.rs @@ -235,15 +235,7 @@ impl Subs { if desc.copy.is_some() { let content = desc.content; - self.set( - var, - Descriptor { - content: content.clone(), - rank: Rank::NONE, - mark: Mark::NONE, - copy: None, - }, - ); + self.set(var, content.clone().into()); restore_content(self, &content); } diff --git a/tests/test_format.rs b/tests/test_format.rs index 1ff5e0b1dd..d95a20cb61 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -540,6 +540,24 @@ mod test_format { )); } + // LIST + #[test] + fn empty_list() { + expr_formats_same("[]"); + expr_formats_to("[ ]", "[]"); + } + + #[test] + fn one_item_list() { + expr_formats_same(indoc!("[ 4 ] ")); + } + + #[test] + fn two_item_list() { + expr_formats_same(indoc!("[ 7, 8 ] ")); + expr_formats_to(indoc!("[ 7 , 8 ] "), indoc!("[ 7, 8 ] ")); + } + // RECORD LITERALS #[test] diff --git a/tests/test_parse.rs b/tests/test_parse.rs index 96aed45c41..3fad690b08 100644 --- a/tests/test_parse.rs +++ b/tests/test_parse.rs @@ -669,6 +669,17 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn spaces_inside_empty_list() { + // This is a regression test! + let arena = Bump::new(); + let elems = Vec::new_in(&arena); + let expected = List(elems); + let actual = parse_with(&arena, "[ ]"); + + assert_eq!(Ok(expected), actual); + } + #[test] fn packed_singleton_list() { let arena = Bump::new();