Parse module params in import

This commit is contained in:
Agus Zubiaga 2024-05-02 13:33:15 -03:00
parent 08825a9e90
commit e5a09b5de6
No known key found for this signature in database
19 changed files with 305 additions and 70 deletions

View file

@ -133,6 +133,8 @@ fn desugar_value_def<'a>(
ModuleImport(roc_parse::ast::ModuleImport {
before_name: _,
name: _,
// TODO: Desugar params
params,
alias: _,
exposed: _,
}) => *def,

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,19 @@ impl<'a> Formattable for ModuleImport<'a> {
let Self {
before_name,
name,
params,
alias,
exposed,
} = self;
!before_name.is_empty()
|| name.is_multiline()
|| match params {
Some(ModuleImportParams { before, params }) => {
!before.is_empty() || is_collection_multiline(params)
}
None => false,
}
|| alias.is_multiline()
|| match exposed {
Some(a) => a.keyword.is_multiline() || is_collection_multiline(&a.item),
@ -215,6 +222,7 @@ impl<'a> Formattable for ModuleImport<'a> {
let Self {
before_name,
name,
params,
alias,
exposed,
} = self;
@ -229,6 +237,10 @@ impl<'a> Formattable for ModuleImport<'a> {
buf.indent(indent);
name.format(buf, indent);
if let Some(params) = params {
// TODO: Format import params
}
if let Some(alias) = alias {
alias.format(buf, indent);
}

View file

@ -6,9 +6,9 @@ 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,
@ -600,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

@ -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;
@ -993,6 +994,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())
}),
@ -1000,6 +1002,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_field.map(|_| 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

@ -90,6 +90,7 @@ impl_space_problem! {
EImport<'a>,
EParams<'a>,
EImports,
EImportParams<'a>,
EInParens<'a>,
EClosure<'a>,
EList<'a>,
@ -541,6 +542,7 @@ pub enum EImport<'a> {
PackageShorthand(Position),
PackageShorthandDot(Position),
ModuleName(Position),
Params(EImportParams<'a>, Position),
IndentAs(Position),
As(Position),
IndentAlias(Position),
@ -563,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

@ -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,2 @@
import pf.Menu
import Menu

View file

@ -0,0 +1,83 @@
Defs {
tags: [
Index(2147483648),
Index(2147483649),
],
regions: [
@0-29,
@30-60,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
],
spaces: [
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,
},
),
],
}

View file

@ -0,0 +1,3 @@
import pf.Menu { echo, read }
import Menu { echo,
read }

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

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

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,
@ -249,8 +251,6 @@ mod test_snapshots {
fail/when_over_indented_underscore.expr,
fail/where_type_variable.expr,
fail/wild_case_arrow.expr,
fail/module_with_unfinished_params.header,
fail/module_params_with_missing_arrow.header,
malformed/bad_opaque_ref.expr,
malformed/malformed_ident_due_to_underscore.expr,
malformed/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399
@ -332,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,
@ -352,11 +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/module_with_optional_param.header,
pass/module_with_multiline_params_and_exposes.header,
pass/multi_backpassing.expr,
pass/multi_backpassing_in_def.moduledefs,
pass/multi_backpassing_with_apply.expr,

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"),
@ -1502,6 +1514,10 @@ fn to_import_report<'a>(
]),
)
}
Params(EImportParams::Record(problem, pos), _) => {
to_record_report(alloc, lines, filename, problem, *pos, start)
}
Params(_, _) => todo!("Report param errors"),
IndentAlias(pos) | Alias(pos) => to_unfinished_import_report(
alloc,
lines,
@ -1593,7 +1609,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)
}
}
}