fix: comments in record patterns closes #946

This commit is contained in:
rvcas 2024-05-22 12:26:32 -04:00
parent c16bd06e97
commit 7f38b55c1c
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
25 changed files with 172 additions and 100 deletions

View File

@ -1243,7 +1243,7 @@ pub enum Pattern<Constructor, Type> {
arguments: Vec<CallArg<Self>>, arguments: Vec<CallArg<Self>>,
module: Option<String>, module: Option<String>,
constructor: Constructor, constructor: Constructor,
with_spread: bool, spread_location: Option<Span>,
tipo: Type, tipo: Type,
}, },
@ -1273,6 +1273,15 @@ impl<A, B> Pattern<A, B> {
} }
} }
pub fn with_spread(&self) -> bool {
match self {
Pattern::Constructor {
spread_location, ..
} => spread_location.is_some(),
_ => false,
}
}
/// Returns `true` if the pattern is [`Discard`]. /// Returns `true` if the pattern is [`Discard`].
/// ///
/// [`Discard`]: Pattern::Discard /// [`Discard`]: Pattern::Discard
@ -1295,7 +1304,7 @@ impl UntypedPattern {
name: "True".to_string(), name: "True".to_string(),
arguments: vec![], arguments: vec![],
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
module: None, module: None,
is_record: false, is_record: false,

View File

@ -1068,7 +1068,7 @@ impl<'comments> Formatter<'comments> {
name: &'a str, name: &'a str,
args: &'a [CallArg<UntypedPattern>], args: &'a [CallArg<UntypedPattern>],
module: &'a Option<String>, module: &'a Option<String>,
with_spread: bool, spread_location: Option<Span>,
is_record: bool, is_record: bool,
) -> Document<'a> { ) -> Document<'a> {
fn is_breakable(expr: &UntypedPattern) -> bool { fn is_breakable(expr: &UntypedPattern) -> bool {
@ -1086,7 +1086,7 @@ impl<'comments> Formatter<'comments> {
None => name.to_doc(), None => name.to_doc(),
}; };
if args.is_empty() && with_spread { if args.is_empty() && spread_location.is_some() {
if is_record { if is_record {
name.append(" { .. }") name.append(" { .. }")
// TODO: not possible // TODO: not possible
@ -1095,11 +1095,16 @@ impl<'comments> Formatter<'comments> {
} }
} else if args.is_empty() { } else if args.is_empty() {
name 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::<Vec<_>>();
let wrapped_args = if is_record { 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 { } 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) 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<Item = Document<'a>>,
{
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<Item = Document<'a>>,
{
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<UntypedExpr>]) -> Document<'a> { fn call<'a>(&mut self, fun: &'a UntypedExpr, args: &'a [CallArg<UntypedExpr>]) -> Document<'a> {
let is_constr = match fun { let is_constr = match fun {
UntypedExpr::Var { name, .. } => name[0..1].chars().all(|c| c.is_uppercase()), UntypedExpr::Var { name, .. } => name[0..1].chars().all(|c| c.is_uppercase()),
@ -1821,26 +1868,31 @@ impl<'comments> Formatter<'comments> {
name, name,
arguments: args, arguments: args,
module, module,
with_spread, spread_location,
is_record, 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) commented(doc, comments)
} }
fn pattern_call_arg<'a>(&mut self, arg: &'a CallArg<UntypedPattern>) -> Document<'a> { fn pattern_call_arg<'a>(&mut self, arg: &'a CallArg<UntypedPattern>) -> Document<'a> {
let comments = self.pop_comments(arg.location.start);
if let (UntypedPattern::Var { name, .. }, Some(label)) = (&arg.value, &arg.label) { if let (UntypedPattern::Var { name, .. }, Some(label)) = (&arg.value, &arg.label) {
if name == label { if name == label {
return self.pattern(&arg.value); return self.pattern(&arg.value);
} }
} }
arg.label let doc = arg
.label
.as_ref() .as_ref()
.map(|s| s.to_doc().append(": ")) .map(|s| s.to_doc().append(": "))
.unwrap_or_else(nil) .unwrap_or_else(nil)
.append(self.pattern(&arg.value)) .append(self.pattern(&arg.value));
commented(doc, comments)
} }
pub fn clause_guard_bin_op<'a>( pub fn clause_guard_bin_op<'a>(
@ -2029,44 +2081,6 @@ where
.append(line()) .append(line())
} }
pub fn wrap_args_with_spread<'a, I>(args: I) -> Document<'a>
where
I: IntoIterator<Item = Document<'a>>,
{
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<Item = Document<'a>>,
{
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>>) -> Document<'a> { fn list<'a>(elements: Document<'a>, length: usize, tail: Option<Document<'a>>) -> Document<'a> {
if length == 0 { if length == 0 {
return match tail { return match tail {

View File

@ -23,7 +23,7 @@ Test(
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
}, },
annotation: None, annotation: None,

View File

@ -30,7 +30,7 @@ Assignment {
], ],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
}, },
annotation: None, annotation: None,

View File

@ -29,7 +29,7 @@ Assignment {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
}, },
annotation: None, annotation: None,

View File

@ -41,7 +41,7 @@ Assignment {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
}, },
annotation: None, annotation: None,

View File

@ -20,7 +20,7 @@ Assignment {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
}, },
annotation: None, annotation: None,

View File

@ -19,7 +19,9 @@ When {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: true, spread_location: Some(
21..23,
),
tipo: (), tipo: (),
}, },
], ],
@ -46,7 +48,9 @@ When {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: true, spread_location: Some(
40..42,
),
tipo: (), tipo: (),
}, },
], ],

