Merge pull request #6732 from roc-lang/import-parse-reporting

Friendly reporting of import syntax errors
This commit is contained in:
Ayaz 2024-05-07 21:29:58 -05:00 committed by GitHub
commit 010aed88f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 517 additions and 116 deletions

View File

@ -4914,6 +4914,176 @@ mod test_reporting {
" "
); );
test_report!(
unfinished_import,
indoc!(
r"
import [
"
),
@r###"
UNFINISHED IMPORT in tmp/unfinished_import/Test.roc
I was partway through parsing an `import`, but I got stuck here:
4 import [
^
I was expecting to see a module name, like:
import BigNum
Or a package module name, like:
import pf.Stdout
Or a file path to ingest, like:
import "users.json" as users : Str
"###
);
test_report!(
unfinished_import_as_or_exposing,
indoc!(
r"
import svg.Path a
"
),
@r###"
UNFINISHED IMPORT in tmp/unfinished_import_as_or_exposing/Test.roc
I was partway through parsing an `import`, but I got stuck here:
4 import svg.Path a
^
I was expecting to see the `as` keyword, like:
import svg.Path as SvgPath
Or the `exposing` keyword, like:
import svg.Path exposing [arc, rx]
"###
);
test_report!(
unfinished_import_alias,
indoc!(
r"
import svg.Path as
"
),
@r###"
UNFINISHED IMPORT in tmp/unfinished_import_alias/Test.roc
I was partway through parsing an `import`, but I got stuck here:
4 import svg.Path as
^
I just saw the `as` keyword, so I was expecting to see an alias next.
"###
);
test_report!(
lowercase_import_alias,
indoc!(
r"
import svg.Path as path
"
),
@r###"
LOWERCASE ALIAS in tmp/lowercase_import_alias/Test.roc
This import is using a lowercase alias:
4 import svg.Path as path
^^^^
Module names and aliases must start with an uppercase letter.
"###
);
test_report!(
unfinished_import_exposing,
indoc!(
r"
import svg.Path exposing
"
),
@r###"
UNFINISHED IMPORT in tmp/unfinished_import_exposing/Test.roc
I was partway through parsing an `import`, but I got stuck here:
4 import svg.Path exposing
^
I just saw the `exposing` keyword, so I was expecting to see `[` next.
"###);
test_report!(
unfinished_import_exposing_name,
indoc!(
r"
import svg.Path exposing [3
"
),
@r###"
WEIRD EXPOSING in tmp/unfinished_import_exposing_name/Test.roc
I'm partway through parsing an exposing list, but I got stuck here:
4 import svg.Path exposing [3
^
I was expecting a type, value, or function name next, like:
import Svg exposing [Path, arc, rx]
"###);
test_report!(
unfinished_ingested_file_name,
indoc!(
r#"
import "example.json" as
"#
),
@r###"
UNFINISHED IMPORT in tmp/unfinished_ingested_file_name/Test.roc
I was partway through parsing an `import`, but I got stuck here:
4 import "example.json" as
^
I was expecting to see a name next, like:
import "users.json" as users : Str
"###
);
test_report!(
ingested_file_import_ann_syntax_err,
indoc!(
r#"
import "example.json" as example : List U8, U32
"#
),
@r###"
UNFINISHED TYPE in tmp/ingested_file_import_ann_syntax_err/Test.roc
I am partway through parsing a type, but I got stuck here:
4 import "example.json" as example : List U8, U32
^
Note: I may be confused by indentation
"###
);
// TODO could do better by pointing out we're parsing a function type // TODO could do better by pointing out we're parsing a function type
test_report!( test_report!(
dict_type_formatting, dict_type_formatting,

View File

@ -9,8 +9,7 @@ use crate::blankspace::{
space0_before_optional_after, space0_e, spaces, spaces_around, spaces_before, space0_before_optional_after, space0_e, spaces, spaces_around, spaces_before,
}; };
use crate::ident::{ use crate::ident::{
integer_ident, lowercase_ident, parse_ident, unqualified_ident, uppercase_ident, Accessor, integer_ident, lowercase_ident, parse_ident, unqualified_ident, Accessor, Ident, Suffix,
Ident, Suffix,
}; };
use crate::module::module_name_help; use crate::module::module_name_help;
use crate::parser::{ use crate::parser::{
@ -656,8 +655,9 @@ pub fn parse_single_def<'a>(
min_indent, min_indent,
) { ) {
Err((NoProgress, _)) => { Err((NoProgress, _)) => {
match loc!(import()).parse(arena, state.clone(), min_indent) { let pos_before_import = state.pos();
Err((_, _)) => { match import().parse(arena, state.clone(), min_indent) {
Err((NoProgress, _)) => {
match parse_expect.parse(arena, state.clone(), min_indent) { match parse_expect.parse(arena, state.clone(), min_indent) {
Err((_, _)) => { Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this // a hacky way to get expression-based error messages. TODO fix this
@ -684,12 +684,16 @@ pub fn parse_single_def<'a>(
), ),
} }
} }
Ok((_, loc_import, state)) => Ok(( Err((MadeProgress, err)) => {
Err((MadeProgress, EExpr::Import(err, pos_before_import)))
}
Ok((_, (loc_import, spaces_after), state)) => Ok((
MadeProgress, MadeProgress,
Some(SingleDef { Some(SingleDef {
type_or_value: Either::Second(loc_import.value), type_or_value: Either::Second(loc_import.value),
region: loc_import.region, region: loc_import.region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after,
}), }),
state, state,
)), )),
@ -753,6 +757,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def), type_or_value: Either::First(type_def),
region: def_region, region: def_region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -813,6 +818,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def), type_or_value: Either::First(type_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -836,6 +842,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def), type_or_value: Either::First(type_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -849,6 +856,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::Second(value_def), type_or_value: Either::Second(value_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -887,6 +895,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def), type_or_value: Either::First(type_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -911,6 +920,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def), type_or_value: Either::First(type_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -924,6 +934,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::Second(value_def), type_or_value: Either::Second(value_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)); ));
@ -957,10 +968,23 @@ pub fn parse_single_def<'a>(
} }
} }
fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> { fn import<'a>() -> impl Parser<'a, (Loc<ValueDef<'a>>, &'a [CommentOrNewline<'a>]), EImport<'a>> {
skip_first!( then(
parser::keyword(keyword::IMPORT, EImport::Import), and!(
increment_min_indent(one_of!(import_body(), import_ingested_file_body())) loc!(skip_first!(
parser::keyword(keyword::IMPORT, EImport::Import),
increment_min_indent(one_of!(import_body(), import_ingested_file_body()))
)),
space0_e(EImport::EndNewline)
),
|_arena, state, progress, (import, spaces_after)| {
if !spaces_after.is_empty() || state.has_reached_end() {
Ok((progress, (import, spaces_after), state))
} else {
// We require EOF, comment, or newline after import
Err((progress, EImport::EndNewline(state.pos())))
}
},
) )
} }
@ -969,8 +993,8 @@ fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
record!(ModuleImport { record!(ModuleImport {
before_name: space0_e(EImport::IndentStart), before_name: space0_e(EImport::IndentStart),
name: loc!(imported_module_name()), name: loc!(imported_module_name()),
alias: optional(backtrackable(import_as())), alias: optional(import_as()),
exposed: optional(backtrackable(import_exposing())) exposed: optional(import_exposing())
}), }),
ValueDef::ModuleImport ValueDef::ModuleImport
) )
@ -997,10 +1021,20 @@ fn import_as<'a>(
EImport::IndentAs, EImport::IndentAs,
EImport::IndentAlias EImport::IndentAlias
), ),
item: loc!(map!( item: then(
specialize_err(|_, pos| EImport::Alias(pos), uppercase_ident()), specialize_err(|_, pos| EImport::Alias(pos), loc!(unqualified_ident())),
ImportAlias::new |_arena, state, _progress, loc_ident| {
)) match loc_ident.value.chars().next() {
Some(first) if first.is_uppercase() => Ok((
MadeProgress,
loc_ident.map(|ident| ImportAlias::new(ident)),
state,
)),
Some(_) => Err((MadeProgress, EImport::LowercaseAlias(loc_ident.region))),
None => Err((MadeProgress, EImport::Alias(state.pos()))),
}
}
)
}) })
} }
@ -1133,6 +1167,7 @@ pub fn parse_single_def_assignment<'a>(
type_or_value: Either::Second(value_def), type_or_value: Either::Second(value_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state_after_rest_of_def, state_after_rest_of_def,
)); ));
@ -1154,6 +1189,7 @@ pub fn parse_single_def_assignment<'a>(
type_or_value: Either::Second(value_def), type_or_value: Either::Second(value_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state_after_first_expression, state_after_first_expression,
)); ));
@ -1169,6 +1205,7 @@ pub fn parse_single_def_assignment<'a>(
type_or_value: Either::Second(value_def), type_or_value: Either::Second(value_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state_after_first_expression, state_after_first_expression,
)) ))
@ -1216,6 +1253,7 @@ fn parse_statement_inside_def<'a>(
type_or_value: Either::Second(value_def), type_or_value: Either::Second(value_def),
region, region,
spaces_before: spaces_before_current, spaces_before: spaces_before_current,
spaces_after: &[],
}), }),
state, state,
)) ))
@ -1299,10 +1337,16 @@ fn parse_defs_end<'a>(
Ok((_, Some(single_def), next_state)) => { Ok((_, Some(single_def), next_state)) => {
let region = single_def.region; let region = single_def.region;
let spaces_before_current = single_def.spaces_before; let spaces_before_current = single_def.spaces_before;
let spaces_after_current = single_def.spaces_after;
match single_def.type_or_value { match single_def.type_or_value {
Either::First(type_def) => { Either::First(type_def) => {
defs.push_type_def(type_def, region, spaces_before_current, &[]); defs.push_type_def(
type_def,
region,
spaces_before_current,
spaces_after_current,
);
} }
Either::Second(value_def) => { Either::Second(value_def) => {
// If we got a ValueDef::Body, check if a type annotation preceded it. // If we got a ValueDef::Body, check if a type annotation preceded it.
@ -1364,7 +1408,12 @@ fn parse_defs_end<'a>(
if !joined { if !joined {
// the previous and current def can't be joined up // the previous and current def can't be joined up
defs.push_value_def(value_def, region, spaces_before_current, &[]); defs.push_value_def(
value_def,
region,
spaces_before_current,
spaces_after_current,
);
} }
} }
} }
@ -1386,6 +1435,7 @@ pub struct SingleDef<'a> {
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>, pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
pub region: Region, pub region: Region,
pub spaces_before: &'a [CommentOrNewline<'a>], pub spaces_before: &'a [CommentOrNewline<'a>],
pub spaces_after: &'a [CommentOrNewline<'a>],
} }
fn parse_defs_expr<'a>( fn parse_defs_expr<'a>(
@ -2873,11 +2923,11 @@ fn dbg_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpect<
fn import_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { fn import_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent: u32| { move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, import_def, state) = let (_, (import_def, spaces_after), state) =
loc!(specialize_err(EExpr::Import, import())).parse(arena, state, min_indent)?; specialize_err(EExpr::Import, import()).parse(arena, state, min_indent)?;
let mut defs = Defs::default(); let mut defs = Defs::default();
defs.push_value_def(import_def.value, import_def.region, &[], &[]); defs.push_value_def(import_def.value, import_def.region, &[], spaces_after);
parse_defs_expr(options, min_indent, defs, arena, state) parse_defs_expr(options, min_indent, defs, arena, state)
} }

