better indentation errors (use original location for error)

This commit is contained in:
Folkert 2021-02-12 13:47:56 +01:00
parent 21efa8cd71
commit 19d3e43f09
5 changed files with 241 additions and 38 deletions

View file

@ -444,6 +444,9 @@ where
let mut state = state; let mut state = state;
let mut any_newlines = false; let mut any_newlines = false;
let start_row = original_state.line;
let start_col = original_state.column;
let start_bytes_len = state.bytes.len(); let start_bytes_len = state.bytes.len();
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
@ -491,7 +494,13 @@ where
let progress = let progress =
Progress::from_lengths(start_bytes_len, state.bytes.len()); Progress::from_lengths(start_bytes_len, state.bytes.len());
state = state state = state
.check_indent_e(arena, min_indent, indent_problem) .check_indent_e(
arena,
min_indent,
indent_problem,
start_row,
start_col,
)
.map_err(|(fail, _)| { .map_err(|(fail, _)| {
(progress, fail, original_state.clone()) (progress, fail, original_state.clone())
})? })?
@ -523,7 +532,13 @@ where
); );
if any_newlines { if any_newlines {
state = state state = state
.check_indent_e(arena, min_indent, indent_problem) .check_indent_e(
arena,
min_indent,
indent_problem,
start_row,
start_col,
)
.map_err(|(fail, _)| { .map_err(|(fail, _)| {
(progress, fail, original_state.clone()) (progress, fail, original_state.clone())
})?; })?;
@ -692,7 +707,13 @@ where
progress, progress,
space_slice, space_slice,
state state
.check_indent_e(arena, min_indent, indent_problem) .check_indent_e(
arena,
min_indent,
indent_problem,
start_row,
start_col,
)
.map_err(|(fail, _)| (progress, fail, original_state))?, .map_err(|(fail, _)| (progress, fail, original_state))?,
)); ));
} }
@ -720,7 +741,7 @@ where
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len()); let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines { if any_newlines {
state = state state = state
.check_indent_e(arena, min_indent, indent_problem) .check_indent_e(arena, min_indent, indent_problem, start_row, start_col)
.map_err(|(fail, _)| (progress, fail, original_state))?; .map_err(|(fail, _)| (progress, fail, original_state))?;
} }

View file

