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

@ -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
)
}