View File

@ -19,7 +19,9 @@ When {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: true, spread_location: Some(
21..23,
),
tipo: (), tipo: (),
}, },
], ],

View File

@ -19,7 +19,9 @@ When {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: true, spread_location: Some(
21..23,
),
tipo: (), tipo: (),
}, },
], ],
@ -39,7 +41,9 @@ When {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: true, spread_location: Some(
40..42,
),
tipo: (), tipo: (),
}, },
], ],

View File

@ -1,7 +1,7 @@
use chumsky::prelude::*; use chumsky::prelude::*;
use crate::{ use crate::{
ast::{CallArg, UntypedPattern}, ast::{CallArg, Span, UntypedPattern},
parser::{error::ParseError, token::Token}, parser::{error::ParseError, token::Token},
}; };
@ -10,23 +10,26 @@ pub fn parser(
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ { ) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
select! {Token::UpName { name } => name} select! {Token::UpName { name } => name}
.then(args(expression)) .then(args(expression))
.map_with_span(|(name, (arguments, with_spread, is_record)), location| { .map_with_span(
UntypedPattern::Constructor { |(name, (arguments, spread_location, is_record)), location| {
is_record, UntypedPattern::Constructor {
location, is_record,
name, location,
arguments, name,
module: None, arguments,
constructor: (), module: None,
with_spread, constructor: (),
tipo: (), spread_location,
} tipo: (),
}) }
},
)
} }
pub(crate) fn args( pub(crate) fn args(
expression: Recursive<'_, Token, UntypedPattern, ParseError>, expression: Recursive<'_, Token, UntypedPattern, ParseError>,
) -> impl Parser<Token, (Vec<CallArg<UntypedPattern>>, bool, bool), Error = ParseError> + '_ { ) -> impl Parser<Token, (Vec<CallArg<UntypedPattern>>, Option<Span>, bool), Error = ParseError> + '_
{
let record_constructor_pattern_arg_parser = choice(( let record_constructor_pattern_arg_parser = choice((
select! {Token::Name {name} => name} select! {Token::Name {name} => name}
.then_ignore(just(Token::Colon)) .then_ignore(just(Token::Colon))
@ -49,8 +52,9 @@ pub(crate) fn args(
.allow_trailing() .allow_trailing()
.then( .then(
just(Token::DotDot) just(Token::DotDot)
.then_ignore(just(Token::Comma).or_not())
.ignored() .ignored()
.map_with_span(|_spread, span| span)
.then_ignore(just(Token::Comma).or_not())
.or_not(), .or_not(),
) )
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); .delimited_by(just(Token::LeftBrace), just(Token::RightBrace));
@ -66,8 +70,9 @@ pub(crate) fn args(
.allow_trailing() .allow_trailing()
.then( .then(
just(Token::DotDot) just(Token::DotDot)
.then_ignore(just(Token::Comma).or_not())
.ignored() .ignored()
.map_with_span(|_spread, span| span)
.then_ignore(just(Token::Comma).or_not())
.or_not(), .or_not(),
) )
.delimited_by(just(Token::LeftParen), just(Token::RightParen)); .delimited_by(just(Token::LeftParen), just(Token::RightParen));
@ -79,8 +84,8 @@ pub(crate) fn args(
.or_not() .or_not()
.map(|opt_args| { .map(|opt_args| {
opt_args opt_args
.map(|((a, b), c)| (a, b.is_some(), c)) .map(|((a, b), c)| (a, b, c))
.unwrap_or_else(|| (vec![], false, false)) .unwrap_or_else(|| (vec![], None, false))
}) })
} }

View File

@ -9,6 +9,6 @@ Constructor {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
} }

View File

@ -11,6 +11,6 @@ Constructor {
"module", "module",
), ),
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
} }

View File

@ -30,6 +30,6 @@ Constructor {
], ],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
} }

View File

@ -26,6 +26,6 @@ Constructor {
], ],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
} }

View File

@ -39,6 +39,6 @@ Constructor {
], ],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
} }

View File

@ -20,6 +20,8 @@ Constructor {
], ],
module: None, module: None,
constructor: (), constructor: (),
with_spread: true, spread_location: Some(
9..11,
),
tipo: (), tipo: (),
} }

View File

@ -18,7 +18,7 @@ Pair {
arguments: [], arguments: [],
module: None, module: None,
constructor: (), constructor: (),
with_spread: false, spread_location: None,
tipo: (), tipo: (),
}, },
} }

View File

@ -18,7 +18,7 @@ pub fn parser(
.or_not(), .or_not(),
) )
.map_with_span(|(name, opt_pattern), span| { .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 { UntypedPattern::Constructor {
is_record, is_record,
location: span, location: span,
@ -26,7 +26,7 @@ pub fn parser(
arguments, arguments,
module: Some(name), module: Some(name),
constructor: (), constructor: (),
with_spread, spread_location,
tipo: (), tipo: (),
} }
} else { } else {

View File

@ -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] #[test]
fn fail_expr() { fn fail_expr() {
assert_format!( assert_format!(

View File

@ -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
}

View File

@ -732,7 +732,7 @@ Perhaps, try the following:
, constructor = name , constructor = name
.if_supports_color(Stdout, |s| s.bright_blue()) .if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold()) .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 { UnexpectedLabeledArgInPattern {
#[label] #[label]
@ -741,7 +741,7 @@ Perhaps, try the following:
name: String, name: String,
args: Vec<CallArg<UntypedPattern>>, args: Vec<CallArg<UntypedPattern>>,
module: Option<String>, module: Option<String>,
with_spread: bool, spread_location: Option<Span>,
}, },
#[error("I discovered a regular let assignment with multiple patterns.\n")] #[error("I discovered a regular let assignment with multiple patterns.\n")]
@ -1210,7 +1210,7 @@ fn suggest_pattern(
Some(format!( Some(format!(
"Try instead: {}", "Try instead: {}",
Formatter::new() Formatter::new()
.pattern_constructor(name, given, module, true, is_record) .pattern_constructor(name, given, module, Some(Span::empty()), is_record)
.to_pretty_string(70), .to_pretty_string(70),
)) ))
} else { } else {
@ -1239,7 +1239,7 @@ fn suggest_constructor_pattern(
name: &str, name: &str,
args: &[CallArg<UntypedPattern>], args: &[CallArg<UntypedPattern>],
module: &Option<String>, module: &Option<String>,
with_spread: bool, spread_location: Option<Span>,
) -> String { ) -> String {
let fixed_args = args let fixed_args = args
.iter() .iter()
@ -1251,7 +1251,7 @@ fn suggest_constructor_pattern(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Formatter::new() Formatter::new()
.pattern_constructor(name, &fixed_args, module, with_spread, false) .pattern_constructor(name, &fixed_args, module, spread_location, false)
.to_pretty_string(70) .to_pretty_string(70)
} }

View File

@ -558,7 +558,7 @@ pub(super) fn simplify(
arguments, arguments,
location, location,
tipo, tipo,
with_spread, spread_location,
constructor: super::PatternConstructor::Record { name, .. }, constructor: super::PatternConstructor::Record { name, .. },
.. ..
} => { } => {
@ -589,7 +589,7 @@ pub(super) fn simplify(
args.push(simplify(environment, &argument.value)?); args.push(simplify(environment, &argument.value)?);
} }
if *with_spread { if spread_location.is_some() {
for _ in 0..(arity - arguments.len()) { for _ in 0..(arity - arguments.len()) {
args.push(Pattern::Wildcard) args.push(Pattern::Wildcard)
} }

View File

@ -2608,7 +2608,7 @@ fn assert_assignment(expr: TypedExpr) -> Result<TypedExpr, Error> {
}, },
arguments: vec![], arguments: vec![],
module: None, module: None,
with_spread: false, spread_location: None,
tipo: void(), tipo: void(),
}, },
kind: AssignmentKind::let_(), kind: AssignmentKind::let_(),

View File

@ -344,7 +344,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
module, module,
name, name,
arguments: mut pattern_args, arguments: mut pattern_args,
with_spread, spread_location,
is_record, is_record,
.. ..
} => { } => {
@ -360,7 +360,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
match cons.field_map() { match cons.field_map() {
// The fun has a field map so labelled arguments may be present and need to be reordered. // The fun has a field map so labelled arguments may be present and need to be reordered.
Some(field_map) => { 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 // Using the spread operator when you have already provided variables for all of the
// record's fields throws an error // record's fields throws an error
if pattern_args.len() == field_map.arity { if pattern_args.len() == field_map.arity {
@ -416,7 +416,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
name: name.clone(), name: name.clone(),
args: pattern_args.clone(), args: pattern_args.clone(),
module: module.clone(), module: module.clone(),
with_spread, spread_location,
}) })
}) })
.unwrap_or(Ok(()))?, .unwrap_or(Ok(()))?,
@ -445,7 +445,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
match instantiated_constructor_type.deref() { match instantiated_constructor_type.deref() {
Type::Fn { args, ret, .. } => { Type::Fn { args, ret, .. } => {
if with_spread && has_no_fields { if spread_location.is_some() && has_no_fields {
if pattern_args.len() == args.len() { if pattern_args.len() == args.len() {
return Err(Error::UnnecessarySpreadOperator { return Err(Error::UnnecessarySpreadOperator {
location: Span { location: Span {
@ -502,7 +502,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
name, name,
arguments: pattern_args, arguments: pattern_args,
constructor, constructor,
with_spread, spread_location,
tipo: instantiated_constructor_type, tipo: instantiated_constructor_type,
is_record, is_record,
}) })
@ -533,7 +533,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
name, name,
arguments: vec![], arguments: vec![],
constructor, constructor,
with_spread, spread_location,
tipo: instantiated_constructor_type, tipo: instantiated_constructor_type,
is_record, is_record,
}) })