@ -72,12 +72,14 @@ impl<'a> State<'a> {
_arena: &'a Bump, _arena: &'a Bump,
min_indent: u16, min_indent: u16,
to_error: TE, to_error: TE,
row: Row,
col: Col,
) -> Result<Self, (E, Self)> ) -> Result<Self, (E, Self)>
where where
TE: Fn(Row, Col) -> E, TE: Fn(Row, Col) -> E,
{ {
if self.indent_col < min_indent { if self.indent_col < min_indent {
Err((to_error(self.line, self.column), self)) Err((to_error(row, col), self))
} else { } else {
Ok(self) Ok(self)
} }
@ -378,6 +380,7 @@ pub enum Type<'a> {
TStart(Row, Col), TStart(Row, Col),
TEnd(Row, Col), TEnd(Row, Col),
TSpace(BadInputError, Row, Col), TSpace(BadInputError, Row, Col),
TFunctionArgument(Row, Col),
/// ///
TIndentStart(Row, Col), TIndentStart(Row, Col),
TIndentEnd(Row, Col), TIndentEnd(Row, Col),
@ -1496,6 +1499,31 @@ where
} }
} }
pub fn check_indent<'a, TE, E>(min_indent: u16, to_problem: TE) -> impl Parser<'a, (), E>
where
TE: Fn(Row, Col) -> E,
E: 'a,
{
move |_arena, state: State<'a>| {
dbg!(state.indent_col, min_indent);
if state.indent_col < min_indent {
Err((NoProgress, to_problem(state.line, state.column), state))
} else {
Ok((NoProgress, (), state))
}
}
}
#[macro_export]
macro_rules! word1_check_indent {
($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => {
and!(
word1($word, $word_problem),
crate::parser::check_indent($min_indent, $indent_problem)
)
};
}
#[allow(dead_code)] #[allow(dead_code)]
fn in_context<'a, AddContext, P1, P2, Start, A, X, Y>( fn in_context<'a, AddContext, P1, P2, Start, A, X, Y>(
add_context: AddContext, add_context: AddContext,

View file

@ -1,15 +1,13 @@
use crate::ast::{AssignedField, CommentOrNewline, Tag, TypeAnnotation}; use crate::ast::{AssignedField, CommentOrNewline, Tag, TypeAnnotation};
use crate::blankspace::{ use crate::blankspace::{space0_around_e, space0_before_e, space0_e};
space0, space0_around, space0_around_e, space0_before, space0_before_e, space0_e,
};
use crate::expr::{global_tag, private_tag}; use crate::expr::{global_tag, private_tag};
use crate::ident::join_module_parts; use crate::ident::join_module_parts;
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_string, backtrackable, not_e, optional, peek_utf8_char_e, allocated, backtrackable, not_e, optional, peek_utf8_char_e, specialize, specialize_ref, word1,
specialize, specialize_ref, word1, word2, BadInputError, Col, Either, ParseResult, Parser, word2, BadInputError, Either, ParseResult, Parser,
Progress::{self, *}, Progress::{self, *},
Row, State, SyntaxError, TApply, TInParens, TRecord, TTagUnion, TVariable, Type, State, SyntaxError, TApply, TInParens, TRecord, TTagUnion, TVariable, Type,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
@ -54,20 +52,6 @@ fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TT
} }
} }
fn check_indent<'a, TE, E>(min_indent: u16, to_problem: TE) -> impl Parser<'a, (), E>
where
TE: Fn(Row, Col) -> E,
E: 'a,
{
move |_arena, state: State<'a>| {
if state.indent_col < min_indent {
Err((NoProgress, to_problem(state.line, state.column), state))
} else {
Ok((NoProgress, (), state))
}
}
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, SyntaxError<'a>> { fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, SyntaxError<'a>> {
specialize(|x, _, _| SyntaxError::Type(x), term_help(min_indent)) specialize(|x, _, _| SyntaxError::Type(x), term_help(min_indent))
@ -247,7 +231,7 @@ fn record_type_field<'a>(
debug_assert_eq!(progress, MadeProgress); debug_assert_eq!(progress, MadeProgress);
let (_, spaces, state) = let (_, spaces, state) =
space0_e(min_indent, TRecord::Space, TRecord::IndentEnd).parse(arena, state)?; debug!(space0_e(min_indent, TRecord::Space, TRecord::IndentEnd)).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work. // Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.) // (This is true in both literals and types.)
@ -312,9 +296,11 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TReco
move |arena, state| { move |arena, state| {
let (_, (fields, final_comments), state) = collection_trailing_sep_e!( let (_, (fields, final_comments), state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
word1(b'{', TRecord::Open), word1(b'{', TRecord::Open),
loc!(record_type_field(min_indent)), loc!(record_type_field(min_indent)),
word1(b',', TRecord::End), word1(b',', TRecord::End),
// word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd),
word1(b'}', TRecord::End), word1(b'}', TRecord::End),
min_indent, min_indent,
TRecord::Open, TRecord::Open,
@ -377,13 +363,20 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
.parse(arena, state)?; .parse(arena, state)?;
let (p2, rest, state) = zero_or_more!(skip_first!( let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', Type::TStart), word1(b',', Type::TFunctionArgument),
one_of![
space0_around_e( space0_around_e(
term_help(min_indent), term_help(min_indent),
min_indent, min_indent,
Type::TSpace, Type::TSpace,
Type::TIndentStart Type::TIndentStart
) ),
|_, state: State<'a>| Err((
NoProgress,
Type::TFunctionArgument(state.line, state.column),
state
))
]
)) ))
.parse(arena, state)?; .parse(arena, state)?;

View file

@ -102,6 +102,24 @@ fn to_syntax_report<'a>(
report(doc) report(doc)
} }
SyntaxError::Eof(region) => {
let doc = alloc.stack(vec![alloc.reflow("End of Field"), alloc.region(*region)]);
Report {
filename,
doc,
title: "PARSE PROBLEM".to_string(),
}
}
SyntaxError::OutdentedTooFar => {
let doc = alloc.stack(vec![alloc.reflow("OutdentedTooFar")]);
Report {
filename,
doc,
title: "PARSE PROBLEM".to_string(),
}
}
Type(typ) => to_type_report(alloc, filename, &typ, 0, 0), Type(typ) => to_type_report(alloc, filename, &typ, 0, 0),
_ => todo!("unhandled parse error: {:?}", parse_problem), _ => todo!("unhandled parse error: {:?}", parse_problem),
} }
@ -126,6 +144,43 @@ fn to_type_report<'a>(
} }
Type::TApply(tapply, row, col) => to_tapply_report(alloc, filename, &tapply, *row, *col), Type::TApply(tapply, row, col) => to_tapply_report(alloc, filename, &tapply, *row, *col),
Type::TFunctionArgument(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 just started parsing a function argument type, but I encounterd two commas in a row:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow("Try removing one of them.")]),
]);
Report {
filename,
doc,
title: "DOUBLE COMMA".to_string(),
}
}
_ => todo!(),
},
Type::TStart(row, col) => {
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 just started parsing a type, but I got stuck here:"),
alloc.region_with_subregion(surroundings, region),
alloc.note("I may be confused by indentation"),
]);
Report {
filename,
doc,
title: "UNFINISHED TYPE".to_string(),
}
}
Type::TIndentStart(row, col) => { Type::TIndentStart(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_col(*row, *col); let region = Region::from_row_col(*row, *col);
@ -342,7 +397,7 @@ fn to_trecord_report<'a>(
} }
TRecord::IndentEnd(row, col) => { TRecord::IndentEnd(row, col) => {
match next_line_starts_with_close_curly(alloc.src_lines, row - 1) { match next_line_starts_with_close_curly(alloc.src_lines, row.saturating_sub(1)) {
Some((curly_row, curly_col)) => { Some((curly_row, curly_col)) => {
let surroundings = let surroundings =
Region::from_rows_cols(start_row, start_col, curly_row, curly_col); Region::from_rows_cols(start_row, start_col, curly_row, curly_col);

View file

@ -4246,7 +4246,6 @@ mod test_reporting {
I am partway through parsing a record type, but I got stuck here: I am partway through parsing a record type, but I got stuck here:
1 f : { 1 f : {
2 foo : I64,
^ ^
I was expecting to see a closing curly brace before this, so try I was expecting to see a closing curly brace before this, so try
@ -4589,7 +4588,114 @@ mod test_reporting {
I just started parsing an inline type alias, but I got stuck here: I just started parsing an inline type alias, but I got stuck here:
1 f : I64 as 1 f : I64 as
2 f = 0 ^
Note: I may be confused by indentation
"#
),
)
}
#[test]
fn type_double_comma() {
report_problem_as(
indoc!(
r#"
f : I64,,I64 -> I64
f = 0
f
"#
),
indoc!(
r#"
DOUBLE COMMA
I just started parsing a function argument type, but I encounterd two
commas in a row:
1 f : I64,,I64 -> I64
^
Try removing one of them.
"#
),
)
}
#[test]
fn type_argument_no_arrow() {
report_problem_as(
indoc!(
r#"
f : I64, I64
f = 0
f
"#
),
indoc!(
r#"
UNFINISHED TYPE
I just started parsing a type, but I got stuck here:
1 f : I64, I64
^
Note: I may be confused by indentation
"#
),
)
}
#[test]
fn type_argument_arrow_then_nothing() {
// TODO could do better by pointing out we're parsing a function type
report_problem_as(
indoc!(
r#"
f : I64, I64 ->
f = 0
f
"#
),
indoc!(
r#"
UNFINISHED TYPE
I just started parsing a type, but I got stuck here:
1 f : I64, I64 ->
^
Note: I may be confused by indentation
"#
),
)
}
#[test]
fn foobar() {
// TODO fix error on new row
// we should make whitespace only consumed when it puts us in a validly-indented position
report_problem_as(
indoc!(
r#"
f : I64 ->
f = 0
f
"#
),
indoc!(
r#"
UNFINISHED TYPE
I just started parsing a type, but I got stuck here:
1 f : I64 ->
^ ^
Note: I may be confused by indentation Note: I may be confused by indentation