fix: comments in record patterns closes #946
This commit is contained in:
parent
c16bd06e97
commit
7f38b55c1c
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -23,7 +23,7 @@ Test(
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
annotation: None,
|
||||
|
|
|
@ -30,7 +30,7 @@ Assignment {
|
|||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
annotation: None,
|
||||
|
|
|
@ -29,7 +29,7 @@ Assignment {
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
annotation: None,
|
||||
|
|
|
@ -41,7 +41,7 @@ Assignment {
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
annotation: None,
|
||||
|
|
|
@ -20,7 +20,7 @@ Assignment {
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
annotation: None,
|
||||
|
|
|
@ -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: (),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -19,7 +19,9 @@ When {
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: true,
|
||||
spread_location: Some(
|
||||
21..23,
|
||||
),
|
||||
tipo: (),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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: (),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,6 @@ Constructor {
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@ Constructor {
|
|||
"module",
|
||||
),
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
}
|
||||
|
|
|
@ -30,6 +30,6 @@ Constructor {
|
|||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
}
|
||||
|
|
|
@ -26,6 +26,6 @@ Constructor {
|
|||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
}
|
||||
|
|
|
@ -39,6 +39,6 @@ Constructor {
|
|||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ Constructor {
|
|||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: true,
|
||||
spread_location: Some(
|
||||
9..11,
|
||||
),
|
||||
tipo: (),
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ Pair {
|
|||
arguments: [],
|
||||
module: None,
|
||||
constructor: (),
|
||||
with_spread: false,
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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_(),
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue