Merge pull request #6708 from roc-lang/module-params-syntax

Parse and format module params
This commit is contained in:
Ayaz 2024-05-12 13:51:13 -05:00 committed by GitHub
commit e5ea6dc461
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 989 additions and 162 deletions

View file

@ -9,8 +9,8 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{
AssignedField, Collection, Pattern, RecordBuilderField, StrLiteral, StrSegment, ValueDef,
WhenBranch,
AssignedField, Collection, ModuleImportParams, Pattern, RecordBuilderField, StrLiteral,
StrSegment, ValueDef, WhenBranch,
};
use roc_region::all::{LineInfo, Loc, Region};
@ -131,11 +131,26 @@ fn desugar_value_def<'a>(
}
}
ModuleImport(roc_parse::ast::ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
}) => *def,
before_name,
name,
params,
alias,
exposed,
}) => {
let desugared_params =
params.map(|ModuleImportParams { before, params }| ModuleImportParams {
before,
params: desugar_field_collection(arena, params, src, line_info, module_path),
});
ModuleImport(roc_parse::ast::ModuleImport {
before_name,
name: *name,
params: desugared_params,
alias: *alias,
exposed: *exposed,
})
}
IngestedFileImport(_) => *def,
Stmt(stmt_expr) => {
@ -383,15 +398,7 @@ pub fn desugar_expr<'a>(
})
}
Record(fields) => {
let mut allocated = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() {
let value = desugar_field(arena, &field.value, src, line_info, module_path);
allocated.push(Loc {
value,
region: field.region,
});
}
let fields = fields.replace_items(allocated.into_bump_slice());
let fields = desugar_field_collection(arena, *fields, src, line_info, module_path);
arena.alloc(Loc {
region: loc_expr.region,
value: Record(fields),
@ -825,6 +832,24 @@ fn desugar_str_segments<'a>(
.into_bump_slice()
}
fn desugar_field_collection<'a>(
arena: &'a Bump,
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
src: &'a str,
line_info: &mut Option<LineInfo>,
module_path: &str,
) -> Collection<'a, Loc<AssignedField<'a, Expr<'a>>>> {
let mut allocated = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() {
let value = desugar_field(arena, &field.value, src, line_info, module_path);
allocated.push(Loc::at(field.region, value));
}
fields.replace_items(allocated.into_bump_slice())
}
fn desugar_field<'a>(
arena: &'a Bump,
field: &'a AssignedField<'a, Expr<'a>>,

View file

@ -6,8 +6,8 @@ use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT}
use crate::Buf;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern, Spaces,
StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use roc_parse::header::Keyword;
use roc_region::all::Loc;
@ -192,12 +192,14 @@ impl<'a> Formattable for ModuleImport<'a> {
let Self {
before_name,
name,
params,
alias,
exposed,
} = self;
!before_name.is_empty()
|| name.is_multiline()
|| params.is_multiline()
|| alias.is_multiline()
|| match exposed {
Some(a) => a.keyword.is_multiline() || is_collection_multiline(&a.item),
@ -215,6 +217,7 @@ impl<'a> Formattable for ModuleImport<'a> {
let Self {
before_name,
name,
params,
alias,
exposed,
} = self;
@ -222,35 +225,44 @@ impl<'a> Formattable for ModuleImport<'a> {
buf.indent(indent);
buf.push_str("import");
let indent = indent + INDENT;
let indent = if !before_name.is_empty()
|| (params.is_multiline() && exposed.is_some())
|| alias.is_multiline()
|| exposed.map_or(false, |e| e.keyword.is_multiline())
{
indent + INDENT
} else {
indent
};
fmt_default_spaces(buf, before_name, indent);
buf.indent(indent);
name.format(buf, indent);
if let Some(alias) = alias {
alias.format(buf, indent);
}
params.format(buf, indent);
alias.format(buf, indent);
if let Some(exposed) = exposed {
exposed.keyword.format(buf, indent);
let list_indent = if !before_name.is_empty()
|| alias.is_multiline()
|| exposed.keyword.is_multiline()
{
indent
} else {
// Align list with import keyword
indent - INDENT
};
fmt_collection(buf, list_indent, Braces::Square, exposed.item, Newlines::No);
fmt_collection(buf, indent, Braces::Square, exposed.item, Newlines::No);
}
}
}
impl<'a> Formattable for ModuleImportParams<'a> {
fn is_multiline(&self) -> bool {
let ModuleImportParams { before, params } = self;
!before.is_empty() || is_collection_multiline(params)
}
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
let ModuleImportParams { before, params } = self;
fmt_default_spaces(buf, before, indent);
fmt_collection(buf, indent, Braces::Curly, *params, newlines);
}
}
impl<'a> Formattable for IngestedFileImport<'a> {
fn is_multiline(&self) -> bool {
let Self {

View file

@ -1,3 +1,5 @@
use std::cmp::max;
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
@ -177,7 +179,20 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
buf.indent(0);
buf.push_str("module");
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
let mut indent = fmt_spaces_with_outdent(buf, header.after_keyword, 0);
if let Some(params) = &header.params {
if is_collection_multiline(&params.params) {
indent = INDENT;
}
fmt_collection(buf, indent, Braces::Curly, params.params, Newlines::Yes);
indent = fmt_spaces_with_outdent(buf, params.before_arrow, indent);
buf.push_str("->");
indent = fmt_spaces_with_outdent(buf, params.after_arrow, indent);
}
fmt_exposes(buf, header.exposes, indent);
}
@ -202,18 +217,19 @@ pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) {
buf.indent(0);
buf.push_str("app");
let indent = fmt_spaces_with_outdent(buf, header.before_provides, INDENT);
let indent = fmt_spaces_with_outdent(buf, header.before_provides, 0);
fmt_exposes(buf, header.provides, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
fmt_packages(buf, header.packages.value, indent);
}
pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 {
if spaces.iter().all(|c| c.is_newline()) {
buf.spaces(1);
0
indent
} else {
let indent = max(INDENT, indent + INDENT);
fmt_default_spaces(buf, spaces, indent);
indent
}
@ -223,10 +239,10 @@ pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) {
buf.indent(0);
buf.push_str("package");
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, 0);
fmt_exposes(buf, header.exposes, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
fmt_packages(buf, header.packages.value, indent);
}

View file

@ -6,14 +6,14 @@ use roc_parse::{
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
IngestedFileImport, Module, ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced,
Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
WhenBranch,
IngestedFileImport, Module, ModuleImport, ModuleImportParams, Pattern, PatternAs,
RecordBuilderField, Spaced, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
TypeHeader, ValueDef, WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
TypedIdent,
ModuleParams, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
ProvidesTo, To, TypedIdent,
},
ident::{BadIdent, UppercaseIdent},
};
@ -285,7 +285,8 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let header = match &self.header {
Header::Module(header) => Header::Module(ModuleHeader {
before_exposes: &[],
after_keyword: &[],
params: header.params.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
interface_imports: header.interface_imports.remove_spaces(arena),
}),
@ -330,6 +331,16 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
}
}
impl<'a> RemoveSpaces<'a> for ModuleParams<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ModuleParams {
params: self.params.remove_spaces(arena),
before_arrow: &[],
after_arrow: &[],
}
}
}
impl<'a> RemoveSpaces<'a> for Region {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Region::zero()
@ -589,12 +600,22 @@ impl<'a> RemoveSpaces<'a> for ModuleImport<'a> {
ModuleImport {
before_name: &[],
name: self.name.remove_spaces(arena),
params: self.params.remove_spaces(arena),
alias: self.alias.remove_spaces(arena),
exposed: self.exposed.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ModuleImportParams<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ModuleImportParams {
before: &[],
params: self.params.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
IngestedFileImport {

View file

@ -4943,6 +4943,67 @@ mod test_reporting {
"###
);
test_report!(
weird_import_params_record,
indoc!(
r"
import Menu { x = 4 }
"
),@r###"
RECORD PARSE PROBLEM in tmp/weird_import_params_record/Test.roc
I am partway through parsing a record, but I got stuck here:
4 import Menu { x = 4 }
^
TODO provide more context.
"###
);
test_report!(
record_builder_in_module_params,
indoc!(
r"
import Menu {
echo,
name: <- applyName
}
"
),@r###"
RECORD BUILDER IN MODULE PARAMS in ...ord_builder_in_module_params/Test.roc
I was partway through parsing module params, but I got stuck here:
4 import Menu {
5 echo,
6 name: <- applyName
^^^^^^^^^^^^^^^^^^
This looks like a record builder field, but those are not allowed in
module params.
"###
);
test_report!(
record_update_in_module_params,
indoc!(
r"
import Menu { myParams & echo: echoFn }
"
),@r###"
RECORD UPDATE IN MODULE PARAMS in ...ecord_update_in_module_params/Test.roc
I was partway through parsing module params, but I got stuck here:
4 import Menu { myParams & echo: echoFn }
^^^^^^^^
It looks like you're trying to update a record, but module params
require a standalone record literal.
"###
);
test_report!(
unfinished_import_as_or_exposing,
indoc!(
@ -4965,6 +5026,10 @@ mod test_reporting {
Or the `exposing` keyword, like:
import svg.Path exposing [arc, rx]
Or module params, like:
import Menu { echo, read }
"###
);
@ -6210,6 +6275,31 @@ In roc, functions are always written as a lambda, like{}
)
}
#[test]
fn module_params_with_missing_arrow() {
report_header_problem_as(
indoc!(
r#"
module {echo, read} [menu]
"#
),
indoc!(
r#"
WEIRD MODULE PARAMS in /code/proj/Main.roc
I am partway through parsing a module header, but I got stuck here:
1 module {echo, read} [menu]
^
I am expecting `->` next, like:
module { echo, read } -> [menu]
"#
),
)
}
#[test]
fn platform_requires_rigids() {
report_header_problem_as(

View file

@ -246,6 +246,7 @@ impl<'a> Module<'a> {
name,
},
},
params: None,
alias: None,
exposed: new_exposed,
})
@ -784,43 +785,6 @@ pub enum ValueDef<'a> {
}
impl<'a> ValueDef<'a> {
pub fn expr(&self) -> Option<&'a Expr<'a>> {
match self {
ValueDef::Body(_, body) => Some(&body.value),
ValueDef::AnnotatedBody {
ann_pattern: _,
ann_type: _,
comment: _,
body_pattern: _,
body_expr,
} => Some(&body_expr.value),
ValueDef::Dbg {
condition,
preceding_comment: _,
}
| ValueDef::Expect {
condition,
preceding_comment: _,
}
| ValueDef::ExpectFx {
condition,
preceding_comment: _,
} => Some(&condition.value),
ValueDef::Annotation(_, _)
| ValueDef::ModuleImport(ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
})
| ValueDef::IngestedFileImport(_) => None,
ValueDef::Stmt(loc_expr) => Some(&loc_expr.value),
}
}
pub fn replace_expr(&mut self, new_expr: &'a Loc<Expr<'a>>) {
match self {
ValueDef::Body(_, expr) => *expr = new_expr,
@ -1019,8 +983,47 @@ impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
let def = &self.current.value_defs[def_index.index()];
let region = &self.current.regions[self.index];
if let Some(expr) = def.expr() {
self.push_pending_from_expr(expr);
match def {
ValueDef::Body(_, body) => self.push_pending_from_expr(&body.value),
ValueDef::AnnotatedBody {
ann_pattern: _,
ann_type: _,
comment: _,
body_pattern: _,
body_expr,
} => self.push_pending_from_expr(&body_expr.value),
ValueDef::Dbg {
condition,
preceding_comment: _,
}
| ValueDef::Expect {
condition,
preceding_comment: _,
}
| ValueDef::ExpectFx {
condition,
preceding_comment: _,
} => self.push_pending_from_expr(&condition.value),
ValueDef::ModuleImport(ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
params,
}) => {
if let Some(ModuleImportParams { before: _, params }) = params {
for loc_assigned_field in params.items {
if let Some(expr) = loc_assigned_field.value.value() {
self.push_pending_from_expr(&expr.value);
}
}
}
}
ValueDef::Stmt(loc_expr) => self.push_pending_from_expr(&loc_expr.value),
ValueDef::Annotation(_, _) | ValueDef::IngestedFileImport(_) => {}
}
self.index += 1;
@ -1046,6 +1049,7 @@ impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
pub struct ModuleImport<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<ImportedModuleName<'a>>,
pub params: Option<ModuleImportParams<'a>>,
pub alias: Option<header::KeywordItem<'a, ImportAsKeyword, Loc<ImportAlias<'a>>>>,
pub exposed: Option<
header::KeywordItem<
@ -1056,6 +1060,12 @@ pub struct ModuleImport<'a> {
>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModuleImportParams<'a> {
pub before: &'a [CommentOrNewline<'a>],
pub params: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IngestedFileImport<'a> {
pub before_path: &'a [CommentOrNewline<'a>],
@ -1537,6 +1547,20 @@ pub enum AssignedField<'a, Val> {
Malformed(&'a str),
}
impl<'a, Val> AssignedField<'a, Val> {
pub fn value(&self) -> Option<&Loc<Val>> {
let mut current = self;
loop {
match current {
Self::RequiredValue(_, _, val) | Self::OptionalValue(_, _, val) => break Some(val),
Self::LabelOnly(_) | Self::Malformed(_) => break None,
Self::SpaceBefore(next, _) | Self::SpaceAfter(next, _) => current = *next,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RecordBuilderField<'a> {
// A field with a value, e.g. `{ name: "blah" }`
@ -2635,9 +2659,10 @@ impl<'a> Malformed for ValueDef<'a> {
ValueDef::ModuleImport(ModuleImport {
before_name: _,
name: _,
params,
alias: _,
exposed: _,
}) => false,
}) => params.is_malformed(),
ValueDef::IngestedFileImport(IngestedFileImport {
before_path: _,
path,
@ -2649,6 +2674,14 @@ impl<'a> Malformed for ValueDef<'a> {
}
}
impl<'a> Malformed for ModuleImportParams<'a> {
fn is_malformed(&self) -> bool {
let Self { before: _, params } = self;
params.is_malformed()
}
}
impl<'a> Malformed for TypeAnnotation<'a> {
fn is_malformed(&self) -> bool {
match self {

View file

@ -1,8 +1,9 @@
use crate::ast::{
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern,
RecordBuilderField, Spaceable, Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, RecordBuilderField, Spaceable, Spaced, Spaces, TypeAnnotation,
TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
@ -15,8 +16,8 @@ use crate::module::module_name_help;
use crate::parser::{
self, backtrackable, byte, byte_indent, increment_min_indent, line_min_indent, optional,
reset_min_indent, sep_by1, sep_by1_e, set_min_indent, specialize_err, specialize_err_ref, then,
two_bytes, EClosure, EExpect, EExpr, EIf, EImport, EInParens, EList, ENumber, EPattern,
ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
two_bytes, EClosure, EExpect, EExpr, EIf, EImport, EImportParams, EInParens, EList, ENumber,
EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
};
use crate::pattern::{closure_param, loc_implements_parser};
use crate::state::State;
@ -979,6 +980,7 @@ fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
record!(ModuleImport {
before_name: space0_e(EImport::IndentStart),
name: loc!(imported_module_name()),
params: optional(specialize_err(EImport::Params, import_params())),
alias: optional(import_as()),
exposed: optional(import_exposing())
}),
@ -986,6 +988,37 @@ fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
)
}
fn import_params<'a>() -> impl Parser<'a, ModuleImportParams<'a>, EImportParams<'a>> {
then(
and!(
backtrackable(space0_e(EImportParams::Indent)),
specialize_err(EImportParams::Record, record_help())
),
|arena, state, _, (before, record): (_, RecordHelp<'a>)| {
if let Some(update) = record.update {
return Err((
MadeProgress,
EImportParams::RecordUpdateFound(update.region),
));
}
let params = record.fields.map_items_result(arena, |loc_field| {
match loc_field.value.to_assigned_field(arena) {
Ok(field) => Ok(Loc::at(loc_field.region, field)),
Err(FoundApplyValue) => Err((
MadeProgress,
EImportParams::RecordApplyFound(loc_field.region),
)),
}
})?;
let import_params = ModuleImportParams { before, params };
Ok((MadeProgress, import_params, state))
},
)
}
#[inline(always)]
fn imported_module_name<'a>() -> impl Parser<'a, ImportedModuleName<'a>, EImport<'a>> {
record!(ImportedModuleName {

View file

@ -1,5 +1,5 @@
use crate::ast::{
Collection, CommentOrNewline, Malformed, Spaced, Spaces, StrLiteral, TypeAnnotation,
Collection, CommentOrNewline, Malformed, Pattern, Spaced, Spaces, StrLiteral, TypeAnnotation,
};
use crate::blankspace::space0_e;
use crate::expr::merge_spaces;
@ -242,13 +242,21 @@ pub struct KeywordItem<'a, K, V> {
#[derive(Clone, Debug, PartialEq)]
pub struct ModuleHeader<'a> {
pub before_exposes: &'a [CommentOrNewline<'a>],
pub after_keyword: &'a [CommentOrNewline<'a>],
pub params: Option<ModuleParams<'a>>,
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
// Keeping this so we can format old interface header into module headers
pub interface_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ModuleParams<'a> {
pub params: Collection<'a, Loc<Pattern<'a>>>,
pub before_arrow: &'a [CommentOrNewline<'a>],
pub after_arrow: &'a [CommentOrNewline<'a>],
}
pub type ImportsKeywordItem<'a> = KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>;
pub type ImportsCollection<'a> = Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>;

View file

@ -4,17 +4,18 @@ use crate::expr::merge_spaces;
use crate::header::{
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
HostedHeader, ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword,
KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader, PackagesKeyword,
PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword,
TypedIdent, WithKeyword,
KeywordItem, ModuleHeader, ModuleName, ModuleParams, PackageEntry, PackageHeader,
PackagesKeyword, PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo,
RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
};
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, byte, increment_min_indent, optional, reset_min_indent, specialize_err,
two_bytes, EExposes, EGenerates, EGeneratesWith, EHeader, EImports, EPackages, EProvides,
ERequires, ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError,
two_bytes, EExposes, EGenerates, EGeneratesWith, EHeader, EImports, EPackages, EParams,
EProvides, ERequires, ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError,
};
use crate::pattern::record_pattern_fields;
use crate::state::State;
use crate::string_literal::{self, parse_str_literal};
use crate::type_annotation;
@ -111,13 +112,25 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
#[inline(always)]
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
record!(ModuleHeader {
before_exposes: space0_e(EHeader::IndentStart),
after_keyword: space0_e(EHeader::IndentStart),
params: optional(specialize_err(EHeader::Params, module_params())),
exposes: specialize_err(EHeader::Exposes, exposes_list()),
interface_imports: succeed!(None)
})
.trace("module_header")
}
fn module_params<'a>() -> impl Parser<'a, ModuleParams<'a>, EParams<'a>> {
record!(ModuleParams {
params: specialize_err(EParams::Pattern, record_pattern_fields()),
before_arrow: skip_second!(
space0_e(EParams::BeforeArrow),
loc!(two_bytes(b'-', b'>', EParams::Arrow))
),
after_arrow: space0_e(EParams::AfterArrow),
})
}
macro_rules! merge_n_spaces {
($arena:expr, $($slice:expr),*) => {
{
@ -131,7 +144,7 @@ macro_rules! merge_n_spaces {
/// Parse old interface headers so we can format them into module headers
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
let before_exposes = map_with_arena!(
let after_keyword = map_with_arena!(
and!(
skip_second!(
space0_e(EHeader::IndentStart),
@ -146,7 +159,8 @@ fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
);
record!(ModuleHeader {
before_exposes: before_exposes,
after_keyword: after_keyword,
params: succeed!(None),
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
interface_imports: map!(
specialize_err(EHeader::Imports, imports()),

View file

@ -88,7 +88,9 @@ impl_space_problem! {
EHeader<'a>,
EIf<'a>,
EImport<'a>,
EParams<'a>,
EImports,
EImportParams<'a>,
EInParens<'a>,
EClosure<'a>,
EList<'a>,
@ -115,6 +117,7 @@ impl_space_problem! {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EHeader<'a> {
Provides(EProvides<'a>, Position),
Params(EParams<'a>, Position),
Exposes(EExposes, Position),
Imports(EImports, Position),
Requires(ERequires<'a>, Position),
@ -149,6 +152,15 @@ pub enum EProvides<'a> {
Space(BadInputError, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EParams<'a> {
Pattern(PRecord<'a>, Position),
BeforeArrow(Position),
Arrow(Position),
AfterArrow(Position),
Space(BadInputError, Position),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EExposes {
Exposes(Position),
@ -530,6 +542,7 @@ pub enum EImport<'a> {
PackageShorthand(Position),
PackageShorthandDot(Position),
ModuleName(Position),
Params(EImportParams<'a>, Position),
IndentAs(Position),
As(Position),
IndentAlias(Position),
@ -552,6 +565,15 @@ pub enum EImport<'a> {
EndNewline(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EImportParams<'a> {
Indent(Position),
Record(ERecord<'a>, Position),
RecordUpdateFound(Region),
RecordApplyFound(Region),
Space(BadInputError, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPattern<'a> {
Record(PRecord<'a>, Position),

View file

@ -1,4 +1,4 @@
use crate::ast::{Implements, Pattern, PatternAs, Spaceable};
use crate::ast::{Collection, Implements, Pattern, PatternAs, Spaceable};
use crate::blankspace::{space0_e, spaces, spaces_before};
use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident};
use crate::keyword;
@ -468,15 +468,17 @@ fn lowercase_ident_pattern<'a>() -> impl Parser<'a, &'a str, EPattern<'a>> {
#[inline(always)]
fn record_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, PRecord<'a>> {
map!(
collection_trailing_sep_e!(
byte(b'{', PRecord::Open),
record_pattern_field(),
byte(b',', PRecord::End),
byte(b'}', PRecord::End),
Pattern::SpaceBefore
),
Pattern::RecordDestructure
map!(record_pattern_fields(), Pattern::RecordDestructure)
}
pub fn record_pattern_fields<'a>() -> impl Parser<'a, Collection<'a, Loc<Pattern<'a>>>, PRecord<'a>>
{
collection_trailing_sep_e!(
byte(b'{', PRecord::Open),
record_pattern_field(),
byte(b',', PRecord::End),
byte(b'}', PRecord::End),
Pattern::SpaceBefore
)
}

View file

@ -0,0 +1 @@
Header(Params(Arrow(@22), @7))

View file

@ -0,0 +1 @@
module { echo, name } [menu]

View file

@ -0,0 +1 @@
Header(Params(Pattern(End(@20), @7), @7))

View file

@ -0,0 +1 @@
module { echo, name -> [menu]

View file

@ -2,7 +2,8 @@ Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
after_keyword: [],
params: None,
exposes: [],
interface_imports: None,
},

View file

@ -23,6 +23,7 @@ Defs {
"Json",
),
},
params: None,
alias: None,
exposed: None,
},

View file

@ -41,6 +41,7 @@ Defs {
"Decode",
),
},
params: None,
alias: None,
exposed: None,
},
@ -56,6 +57,7 @@ Defs {
"Decode",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -82,6 +84,7 @@ Defs {
"Decode",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {
@ -118,6 +121,7 @@ Defs {
"Decode",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {

View file

@ -29,6 +29,7 @@ Defs {
"JsonEncode",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -53,6 +54,7 @@ Defs {
"Bytes.Decode",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {

View file

@ -104,6 +104,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -141,6 +142,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -183,6 +185,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -212,6 +215,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -254,6 +258,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -304,6 +309,7 @@ Defs {
"Json",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {
@ -343,6 +349,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -385,6 +392,7 @@ Defs {
"Json",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {
@ -416,6 +424,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -458,6 +467,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -513,6 +523,7 @@ Defs {
"Json",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {
@ -583,6 +594,7 @@ Defs {
"A",
),
},
params: None,
alias: None,
exposed: None,
},
@ -596,6 +608,7 @@ Defs {
"B",
),
},
params: None,
alias: None,
exposed: None,
},

View file

@ -34,6 +34,7 @@ Defs {
"Json",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {
@ -63,6 +64,7 @@ Defs {
"Json",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {
@ -97,6 +99,7 @@ Defs {
"Json",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {

View file

@ -0,0 +1,13 @@
import pf.Menu { echo, read }
import Menu {
echo,
read,
}
import Menu {
echo,
read,
} as M
import Menu { echo, read } as M exposing [
main,
credits,
]

View file

@ -0,0 +1,193 @@
Defs {
tags: [
Index(2147483648),
Index(2147483649),
Index(2147483650),
Index(2147483651),
],
regions: [
@0-29,
@30-60,
@61-96,
@97-157,
],
space_before: [
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 = 1, length = 1),
Slice(start = 2, length = 1),
Slice(start = 3, length = 0),
],
spaces: [
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [
ModuleImport(
ModuleImport {
before_name: [],
name: @7-14 ImportedModuleName {
package: Some(
"pf",
),
name: ModuleName(
"Menu",
),
},
params: Some(
ModuleImportParams {
before: [],
params: [
@17-21 LabelOnly(
@17-21 "echo",
),
@23-28 LabelOnly(
@23-27 "read",
),
],
},
),
alias: None,
exposed: None,
},
),
ModuleImport(
ModuleImport {
before_name: [],
name: @37-41 ImportedModuleName {
package: None,
name: ModuleName(
"Menu",
),
},
params: Some(
ModuleImportParams {
before: [],
params: [
@44-48 LabelOnly(
@44-48 "echo",
),
@54-59 SpaceBefore(
LabelOnly(
@54-58 "read",
),
[
Newline,
],
),
],
},
),
alias: None,
exposed: None,
},
),
ModuleImport(
ModuleImport {
before_name: [],
name: @68-72 ImportedModuleName {
package: None,
name: ModuleName(
"Menu",
),
},
params: Some(
ModuleImportParams {
before: [],
params: [
@75-79 LabelOnly(
@75-79 "echo",
),
@85-90 SpaceBefore(
LabelOnly(
@85-89 "read",
),
[
Newline,
],
),
],
},
),
alias: Some(
KeywordItem {
keyword: Spaces {
before: [],
item: ImportAsKeyword,
after: [],
},
item: @95-96 ImportAlias(
"M",
),
},
),
exposed: None,
},
),
ModuleImport(
ModuleImport {
before_name: [],
name: @104-108 ImportedModuleName {
package: None,
name: ModuleName(
"Menu",
),
},
params: Some(
ModuleImportParams {
before: [],
params: [
@111-115 LabelOnly(
@111-115 "echo",
),
@117-122 LabelOnly(
@117-121 "read",
),
],
},
),
alias: Some(
KeywordItem {
keyword: Spaces {
before: [],
item: ImportAsKeyword,
after: [],
},
item: @127-128 ImportAlias(
"M",
),
},
),
exposed: Some(
KeywordItem {
keyword: Spaces {
before: [],
item: ImportExposingKeyword,
after: [],
},
item: [
@140-144 ExposedName(
"main",
),
@148-155 SpaceBefore(
ExposedName(
"credits",
),
[
Newline,
],
),
],
},
),
},
),
],
}

View file

@ -0,0 +1,7 @@
import pf.Menu { echo, read }
import Menu { echo,
read }
import Menu { echo,
read } as M
import Menu { echo, read } as M exposing [ main,
credits ]

View file

@ -32,6 +32,7 @@ Defs(
"Json",
),
},
params: None,
alias: None,
exposed: Some(
KeywordItem {
@ -58,6 +59,7 @@ Defs(
"Json.Encode",
),
},
params: None,
alias: Some(
KeywordItem {
keyword: Spaces {

View file

@ -2,7 +2,8 @@ Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
after_keyword: [],
params: None,
exposes: [
@8-9 ExposedName(
"a",

View file

@ -0,0 +1,9 @@
module
{
echo,
# comment before param
read,
} -> [
mainMenu,
credits,
]

View file

@ -0,0 +1,44 @@
Module {
comments: [],
header: Module(
ModuleHeader {
after_keyword: [],
params: Some(
ModuleParams {
params: [
@8-12 Identifier {
ident: "echo",
},
@39-43 SpaceBefore(
Identifier {
ident: "read",
},
[
Newline,
LineComment(
" comment before param",
),
],
),
],
before_arrow: [],
after_arrow: [],
},
),
exposes: [
@50-58 ExposedName(
"mainMenu",
),
@64-71 SpaceBefore(
ExposedName(
"credits",
),
[
Newline,
],
),
],
interface_imports: None,
},
),
}

View file

@ -0,0 +1,4 @@
module {echo,
# comment before param
read } -> [mainMenu,
credits ]

View file

@ -2,7 +2,8 @@ Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
after_keyword: [],
params: None,
exposes: [],
interface_imports: None,
},

View file

@ -0,0 +1 @@
module { x, y ? 0 } -> [menu]

View file

@ -0,0 +1,31 @@
Module {
comments: [],
header: Module(
ModuleHeader {
after_keyword: [],
params: Some(
ModuleParams {
params: [
@9-10 Identifier {
ident: "x",
},
@12-18 OptionalField(
"y",
@17-18 Num(
"0",
),
),
],
before_arrow: [],
after_arrow: [],
},
),
exposes: [
@25-29 ExposedName(
"menu",
),
],
interface_imports: None,
},
),
}

View file

@ -0,0 +1 @@
module { x, y ? 0 } -> [menu]

View file

@ -0,0 +1 @@
module { echo, read } -> [menu]

View file

@ -0,0 +1,28 @@
Module {
comments: [],
header: Module(
ModuleHeader {
after_keyword: [],
params: Some(
ModuleParams {
params: [
@8-12 Identifier {
ident: "echo",
},
@14-18 Identifier {
ident: "read",
},
],
before_arrow: [],
after_arrow: [],
},
),
exposes: [
@26-30 ExposedName(
"menu",
),
],
interface_imports: None,
},
),
}

View file

@ -0,0 +1 @@
module {echo, read } -> [menu]

View file

@ -0,0 +1,4 @@
module { echo, read } -> [
mainMenu,
credits,
]

View file

@ -0,0 +1,36 @@
Module {
comments: [],
header: Module(
ModuleHeader {
after_keyword: [],
params: Some(
ModuleParams {
params: [
@8-12 Identifier {
ident: "echo",
},
@14-18 Identifier {
ident: "read",
},
],
before_arrow: [],
after_arrow: [],
},
),
exposes: [
@25-33 ExposedName(
"mainMenu",
),
@39-46 SpaceBefore(
ExposedName(
"credits",
),
[
Newline,
],
),
],
interface_imports: None,
},
),
}

View file

@ -0,0 +1,2 @@
module {echo, read } -> [mainMenu,
credits ]

View file

@ -81,6 +81,7 @@ Full {
"Stdout",
),
},
params: None,
alias: None,
exposed: None,
},

View file

@ -2,7 +2,8 @@ Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
after_keyword: [],
params: None,
exposes: [
@23-26 ExposedName(
"Foo",

View file

@ -71,6 +71,7 @@ Full {
"Stdout",
),
},
params: None,
alias: None,
exposed: None,
},

View file

@ -212,6 +212,8 @@ mod test_snapshots {
fail/list_pattern_not_terminated.expr,
fail/list_pattern_weird_rest_pattern.expr,
fail/list_without_end.expr,
fail/module_params_with_missing_arrow.header,
fail/module_with_unfinished_params.header,
fail/multi_no_end.expr,
fail/pattern_binds_keyword.expr,
fail/pattern_in_parens_end.expr,
@ -330,6 +332,7 @@ mod test_snapshots {
pass/import_with_alias.moduledefs,
pass/import_with_comments.moduledefs,
pass/import_with_exposed.moduledefs,
pass/import_with_params.moduledefs,
pass/ingested_file.moduledefs,
pass/inline_import.expr,
pass/inline_ingested_file.expr,
@ -350,7 +353,11 @@ mod test_snapshots {
pass/mixed_docs.expr,
pass/module_def_newline.moduledefs,
pass/module_multiline_exposes.header,
pass/module_with_multiline_params_and_exposes.header,
pass/module_with_newline.header,
pass/module_with_optional_param.header,
pass/module_with_params.header,
pass/module_with_params_and_multiline_exposes.header,
pass/multi_backpassing.expr,
pass/multi_backpassing_in_def.moduledefs,
pass/multi_backpassing_with_apply.expr,

View file

@ -11,8 +11,9 @@ use roc_parse::{
WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, ModuleHeader, ModuleName, PackageEntry,
PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To, TypedIdent,
AppHeader, ExposedName, HostedHeader, ImportsEntry, ModuleHeader, ModuleName, ModuleParams,
PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
TypedIdent,
},
ident::{Accessor, UppercaseIdent},
};
@ -213,12 +214,41 @@ impl IterTokens for Header<'_> {
impl IterTokens for ModuleHeader<'_> {
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
let Self {
before_exposes: _,
after_keyword: _,
params,
exposes,
interface_imports: _,
} = self;
exposes.iter_tokens(arena)
params
.iter_tokens(arena)
.into_iter()
.chain(exposes.iter_tokens(arena))
.collect_in(arena)
}
}
impl<T> IterTokens for Option<T>
where
T: IterTokens,
{
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
match self {
Some(params) => params.iter_tokens(arena),
None => bumpvec![in arena;],
}
}
}
impl IterTokens for ModuleParams<'_> {
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
let Self {
params,
before_arrow: _,
after_arrow: _,
} = self;
params.iter_tokens(arena)
}
}

View file

@ -540,22 +540,8 @@ fn to_expr_report<'a>(
}
}
EExpr::Record(_erecord, pos) => {
let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack([
alloc.reflow(r"I am partway through parsing a record, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat([alloc.reflow("TODO provide more context.")]),
]);
Report {
filename,
doc,
title: "RECORD PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
EExpr::Record(erecord, pos) => {
to_record_report(alloc, lines, filename, erecord, *pos, start)
}
EExpr::OptionalValueInRecordBuilder(region) => {
@ -711,6 +697,31 @@ fn to_expr_report<'a>(
}
}
fn to_record_report<'a>(
alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo,
filename: PathBuf,
_parse_problem: &roc_parse::parser::ERecord<'a>,
pos: Position,
start: Position,
) -> Report<'a> {
let surroundings = Region::new(start, pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(pos));
let doc = alloc.stack([
alloc.reflow(r"I am partway through parsing a record, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat([alloc.reflow("TODO provide more context.")]),
]);
Report {
filename,
doc,
title: "RECORD PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
fn to_lambda_report<'a>(
alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo,
@ -1450,6 +1461,7 @@ fn to_import_report<'a>(
start: Position,
) -> Report<'a> {
use roc_parse::parser::EImport::*;
use roc_parse::parser::EImportParams;
match parse_problem {
Import(_pos) => unreachable!("another branch would be taken"),
@ -1475,32 +1487,76 @@ fn to_import_report<'a>(
.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),
Params(EImportParams::Indent(pos), _)
| 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),
alloc.reflow("Or module params, like:"),
alloc
.parser_suggestion("import Menu { echo, read }")
.indent(4),
]),
),
Params(EImportParams::Record(problem, pos), _) => {
to_record_report(alloc, lines, filename, problem, *pos, start)
}
Params(EImportParams::RecordApplyFound(region), _) => {
let surroundings = Region::new(start, region.end());
let region = lines.convert_region(*region);
let doc = alloc.stack([
alloc.reflow("I was partway through parsing module params, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.reflow("This looks like a record builder field, but those are not allowed in module params."),
]);
Report {
filename,
doc,
title: "RECORD BUILDER IN MODULE PARAMS".to_string(),
severity: Severity::RuntimeError,
}
}
Params(EImportParams::RecordUpdateFound(region), _) => {
let surroundings = Region::new(start, region.end());
let region = lines.convert_region(*region);
let doc = alloc.stack([
alloc.reflow("I was partway through parsing module params, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.reflow("It looks like you're trying to update a record, but module params require a standalone record literal."),
]);
Report {
filename,
doc,
title: "RECORD UPDATE IN MODULE PARAMS".to_string(),
severity: Severity::RuntimeError,
}
}
IndentAlias(pos) | Alias(pos) => to_unfinished_import_report(
alloc,
@ -1593,7 +1649,9 @@ fn to_import_report<'a>(
.indent(4),
]),
),
Space(problem, pos) => to_space_report(alloc, lines, filename, problem, *pos),
Space(problem, pos) | Params(EImportParams::Space(problem, pos), _) => {
to_space_report(alloc, lines, filename, problem, *pos)
}
}
}
@ -3392,6 +3450,8 @@ fn to_header_report<'a>(
to_provides_report(alloc, lines, filename, provides, *pos)
}
EHeader::Params(params, pos) => to_params_report(alloc, lines, filename, params, *pos),
EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, lines, filename, exposes, *pos),
EHeader::Imports(imports, pos) => to_imports_report(alloc, lines, filename, imports, *pos),
@ -3786,6 +3846,48 @@ fn to_provides_report<'a>(
}
}
fn to_params_report<'a>(
alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo,
filename: PathBuf,
parse_problem: &roc_parse::parser::EParams<'a>,
start: Position,
) -> Report<'a> {
use roc_parse::parser::EParams;
match parse_problem {
EParams::Pattern(error, pos) => to_precord_report(alloc, lines, filename, error, *pos),
EParams::BeforeArrow(pos) | EParams::Arrow(pos) | EParams::AfterArrow(pos) => {
let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack([
alloc
.reflow(r"I am partway through parsing a module header, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat([
alloc.reflow("I am expecting "),
alloc.keyword("->"),
alloc.reflow(" next, like:"),
]),
alloc
.parser_suggestion("module { echo, read } -> [menu]")
.indent(4),
]);
Report {
filename,
doc,
title: "WEIRD MODULE PARAMS".to_string(),
severity: Severity::RuntimeError,
}
}
EParams::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos),
}
}
fn to_exposes_report<'a>(
alloc: &'a RocDocAllocator<'a>,
lines: &LineInfo,