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>>,
module: Option<String>,
constructor: Constructor,
with_spread: bool,
spread_location: Option<Span>,
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`].
///
/// [`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,

View File

@ -1068,7 +1068,7 @@ impl<'comments> Formatter<'comments> {
name: &'a str,
args: &'a [CallArg<UntypedPattern>],
module: &'a Option<String>,
with_spread: bool,
spread_location: Option<Span>,
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::<Vec<_>>();
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<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> {
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<UntypedPattern>) -> 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<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> {
if length == 0 {
return match tail {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: (),
},
],

View File

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

View File

@ -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: (),
},
],

View File

@ -1,7 +1,7 @@
use chumsky::prelude::*;
use crate::{
ast::{CallArg, UntypedPattern},
ast::{CallArg, Span, UntypedPattern},
parser::{error::ParseError, token::Token},
};
@ -10,7 +10,8 @@ pub fn parser(
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
select! {Token::UpName { name } => name}
.then(args(expression))
.map_with_span(|(name, (arguments, with_spread, is_record)), location| {
.map_with_span(
|(name, (arguments, spread_location, is_record)), location| {
UntypedPattern::Constructor {
is_record,
location,
@ -18,15 +19,17 @@ pub fn parser(
arguments,
module: None,
constructor: (),
with_spread,
spread_location,
tipo: (),
}
})
},
)
}
pub(crate) fn args(
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((
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))
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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]
fn fail_expr() {
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
.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<CallArg<UntypedPattern>>,
module: Option<String>,
with_spread: bool,
spread_location: Option<Span>,
},
#[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<UntypedPattern>],
module: &Option<String>,
with_spread: bool,
spread_location: Option<Span>,
) -> String {
let fixed_args = args
.iter()
@ -1251,7 +1251,7 @@ fn suggest_constructor_pattern(
.collect::<Vec<_>>();
Formatter::new()
.pattern_constructor(name, &fixed_args, module, with_spread, false)
.pattern_constructor(name, &fixed_args, module, spread_location, false)
.to_pretty_string(70)
}

View File

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

View File

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

View File

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