View File

@ -534,6 +534,7 @@ pub enum EImport<'a> {
As(Position), As(Position),
IndentAlias(Position), IndentAlias(Position),
Alias(Position), Alias(Position),
LowercaseAlias(Region),
IndentExposing(Position), IndentExposing(Position),
Exposing(Position), Exposing(Position),
ExposingListStart(Position), ExposingListStart(Position),
@ -548,6 +549,7 @@ pub enum EImport<'a> {
IndentAnnotation(Position), IndentAnnotation(Position),
Annotation(EType<'a>, Position), Annotation(EType<'a>, Position),
Space(BadInputError, Position), Space(BadInputError, Position),
EndNewline(Position),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -1 +1 @@
NotEndOfFile(@12) Expr(Import(LowercaseAlias(@15-19), @0), @0)

View File

@ -13,14 +13,14 @@ Defs {
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
Slice(start = 2, length = 0),
Slice(start = 3, length = 0),
],
space_after: [
Slice(start = 0, length = 1), Slice(start = 0, length = 1),
Slice(start = 1, length = 1), Slice(start = 1, length = 1),
Slice(start = 2, length = 1), Slice(start = 2, length = 1),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
Slice(start = 2, length = 0),
Slice(start = 3, length = 0), Slice(start = 3, length = 0),
], ],
spaces: [ spaces: [

View File

@ -9,10 +9,10 @@ Defs {
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 0, length = 1), Slice(start = 1, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 1),
Slice(start = 1, length = 0), Slice(start = 1, length = 0),
], ],
spaces: [ spaces: [

View File

@ -31,6 +31,20 @@ Defs {
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
Slice(start = 4, length = 0),
Slice(start = 6, length = 0),
Slice(start = 8, length = 0),
Slice(start = 10, length = 0),
Slice(start = 12, length = 0),
Slice(start = 14, length = 0),
Slice(start = 16, length = 0),
Slice(start = 18, length = 0),
Slice(start = 20, length = 0),
Slice(start = 23, length = 0),
Slice(start = 25, length = 0),
],
space_after: [
Slice(start = 0, length = 2), Slice(start = 0, length = 2),
Slice(start = 2, length = 2), Slice(start = 2, length = 2),
Slice(start = 4, length = 2), Slice(start = 4, length = 2),
@ -43,20 +57,6 @@ Defs {
Slice(start = 18, length = 2), Slice(start = 18, length = 2),
Slice(start = 20, length = 3), Slice(start = 20, length = 3),
Slice(start = 23, length = 2), Slice(start = 23, length = 2),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
Slice(start = 4, length = 0),
Slice(start = 6, length = 0),
Slice(start = 8, length = 0),
Slice(start = 10, length = 0),
Slice(start = 12, length = 0),
Slice(start = 14, length = 0),
Slice(start = 16, length = 0),
Slice(start = 18, length = 0),
Slice(start = 20, length = 0),
Slice(start = 23, length = 0),
Slice(start = 25, length = 0), Slice(start = 25, length = 0),
], ],
spaces: [ spaces: [

View File

@ -11,12 +11,12 @@ Defs {
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 0, length = 1), Slice(start = 1, length = 0),
Slice(start = 1, length = 1), Slice(start = 2, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 1),
Slice(start = 1, length = 0), Slice(start = 1, length = 1),
Slice(start = 2, length = 0), Slice(start = 2, length = 0),
], ],
spaces: [ spaces: [

View File

@ -9,10 +9,10 @@ Defs {
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 0, length = 1), Slice(start = 1, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 1),
Slice(start = 1, length = 0), Slice(start = 1, length = 0),
], ],
spaces: [ spaces: [

View File

@ -9,15 +9,17 @@ Defs(
@27-51, @27-51,
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 1, length = 0), Slice(start = 1, length = 0),
], ],
space_after: [
Slice(start = 0, length = 1),
Slice(start = 1, length = 2),
],
spaces: [ spaces: [
Newline, Newline,
Newline,
Newline,
], ],
type_defs: [], type_defs: [],
value_defs: [ value_defs: [
@ -73,33 +75,27 @@ Defs(
), ),
], ],
}, },
@53-71 SpaceBefore( @53-71 Apply(
Apply( @53-62 Var {
@53-62 Var { module_name: "JE",
module_name: "JE", ident: "encode",
ident: "encode", },
},
[
@64-70 ParensAround(
Apply(
@64-67 Var {
module_name: "",
ident: "int",
},
[
@68-70 Num(
"42",
),
],
Space,
),
),
],
Space,
),
[ [
Newline, @64-70 ParensAround(
Newline, Apply(
@64-67 Var {
module_name: "",
ident: "int",
},
[
@68-70 Num(
"42",
),
],
Space,
),
),
], ],
Space,
), ),
) )

