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:
parent
78770d14b7
commit
f307e214c3
|
@ -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 {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue