mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 13:59:08 +00:00
fix arg comma messages
This commit is contained in:
parent
1a7fd57833
commit
c208f500d9
4 changed files with 370 additions and 35 deletions
|
@ -11,9 +11,9 @@ use crate::keyword;
|
|||
use crate::number_literal::number_literal;
|
||||
use crate::parser::{
|
||||
self, allocated, and_then_with_indent_level, ascii_char, ascii_string, attempt, backtrackable,
|
||||
fail, map, newline_char, not, not_followed_by, optional, sep_by1, specialize, specialize_ref,
|
||||
then, unexpected, unexpected_eof, word1, word2, EExpr, ELambda, Either, ParseResult, Parser,
|
||||
State, SyntaxError, When,
|
||||
fail, map, newline_char, not, not_followed_by, optional, sep_by1, sep_by1_e, specialize,
|
||||
specialize_ref, then, unexpected, unexpected_eof, word1, word2, EExpr, ELambda, Either,
|
||||
ParseResult, Parser, State, SyntaxError, When,
|
||||
};
|
||||
use crate::pattern::loc_closure_param;
|
||||
use crate::type_annotation;
|
||||
|
@ -991,10 +991,10 @@ fn closure_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
|
|||
word1(b'\\', ELambda::Start),
|
||||
// Once we see the '\', we're committed to parsing this as a closure.
|
||||
// It may turn out to be malformed, but it is definitely a closure.
|
||||
optional(and!(
|
||||
and!(
|
||||
// Parse the params
|
||||
// Params are comma-separated
|
||||
sep_by1(
|
||||
sep_by1_e(
|
||||
word1(b',', ELambda::Comma),
|
||||
space0_around_ee(
|
||||
specialize(ELambda::Pattern, loc_closure_param(min_indent)),
|
||||
|
@ -1002,7 +1002,8 @@ fn closure_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
|
|||
ELambda::Space,
|
||||
ELambda::IndentArg,
|
||||
ELambda::IndentArrow
|
||||
)
|
||||
),
|
||||
ELambda::Arg,
|
||||
),
|
||||
skip_first!(
|
||||
// Parse the -> which separates params from body
|
||||
|
@ -1018,16 +1019,13 @@ fn closure_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
|
|||
ELambda::IndentBody
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
),
|
||||
|arena: &'a Bump, opt_contents| match opt_contents {
|
||||
None => Expr::MalformedClosure,
|
||||
Some((params, loc_body)) => {
|
||||
let params: Vec<'a, Located<Pattern<'a>>> = params;
|
||||
let params: &'a [Located<Pattern<'a>>] = params.into_bump_slice();
|
||||
|arena: &'a Bump, (params, loc_body)| {
|
||||
let params: Vec<'a, Located<Pattern<'a>>> = params;
|
||||
let params: &'a [Located<Pattern<'a>>] = params.into_bump_slice();
|
||||
|
||||
Expr::Closure(params, arena.alloc(loc_body))
|
||||
}
|
||||
Expr::Closure(params, arena.alloc(loc_body))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1792,25 +1790,6 @@ fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<
|
|||
)
|
||||
}
|
||||
|
||||
/// This is mainly for matching tags in closure params, e.g. \@Foo -> ...
|
||||
pub fn private_tag<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
|
||||
map_with_arena!(
|
||||
skip_first!(ascii_char(b'@'), global_tag()),
|
||||
|arena: &'a Bump, name: &'a str| {
|
||||
let mut buf = String::with_capacity_in(1 + name.len(), arena);
|
||||
|
||||
buf.push('@');
|
||||
buf.push_str(name);
|
||||
buf.into_bump_str()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// This is mainly for matching tags in closure params, e.g. \Foo -> ...
|
||||
pub fn global_tag<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
|
||||
global_tag_or_ident(|first_char| first_char.is_uppercase())
|
||||
}
|
||||
|
||||
pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
|
||||
map!(crate::string_literal::parse(), Expr::Str)
|
||||
}
|
||||
|
|
|
@ -392,6 +392,7 @@ pub enum ELambda<'a> {
|
|||
Start(Row, Col),
|
||||
Arrow(Row, Col),
|
||||
Comma(Row, Col),
|
||||
Arg(Row, Col),
|
||||
// TODO make EEXpr
|
||||
Pattern(EPattern<'a>, Row, Col),
|
||||
Syntax(&'a SyntaxError<'a>, Row, Col),
|
||||
|
@ -1204,8 +1205,6 @@ where
|
|||
buf.push(next_output);
|
||||
}
|
||||
Err((element_progress, fail, state)) => {
|
||||
// If the delimiter parsed, but the following
|
||||
// element did not, that's a fatal error.
|
||||
return Err((element_progress, fail, state));
|
||||
}
|
||||
}
|
||||
|
@ -1233,6 +1232,84 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse one or more values separated by a delimiter (e.g. a comma) whose
|
||||
/// values are discarded
|
||||
pub fn sep_by1_e<'a, P, V, D, Val, Error>(
|
||||
delimiter: D,
|
||||
parser: P,
|
||||
to_element_error: V,
|
||||
) -> impl Parser<'a, Vec<'a, Val>, Error>
|
||||
where
|
||||
D: Parser<'a, (), Error>,
|
||||
P: Parser<'a, Val, Error>,
|
||||
V: Fn(Row, Col) -> Error,
|
||||
Error: 'a,
|
||||
{
|
||||
move |arena, state: State<'a>| {
|
||||
let start_bytes_len = state.bytes.len();
|
||||
|
||||
match parser.parse(arena, state) {
|
||||
Ok((progress, first_output, next_state)) => {
|
||||
debug_assert_eq!(progress, MadeProgress);
|
||||
let mut state = next_state;
|
||||
let mut buf = Vec::with_capacity_in(1, arena);
|
||||
|
||||
buf.push(first_output);
|
||||
|
||||
loop {
|
||||
match delimiter.parse(arena, state) {
|
||||
Ok((_, (), next_state)) => {
|
||||
// If the delimiter passed, check the element parser.
|
||||
match parser.parse(arena, next_state) {
|
||||
Ok((_, next_output, next_state)) => {
|
||||
state = next_state;
|
||||
buf.push(next_output);
|
||||
}
|
||||
Err((MadeProgress, fail, state)) => {
|
||||
return Err((MadeProgress, fail, state));
|
||||
}
|
||||
Err((NoProgress, _fail, state)) => {
|
||||
return Err((
|
||||
NoProgress,
|
||||
to_element_error(state.line, state.column),
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err((delim_progress, fail, old_state)) => {
|
||||
match delim_progress {
|
||||
MadeProgress => {
|
||||
// fail if the delimiter made progress
|
||||
return Err((MadeProgress, fail, old_state));
|
||||
}
|
||||
NoProgress => {
|
||||
let progress = Progress::from_lengths(
|
||||
start_bytes_len,
|
||||
old_state.bytes.len(),
|
||||
);
|
||||
return Ok((progress, buf, old_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err((MadeProgress, fail, state)) => {
|
||||
return Err((MadeProgress, fail, state));
|
||||
}
|
||||
Err((NoProgress, _fail, state)) => {
|
||||
return Err((
|
||||
NoProgress,
|
||||
to_element_error(state.line, state.column),
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fail_when_progress<T, E>(
|
||||
progress: Progress,
|
||||
fail: E,
|
||||
|
|
|
@ -173,10 +173,238 @@ fn to_expr_report<'a>(
|
|||
|
||||
match parse_problem {
|
||||
EExpr::When(when, row, col) => to_when_report(alloc, filename, context, &when, *row, *col),
|
||||
EExpr::Lambda(lambda, row, col) => {
|
||||
to_lambda_report(alloc, filename, context, &lambda, *row, *col)
|
||||
}
|
||||
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lambda_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
filename: PathBuf,
|
||||
context: Context,
|
||||
parse_problem: &roc_parse::parser::ELambda<'a>,
|
||||
start_row: Row,
|
||||
start_col: Col,
|
||||
) -> Report<'a> {
|
||||
use roc_parse::parser::ELambda;
|
||||
|
||||
match *parse_problem {
|
||||
ELambda::Arrow(row, col) => match what_is_next(alloc.src_lines, row, col) {
|
||||
Next::Token("=>") => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc
|
||||
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "WEIRD ARROW".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc
|
||||
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "MISSING ARROW".to_string(),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ELambda::Comma(row, col) => match what_is_next(alloc.src_lines, row, col) {
|
||||
Next::Token("=>") => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc
|
||||
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "WEIRD ARROW".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc
|
||||
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "MISSING ARROW".to_string(),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ELambda::Arg(row, col) => match what_is_next(alloc.src_lines, row, col) {
|
||||
Next::Other(Some(',')) => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc
|
||||
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck at this comma:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting an argument pattern before this, "),
|
||||
alloc.reflow("so try adding an argument before the comma and see if that helps?"),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNFINISHED ARGUMENT LIST".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc
|
||||
.reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I was expecting an argument pattern before this, "),
|
||||
alloc.reflow("so try adding an argument and see if that helps?"),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "MISSING ARROW".to_string(),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ELambda::Start(_row, _col) => unreachable!("another branch would have been taken"),
|
||||
|
||||
ELambda::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col),
|
||||
ELambda::Pattern(ref pattern, row, col) => {
|
||||
to_pattern_report(alloc, filename, pattern, row, col)
|
||||
}
|
||||
ELambda::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
|
||||
|
||||
ELambda::IndentArrow(row, col) => to_unfinished_lambda_report(
|
||||
alloc,
|
||||
filename,
|
||||
row,
|
||||
col,
|
||||
start_row,
|
||||
start_col,
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I just saw a pattern, so I was expecting to see a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
),
|
||||
|
||||
ELambda::IndentBody(row, col) => to_unfinished_lambda_report(
|
||||
alloc,
|
||||
filename,
|
||||
row,
|
||||
col,
|
||||
start_row,
|
||||
start_col,
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I just saw a pattern, so I was expecting to see a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
),
|
||||
|
||||
ELambda::IndentArg(row, col) => to_unfinished_lambda_report(
|
||||
alloc,
|
||||
filename,
|
||||
row,
|
||||
col,
|
||||
start_row,
|
||||
start_col,
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I just saw a pattern, so I was expecting to see a "),
|
||||
alloc.parser_suggestion("->"),
|
||||
alloc.reflow(" next."),
|
||||
]),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_unfinished_lambda_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
filename: PathBuf,
|
||||
row: Row,
|
||||
col: Col,
|
||||
start_row: Row,
|
||||
start_col: Col,
|
||||
message: RocDocBuilder<'a>,
|
||||
) -> Report<'a> {
|
||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||
let region = Region::from_row_col(row, col);
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I was partway through parsing a "),
|
||||
alloc.reflow(r" function, but I got stuck here:"),
|
||||
]),
|
||||
alloc.region_with_subregion(surroundings, region),
|
||||
message,
|
||||
// note_for_when_error(alloc),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNFINISHED FUNCTION".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_when_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
filename: PathBuf,
|
||||
|
@ -1635,6 +1863,7 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a>
|
|||
']' => Next::Close("square bracket", ']'),
|
||||
'}' => Next::Close("curly brace", '}'),
|
||||
'-' if it.next() == Some('>') => Next::Token("->"),
|
||||
'=' if it.next() == Some('>') => Next::Token("=>"),
|
||||
// _ if is_symbol(c) => todo!("it's an operator"),
|
||||
_ => Next::Other(Some(c)),
|
||||
},
|
||||
|
|
|
@ -4912,6 +4912,56 @@ mod test_reporting {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_double_comma() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\a,,b -> 1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED ARGUMENT LIST ────────────────────────────────────────────────────
|
||||
|
||||
I am in the middle of parsing a function argument list, but I got
|
||||
stuck at this comma:
|
||||
|
||||
1│ \,b -> 1
|
||||
^
|
||||
|
||||
I was expecting an argument pattern before this, so try adding an
|
||||
argument before the comma and see if that helps?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_leading_comma() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\,b -> 1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNFINISHED ARGUMENT LIST ────────────────────────────────────────────────────
|
||||
|
||||
I am in the middle of parsing a function argument list, but I got
|
||||
stuck at this comma:
|
||||
|
||||
1│ \,b -> 1
|
||||
^
|
||||
|
||||
I was expecting an argument pattern before this, so try adding an
|
||||
argument before the comma and see if that helps?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_outdented_branch() {
|
||||
// this should get better with time
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue