Remove parse error on bytearray literals for trace, todo & error, parse as String instead.

This has been bothering me and the more I thought of it the more I
  disliked the idea of a warning. The rationale being that in this very
  context, there's absolutely no ambiguity. So it is only frustrating
  that the parser is even able to make the exact suggestion of what
  should be fixed, but still fails.

  I can imagine it is going to be very common for people to type:

  ```
  trace "foo"
  ```

  ...yet terribly frustrating if they have to remember each time that
  this should actually be a string. Because of the `trace`, `todo` and
  `error` keywords, we know exactly the surrounding context and what to
  expect here. So we can work it nicely.

  However, the formatter will re-format it to:

  ```
  trace @"foo"
  ```

  Just for the sake of remaining consistent with the type-system. This
  way, we still only manipulate `String` in the AST, but we conveniently
  parse a double-quote utf-8 literal when coupled with one of the
  specific keywords.

  I believe that's the best of both worlds.
This commit is contained in:
KtorZ 2023-02-19 09:47:06 +01:00
parent 78770d14b7
commit f307e214c3
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 66 additions and 97 deletions

View File

@ -533,46 +533,22 @@ pub fn anon_fn_param_parser() -> impl Parser<Token, ast::UntypedArg, Error = Par
}) })
} }
fn unexpected_bytearray_literal( // Interpret bytearray string literals written as utf-8 strings, as strings.
token: Token, //
expr: &expr::UntypedExpr, // This is mostly convenient so that todo & error works with either @"..." or plain "...".
) -> Option<(ParseError, String)> { // In this particular context, there's actually no ambiguity about the right-hand-side, so
// we can provide this syntactic sugar.
fn flexible_string_literal(expr: expr::UntypedExpr) -> expr::UntypedExpr {
match expr { match expr {
expr::UntypedExpr::ByteArray { expr::UntypedExpr::ByteArray {
bytes,
preferred_format: ByteArrayFormatPreference::Utf8String, preferred_format: ByteArrayFormatPreference::Utf8String,
bytes,
location, location,
.. } => expr::UntypedExpr::String {
} => { location,
let literal = String::from_utf8(bytes.clone()).unwrap(); value: String::from_utf8(bytes).unwrap(),
},
Some(( _ => expr,
ParseError::unexpected_bytearray_literal(*location, token, literal.clone()),
literal,
))
}
_ => None,
}
}
fn validate_error_todo(
token: Token,
reason: Option<expr::UntypedExpr>,
emit: &mut dyn FnMut(ParseError),
) -> Option<expr::UntypedExpr> {
if let Some(reason) = reason {
match unexpected_bytearray_literal(token, &reason) {
None => Some(reason),
Some((err, value)) => {
emit(err);
Some(expr::UntypedExpr::String {
location: reason.location(),
value,
})
}
}
} else {
reason
} }
} }
@ -582,35 +558,22 @@ pub fn expr_seq_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseE
just(Token::Trace) just(Token::Trace)
.ignore_then(expr_parser(r.clone())) .ignore_then(expr_parser(r.clone()))
.then(r.clone()) .then(r.clone())
.validate(|(text, then), _span, emit| {
match unexpected_bytearray_literal(Token::Trace, &text) {
None => (text, then),
Some((err, value)) => {
emit(err);
(
expr::UntypedExpr::String {
location: text.location(),
value,
},
then,
)
}
}
})
.map_with_span(|(text, then_), span| expr::UntypedExpr::Trace { .map_with_span(|(text, then_), span| expr::UntypedExpr::Trace {
kind: TraceKind::Trace, kind: TraceKind::Trace,
location: span, location: span,
then: Box::new(then_), then: Box::new(then_),
text: Box::new(text), text: Box::new(flexible_string_literal(text)),
}), }),
just(Token::ErrorTerm) just(Token::ErrorTerm)
.ignore_then(expr_parser(r.clone()).or_not()) .ignore_then(expr_parser(r.clone()).or_not())
.validate(|reason, _span, emit| validate_error_todo(Token::ErrorTerm, reason, emit)) .map_with_span(|reason, span| {
.map_with_span(|reason, span| expr::UntypedExpr::error(span, reason)), expr::UntypedExpr::error(span, reason.map(flexible_string_literal))
}),
just(Token::Todo) just(Token::Todo)
.ignore_then(expr_parser(r.clone()).or_not()) .ignore_then(expr_parser(r.clone()).or_not())
.validate(|reason, _span, emit| validate_error_todo(Token::Todo, reason, emit)) .map_with_span(|reason, span| {
.map_with_span(|reason, span| expr::UntypedExpr::todo(span, reason)), expr::UntypedExpr::todo(span, reason.map(flexible_string_literal))
}),
expr_parser(r.clone()) expr_parser(r.clone())
.then(r.repeated()) .then(r.repeated())
.foldl(|current, next| current.append_in_sequence(next)), .foldl(|current, next| current.append_in_sequence(next)),
@ -984,18 +947,18 @@ pub fn expr_parser(
.then_ignore(one_of(Token::RArrow).not().rewind()) .then_ignore(one_of(Token::RArrow).not().rewind())
.or_not(), .or_not(),
) )
.validate(|reason, _span, emit| validate_error_todo(Token::Todo, reason, emit)) .map_with_span(|reason, span| {
.map_with_span(|reason, span| expr::UntypedExpr::todo(span, reason)), expr::UntypedExpr::todo(span, reason.map(flexible_string_literal))
}),
just(Token::ErrorTerm) just(Token::ErrorTerm)
.ignore_then( .ignore_then(
r.clone() r.clone()
.then_ignore(just(Token::RArrow).not().rewind()) .then_ignore(just(Token::RArrow).not().rewind())
.or_not(), .or_not(),
) )
.validate(|reason, _span, emit| { .map_with_span(|reason, span| {
validate_error_todo(Token::ErrorTerm, reason, emit) expr::UntypedExpr::error(span, reason.map(flexible_string_literal))
}) }),
.map_with_span(|reason, span| expr::UntypedExpr::error(span, reason)),
))) )))
.map_with_span( .map_with_span(
|(((patterns, alternative_patterns_opt), guard), then), span| ast::UntypedClause { |(((patterns, alternative_patterns_opt), guard), then), span| ast::UntypedClause {

View File

@ -55,16 +55,6 @@ impl ParseError {
label: None, label: None,
} }
} }
pub fn unexpected_bytearray_literal(span: Span, keyword: Token, literal: String) -> Self {
Self {
kind: ErrorKind::UnexpectedByteArrayLiteral(keyword, literal),
span,
while_parsing: None,
expected: HashSet::new(),
label: Some("use @"),
}
}
} }
impl PartialEq for ParseError { impl PartialEq for ParseError {
@ -159,29 +149,6 @@ pub enum ErrorKind {
, bad = "✖️".red() , bad = "✖️".red()
}))] }))]
InvalidWhenClause, InvalidWhenClause,
#[error(
"I noticed a {} literal where I would expect a {}.",
"ByteArray".bold().bright_blue(),
"String".bold().bright_blue(),
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#string"))]
#[diagnostic(help("{}", formatdoc! {
r#"The keyword {keyword} accepts a {type_String} as argument. A {type_String} literal is denoted by {syntax}. Simple double-quotes are interpreted as {type_ByteArray} of UTF-8 encoded bytes. Juggling between {type_ByteArray} and {type_String} can be a little confusing.
In this instance, you probably meant to write the following:
{keyword} {symbol_at}{symbol_doublequotes}{literal}{symbol_doublequotes}
"#,
type_String = "String".bold().bright_blue(),
type_ByteArray = "ByteArray".bold().bright_blue(),
symbol_at = "@".purple(),
symbol_doublequotes = "\"".purple(),
syntax = format!("{}{}", "@".purple(), "\"...\"".purple()),
keyword = format!("{}", .0).replace('"', "").bold(),
literal = .1.purple(),
}))]
UnexpectedByteArrayLiteral(Token, String),
} }
#[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] #[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)]

View File

@ -344,7 +344,7 @@ fn test_nested_function_calls() {
), ),
when output.datum is { when output.datum is {
InlineDatum(_) -> True InlineDatum(_) -> True
_ -> error "expected inline datum" _ -> error @"expected inline datum"
}, },
] ]
|> list.and |> list.and
@ -382,7 +382,33 @@ fn format_trace_todo_error() {
} }
"#}; "#};
assert_fmt(src, src); let out = indoc! {r#"
fn foo_1() {
todo
}
fn foo_2() {
todo @"my custom message"
}
fn foo_3() {
when x is {
Foo -> True
_ -> error
}
}
fn foo_4() {
if 14 == 42 {
error @"I don't think so"
} else {
trace @"been there"
True
}
}
"#};
assert_fmt(src, out);
} }
#[test] #[test]
@ -508,3 +534,16 @@ fn test_bytearray_literals() {
assert_fmt(src, src); assert_fmt(src, src);
} }
#[test]
fn test_string_literal() {
let src = indoc! {r#"
const foo_const: String = @"foo"
fn foo() {
let foo_var: String = @"foo"
}
"#};
assert_fmt(src, src);
}