View File

@ -10,9 +10,12 @@ Defs(
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 2),
],
spaces: [
Newline,
Newline,
], ],
spaces: [],
type_defs: [], type_defs: [],
value_defs: [ value_defs: [
IngestedFileImport( IngestedFileImport(
@ -43,23 +46,17 @@ Defs(
), ),
], ],
}, },
@35-49 SpaceBefore( @35-49 Apply(
Apply( @35-44 Var {
@35-44 Var { module_name: "",
module_name: "", ident: "parseJson",
ident: "parseJson", },
},
[
@45-49 Var {
module_name: "",
ident: "data",
},
],
Space,
),
[ [
Newline, @45-49 Var {
Newline, module_name: "",
ident: "data",
},
], ],
Space,
), ),
) )

View File

@ -10,9 +10,12 @@ Defs(
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 2),
],
spaces: [
Newline,
Newline,
], ],
spaces: [],
type_defs: [], type_defs: [],
value_defs: [ value_defs: [
IngestedFileImport( IngestedFileImport(
@ -34,23 +37,17 @@ Defs(
), ),
], ],
}, },
@29-43 SpaceBefore( @29-43 Apply(
Apply( @29-38 Var {
@29-38 Var { module_name: "",
module_name: "", ident: "parseJson",
ident: "parseJson", },
},
[
@39-43 Var {
module_name: "",
ident: "data",
},
],
Space,
),
[ [
Newline, @39-43 Var {
Newline, module_name: "",
ident: "data",
},
], ],
Space,
), ),
) )

View File

@ -46,10 +46,10 @@ Full {
], ],
space_before: [ space_before: [
Slice(start = 0, length = 2), Slice(start = 0, length = 2),
Slice(start = 2, length = 2), Slice(start = 4, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 2, length = 0), Slice(start = 2, length = 2),
Slice(start = 4, length = 0), Slice(start = 4, length = 0),
], ],
spaces: [ spaces: [

View File

@ -324,6 +324,7 @@ pub fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)), Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)),
region, region,
spaces_before, spaces_before,
spaces_after: _,
}), }),
_, _,
)) if spaces_before.len() <= 1 => { )) if spaces_before.len() <= 1 => {
@ -373,6 +374,7 @@ pub fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)), Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)),
region, region,
spaces_before, spaces_before,
spaces_after: _,
}), }),
_, _,
)) if spaces_before.len() <= 1 => { )) if spaces_before.len() <= 1 => {

View File

@ -670,6 +670,9 @@ fn to_expr_report<'a>(
EExpr::Dbg(e_expect, _position) => { EExpr::Dbg(e_expect, _position) => {
to_dbg_or_expect_report(alloc, lines, filename, context, Node::Dbg, e_expect, start) to_dbg_or_expect_report(alloc, lines, filename, context, Node::Dbg, e_expect, start)
} }
EExpr::Import(e_import, position) => {
to_import_report(alloc, lines, filename, e_import, *position)
}
EExpr::TrailingOperator(pos) => { EExpr::TrailingOperator(pos) => {
let surroundings = Region::new(start, *pos); let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
@ -1439,6 +1442,190 @@ fn to_dbg_or_expect_report<'a>(
} }
} }
fn to_import_report<'a>(
alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo,
filename: PathBuf,
parse_problem: &roc_parse::parser::EImport<'a>,
start: Position,
) -> Report<'a> {
use roc_parse::parser::EImport::*;
match parse_problem {
Import(_pos) => unreachable!("another branch would be taken"),
IndentStart(pos)
| PackageShorthand(pos)
| PackageShorthandDot(pos)
| ModuleName(pos)
| IndentIngestedPath(pos)
| IngestedPath(pos) => to_unfinished_import_report(
alloc,
lines,
filename,
*pos,
start,
alloc.stack([
alloc.reflow("I was expecting to see a module name, like:"),
alloc.parser_suggestion("import BigNum").indent(4),
alloc.reflow("Or a package module name, like:"),
alloc.parser_suggestion("import pf.Stdout").indent(4),
alloc.reflow("Or a file path to ingest, like:"),
alloc
.parser_suggestion("import \"users.json\" as users : Str")
.indent(4),
]),
),
IndentAs(pos) | As(pos) | IndentExposing(pos) | Exposing(pos) | EndNewline(pos) => {
to_unfinished_import_report(
alloc,
lines,
filename,
*pos,
start,
alloc.stack([
alloc.concat([
alloc.reflow("I was expecting to see the "),
alloc.keyword("as"),
alloc.reflow(" keyword, like:"),
]),
alloc
.parser_suggestion("import svg.Path as SvgPath")
.indent(4),
alloc.concat([
alloc.reflow("Or the "),
alloc.keyword("exposing"),
alloc.reflow(" keyword, like:"),
]),
alloc
.parser_suggestion("import svg.Path exposing [arc, rx]")
.indent(4),
]),
)
}
IndentAlias(pos) | Alias(pos) => to_unfinished_import_report(
alloc,
lines,
filename,
*pos,
start,
alloc.concat([
alloc.reflow("I just saw the "),
alloc.keyword("as"),
alloc.reflow(" keyword, so I was expecting to see an alias next."),
]),
),
LowercaseAlias(region) => {
let surroundings = Region::new(start, region.end());
let region = lines.convert_region(*region);
let doc = alloc.stack([
alloc.reflow(r"This import is using a lowercase alias:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.reflow(r"Module names and aliases must start with an uppercase letter."),
]);
Report {
filename,
doc,
title: "LOWERCASE ALIAS".to_string(),
severity: Severity::RuntimeError,
}
}
ExposingListStart(pos) => to_unfinished_import_report(
alloc,
lines,
filename,
*pos,
start,
alloc.concat([
alloc.reflow("I just saw the "),
alloc.keyword("exposing"),
alloc.reflow(" keyword, so I was expecting to see "),
alloc.keyword("["),
alloc.reflow(" next."),
]),
),
ExposedName(pos) | ExposingListEnd(pos) => {
let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack([
alloc
.reflow(r"I'm partway through parsing an exposing list, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.reflow(r"I was expecting a type, value, or function name next, like:"),
alloc
.parser_suggestion("import Svg exposing [Path, arc, rx]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD EXPOSING".to_string(),
severity: Severity::RuntimeError,
}
}
IndentIngestedName(pos) | IngestedName(pos) => to_unfinished_import_report(
alloc,
lines,
filename,
*pos,
start,
alloc.stack([
alloc.reflow("I was expecting to see a name next, like:"),
alloc
.parser_suggestion("import \"users.json\" as users : Str")
.indent(4),
]),
),
Annotation(problem, pos) => to_type_report(alloc, lines, filename, problem, *pos),
IndentAnnotation(pos) | IndentColon(pos) | Colon(pos) => to_unfinished_import_report(
alloc,
lines,
filename,
*pos,
start,
alloc.stack([
alloc.reflow("I was expecting to see an annotation next, like:"),
alloc
.parser_suggestion("import \"users.json\" as users : Str")
.indent(4),
]),
),
Space(problem, pos) => to_space_report(alloc, lines, filename, problem, *pos),
}
}
fn to_unfinished_import_report<'a>(
alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo,
filename: PathBuf,
pos: Position,
start: Position,
message: RocDocBuilder<'a>,
) -> Report<'a> {
let surroundings = Region::new(start, pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(pos));
let doc = alloc.stack([
alloc.concat([
alloc.reflow(r"I was partway through parsing an "),
alloc.keyword("import"),
alloc.reflow(r", but I got stuck here:"),
]),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
message,
]);
Report {
filename,
doc,
title: "UNFINISHED IMPORT".to_string(),
severity: Severity::RuntimeError,
}
}
fn to_if_report<'a>( fn to_if_report<'a>(
alloc: &'a RocDocAllocator<'a>, alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo, lines: &LineInfo,