Module Params' proposal import syntax

I previously implemented the syntax in "Proposal: Module and Package Changes" [1]:

```
import [map, map2] from JsonDecode as JD
```

However, we decided [2] to use the one that appears in "Proposal: Module Params" [3]:

```
import JsonDecode as JD exposing [map, map2]
```

The new implementation also now supports comments and newlines between all the tokens.

[1] https://docs.google.com/document/d/1E_77fO-44BtoBtXoVeWyGh1xN2KRTWTu8q6i25RNNx0/edit
[2] 405410612
[3] https://docs.google.com/document/d/110MwQi7Dpo1Y69ECFXyyvDWzF4OYv1BLojIm08qDTvg/edit
This commit is contained in:
Agus Zubiaga 2023-12-04 09:13:09 -03:00
parent 5cd084b73c
commit c56091ee3e
No known key found for this signature in database
20 changed files with 767 additions and 204 deletions

View file

@ -1,12 +1,13 @@
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens}; use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces}; use crate::collection::{fmt_collection, Braces};
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_default_newline, fmt_spaces, INDENT}; use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{ use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef, AbilityMember, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Pattern, Spaced,
TypeHeader, ValueDef, Spaces, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
}; };
use roc_parse::header::{ExposedName, ModuleName};
use roc_region::all::Loc; use roc_region::all::Loc;
/// A Located formattable value is also formattable /// A Located formattable value is also formattable
@ -198,10 +199,14 @@ impl<'a> Formattable for ValueDef<'a> {
ExpectFx { condition, .. } => condition.is_multiline(), ExpectFx { condition, .. } => condition.is_multiline(),
Dbg { condition, .. } => condition.is_multiline(), Dbg { condition, .. } => condition.is_multiline(),
ModuleImport { ModuleImport {
name: _, name,
alias: _, alias,
exposed, exposed,
} => is_collection_multiline(exposed), } => {
name.is_multiline()
|| alias.map_or(false, |a| a.is_multiline())
|| exposed.map_or(false, |(_, exp)| is_collection_multiline(&exp))
}
} }
} }
@ -250,30 +255,80 @@ impl<'a> Formattable for ValueDef<'a> {
exposed, exposed,
} => { } => {
buf.indent(indent); buf.indent(indent);
buf.push_str("import"); buf.push_str("import");
buf.spaces(1); fmt_import_body(buf, &name, &alias, &exposed, indent + INDENT);
if !exposed.is_empty() {
fmt_collection(buf, indent, Braces::Square, *exposed, Newlines::No);
buf.spaces(1);
buf.push_str("from");
buf.spaces(1);
}
buf.push_str(name.value.as_str());
if let Some(alias_name) = alias {
buf.spaces(1);
buf.push_str("as");
buf.spaces(1);
buf.push_str(alias_name.value.as_str());
}
} }
} }
} }
} }
fn fmt_import_body<'a>(
buf: &mut Buf,
name: &'a Loc<Spaced<'a, ModuleName>>,
alias: &'a Option<Loc<Spaced<'a, ModuleName>>>,
exposed: &'a Option<(
&[CommentOrNewline<'a>],
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
)>,
indent: u16,
) -> () {
let name_spaces = name.extract_spaces();
fmt_default_spaces(buf, name_spaces.before, indent);
buf.indent(indent);
buf.push_str(name.value.item().as_str());
fmt_default_spaces(buf, name_spaces.after, indent);
if let Some(alias_name) = alias {
let alias_spaces = alias_name.extract_spaces();
if !alias_spaces.before.is_empty() {
buf.ensure_ends_with_newline();
}
buf.indent(indent);
buf.push_str("as");
fmt_default_spaces(buf, alias_spaces.before, indent + INDENT);
buf.indent(indent + INDENT);
buf.push_str(alias_name.value.item().as_str());
fmt_default_spaces(buf, alias_spaces.after, indent);
if alias_name.is_multiline() {
buf.ensure_ends_with_newline();
}
}
if let Some((spaces_before_list, exposed_list)) = exposed {
let content_indent = if spaces_before_list.is_empty() {
if buf.ends_with_newline() {
indent
} else {
// Align list's closing brace with import keyword if we are in the same line
indent - INDENT
}
} else {
buf.ensure_ends_with_newline();
indent + INDENT
};
buf.indent(indent);
buf.push_str("exposing");
fmt_default_spaces(buf, spaces_before_list, content_indent);
fmt_collection(
buf,
content_indent,
Braces::Square,
*exposed_list,
Newlines::No,
);
}
}
fn fmt_general_def<L: Formattable>( fn fmt_general_def<L: Formattable>(
lhs: L, lhs: L,
buf: &mut Buf, buf: &mut Buf,

View file

@ -572,7 +572,11 @@ impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
} => ModuleImport { } => ModuleImport {
name: name.remove_spaces(arena), name: name.remove_spaces(arena),
alias: alias.map(|alias_name| alias_name.remove_spaces(arena)), alias: alias.map(|alias_name| alias_name.remove_spaces(arena)),
exposed: exposed.remove_spaces(arena), exposed: if let Some((spaces, exposed)) = exposed {
Some((spaces, exposed.remove_spaces(arena)))
} else {
None
},
}, },
} }
} }

View file

@ -456,11 +456,14 @@ pub enum ValueDef<'a> {
preceding_comment: Region, preceding_comment: Region,
}, },
/// e.g. `import [Req] as Http from InternalHttp`. /// e.g. `import InternalHttp as Http exposing [Req]`.
ModuleImport { ModuleImport {
name: Loc<crate::header::ModuleName<'a>>, name: Loc<Spaced<'a, crate::header::ModuleName<'a>>>,
alias: Option<Loc<crate::header::ModuleName<'a>>>, alias: Option<Loc<Spaced<'a, crate::header::ModuleName<'a>>>>,
exposed: Collection<'a, Loc<Spaced<'a, crate::header::ExposedName<'a>>>>, exposed: Option<(
&'a [CommentOrNewline<'a>],
Collection<'a, Loc<Spaced<'a, crate::header::ExposedName<'a>>>>,
)>,
}, },
} }
@ -1803,7 +1806,10 @@ impl<'a> Malformed for ValueDef<'a> {
name, name,
alias, alias,
exposed: _, exposed: _,
} => name.value.contains_dot() || alias.map_or(false, |x| x.value.contains_dot()), } => {
name.value.item().contains_dot()
|| alias.map_or(false, |x| x.value.item().contains_dot())
}
} }
} }
} }

View file

@ -838,54 +838,53 @@ pub fn parse_single_def<'a>(
} }
} }
#[inline(always)]
fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport> { fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport> {
map!( map!(
skip_first!( skip_first!(
crate::parser::keyword_e(crate::keyword::IMPORT, EImport::Import),
and!( and!(
crate::parser::keyword_e(crate::keyword::IMPORT, EImport::Import), spaces_around(loc!(map!(
spaces() crate::module::module_name_help(EImport::ModuleName),
), Spaced::Item
and!( ))),
optional(skip_second!(
skip_second!(
collection_trailing_sep_e!(
word1(b'[', EImport::ExposedListStart),
loc!(import_exposed_name()),
word1(b',', EImport::ExposedListEnd),
word1(b']', EImport::ExposedListEnd),
Spaced::SpaceBefore
),
spaces()
),
skip_second!(
crate::parser::keyword_e(crate::keyword::FROM, EImport::From),
spaces()
)
)),
and!( and!(
loc!(crate::module::module_name_help(EImport::ModuleName)), optional(skip_first!(
optional(backtrackable(skip_first!( crate::parser::keyword_e(crate::keyword::AS, EImport::As),
spaces_around(loc!(map!(
crate::module::module_name_help(EImport::ModuleName),
Spaced::Item
)))
)),
optional(skip_first!(
crate::parser::keyword_e(crate::keyword::EXPOSING, EImport::Exposing),
and!( and!(
spaces(), spaces(),
and!( collection_trailing_sep_e!(
crate::parser::keyword_e(crate::keyword::AS, EImport::As), word1(b'[', EImport::ExposedListStart),
spaces() loc!(import_exposed_name()),
word1(b',', EImport::ExposedListEnd),
word1(b']', EImport::ExposedListEnd),
Spaced::SpaceBefore
) )
), )
loc!(crate::module::module_name_help(EImport::Alias)) ))
)))
) )
) )
), ),
|(exposed, (name, alias)): ( |(name, (alias, exposed)): (
Option<Collection<'a, Loc<Spaced<'a, crate::header::ExposedName<'a>>>>>, Loc<Spaced<'a, crate::header::ModuleName<'a>>>,
_ (
Option<Loc<Spaced<'a, crate::header::ModuleName<'a>>>>,
Option<(
&'a [CommentOrNewline<'a>],
Collection<'a, Loc<Spaced<'a, crate::header::ExposedName<'a>>>>
)>
)
)| { )| {
ValueDef::ModuleImport { ValueDef::ModuleImport {
exposed: exposed.unwrap_or_else(Collection::empty),
name, name,
alias, alias,
exposed,
} }
} }
) )

View file

@ -12,7 +12,7 @@ pub const EXPECT_FX: &str = "expect-fx";
pub const CRASH: &str = "crash"; pub const CRASH: &str = "crash";
// These keywords are valid in imports // These keywords are valid in imports
pub const FROM: &str = "from"; pub const EXPOSING: &str = "exposing";
// These keywords are valid in types // These keywords are valid in types
pub const IMPLEMENTS: &str = "implements"; pub const IMPLEMENTS: &str = "implements";

View file

@ -522,13 +522,13 @@ pub enum EExpect<'a> {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EImport { pub enum EImport {
Import(Position), Import(Position),
ExposedListStart(Position),
ExposedName(Position),
ExposedListEnd(Position),
From(Position),
ModuleName(Position), ModuleName(Position),
As(Position), As(Position),
Alias(Position), Alias(Position),
Exposing(Position),
ExposedListStart(Position),
ExposedName(Position),
ExposedListEnd(Position),
Space(BadInputError, Position), Space(BadInputError, Position),
} }

View file

@ -19,7 +19,7 @@ Defs {
"Json", "Json",
), ),
alias: None, alias: None,
exposed: [], exposed: None,
}, },
], ],
} }

View file

@ -1,2 +1,3 @@
import JsonEncode as JE import JsonEncode as JE
import BytesDecode as BD import BytesDecode as BD

View file

@ -4,20 +4,18 @@ Defs {
Index(2147483649), Index(2147483649),
], ],
regions: [ regions: [
@0-23, @0-24,
@24-49, @24-49,
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 0, length = 1), Slice(start = 0, length = 0),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 1, length = 0), Slice(start = 0, length = 0),
],
spaces: [
Newline,
], ],
spaces: [],
type_defs: [], type_defs: [],
value_defs: [ value_defs: [
ModuleImport { ModuleImport {
@ -25,11 +23,16 @@ Defs {
"JsonEncode", "JsonEncode",
), ),
alias: Some( alias: Some(
@21-23 ModuleName( @21-23 SpaceAfter(
"JE", ModuleName(
"JE",
),
[
Newline,
],
), ),
), ),
exposed: [], exposed: None,
}, },
ModuleImport { ModuleImport {
name: @31-42 ModuleName( name: @31-42 ModuleName(
@ -40,7 +43,7 @@ Defs {
"BD", "BD",
), ),
), ),
exposed: [], exposed: None,
}, },
], ],
} }

View file

@ -0,0 +1,68 @@
import
# comment
Json as J exposing [map]
import Json
# comment
as J exposing [map]
import Json
as
# comment
J
import Json
as
# comment
J
exposing [map]
import Json
as
# comment
J
exposing [
map,
map2,
]
import Json
# comment
exposing [
map,
map2,
]
import Json as J
# comment
exposing [map]
import Json
exposing
# comment
[map]
import Json as J
exposing
# comment
[map]
import Json as J exposing [
# comment
map,
]
import
# comment 1
Json
# comment 2
as
# comment 3
J
# comment 4
exposing
# comment 5
[
# comment 6
map,
]

View file

@ -0,0 +1,431 @@
Defs {
tags: [
Index(2147483648),
Index(2147483649),
Index(2147483650),
Index(2147483651),
Index(2147483652),
Index(2147483653),
Index(2147483654),
Index(2147483655),
Index(2147483656),
Index(2147483657),
Index(2147483658),
],
regions: [
@0-49,
@51-100,
@102-138,
@138-187,
@189-252,
@254-312,
@314-363,
@365-409,
@411-460,
@462-513,
@515-672,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 2),
Slice(start = 2, length = 2),
Slice(start = 4, length = 0),
Slice(start = 4, length = 2),
Slice(start = 6, length = 2),
Slice(start = 8, length = 2),
Slice(start = 10, length = 2),
Slice(start = 12, length = 2),
Slice(start = 14, length = 2),
Slice(start = 16, length = 2),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
Slice(start = 4, 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),
],
spaces: [
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [
ModuleImport {
name: @25-29 SpaceBefore(
ModuleName(
"Json",
),
[
Newline,
LineComment(
" comment",
),
],
),
alias: Some(
@33-34 ModuleName(
"J",
),
),
exposed: Some(
(
[],
[
@45-48 ExposedName(
"map",
),
],
),
),
},
ModuleImport {
name: @58-62 SpaceAfter(
ModuleName(
"Json",
),
[
Newline,
LineComment(
" comment",
),
],
),
alias: Some(
@84-85 ModuleName(
"J",
),
),
exposed: Some(
(
[],
[
@96-99 ExposedName(
"map",
),
],
),
),
},
ModuleImport {
name: @109-113 ModuleName(
"Json",
),
alias: Some(
@135-136 SpaceBefore(
SpaceAfter(
ModuleName(
"J",
),
[
Newline,
Newline,
],
),
[
Newline,
LineComment(
" comment",
),
],
),
),
exposed: None,
},
ModuleImport {
name: @145-149 ModuleName(
"Json",
),
alias: Some(
@171-172 SpaceBefore(
ModuleName(
"J",
),
[
Newline,
LineComment(
" comment",
),
],
),
),
exposed: Some(
(
[],
[
@183-186 ExposedName(
"map",
),
],
),
),
},
ModuleImport {
name: @196-200 ModuleName(
"Json",
),
alias: Some(
@222-223 SpaceBefore(
ModuleName(
"J",
),
[
Newline,
LineComment(
" comment",
),
],
),
),
exposed: Some(
(
[],
[
@234-237 ExposedName(
"map",
),
@247-251 SpaceBefore(
ExposedName(
"map2",
),
[
Newline,
],
),
],
),
),
},
ModuleImport {
name: @261-265 SpaceAfter(
ModuleName(
"Json",
),
[
Newline,
LineComment(
" comment",
),
],
),
alias: None,
exposed: Some(
(
[],
[
@294-297 ExposedName(
"map",
),
@307-311 SpaceBefore(
ExposedName(
"map2",
),
[
Newline,
],
),
],
),
),
},
ModuleImport {
name: @321-325 ModuleName(
"Json",
),
alias: Some(
@329-330 SpaceAfter(
ModuleName(
"J",
),
[
Newline,
LineComment(
" comment",
),
],
),
),
exposed: Some(
(
[],
[
@359-362 ExposedName(
"map",
),
],
),
),
},
ModuleImport {
name: @372-376 ModuleName(
"Json",
),
alias: None,
exposed: Some(
(
[
Newline,
LineComment(
" comment",
),
],
[
@405-408 ExposedName(
"map",
),
],
),
),
},
ModuleImport {
name: @418-422 ModuleName(
"Json",
),
alias: Some(
@426-427 ModuleName(
"J",
),
),
exposed: Some(
(
[
Newline,
LineComment(
" comment",
),
],
[
@456-459 ExposedName(
"map",
),
],
),
),
},
ModuleImport {
name: @469-473 ModuleName(
"Json",
),
alias: Some(
@477-478 ModuleName(
"J",
),
),
exposed: Some(
(
[],
[
@508-511 SpaceBefore(
SpaceAfter(
ExposedName(
"map",
),
[
Newline,
],
),
[
Newline,
LineComment(
" comment",
),
],
),
],
),
),
},
ModuleImport {
name: @542-546 SpaceBefore(
SpaceAfter(
ModuleName(
"Json",
),
[
Newline,
LineComment(
" comment 2",
),
],
),
[
Newline,
LineComment(
" comment 1",
),
],
),
alias: Some(
@590-591 SpaceBefore(
SpaceAfter(
ModuleName(
"J",
),
[
Newline,
LineComment(
" comment 4",
),
],
),
[
Newline,
LineComment(
" comment 3",
),
],
),
),
exposed: Some(
(
[
Newline,
LineComment(
" comment 5",
),
],
[
@663-666 SpaceBefore(
SpaceAfter(
ExposedName(
"map",
),
[
Newline,
],
),
[
Newline,
LineComment(
" comment 6",
),
],
),
],
),
),
},
],
}

View file

@ -0,0 +1,57 @@
import
# comment
Json as J exposing [map]
import Json
# comment
as J exposing [map]
import Json as
# comment
J
import Json as
# comment
J exposing [map]
import Json as
# comment
J exposing [map,
map2]
import Json
# comment
exposing [map,
map2]
import Json as J
# comment
exposing [map]
import Json exposing
# comment
[map]
import Json as J exposing
# comment
[map]
import Json as J exposing [
# comment
map
]
import
# comment 1
Json
# comment 2
as
# comment 3
J
# comment 4
exposing
# comment 5
[
# comment 6
map
]

View file

@ -1,6 +0,0 @@
import [map, Decode] from JsonDecode as JD
import [
# some comment
map,
Decode,
] from JsonDecode as JD

View file

@ -1,78 +0,0 @@
Defs {
tags: [
Index(2147483648),
Index(2147483649),
],
regions: [
@0-42,
@43-110,
],
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 {
name: @26-36 ModuleName(
"JsonDecode",
),
alias: Some(
@40-42 ModuleName(
"JD",
),
),
exposed: [
@8-11 ExposedName(
"map",
),
@13-19 ExposedName(
"Decode",
),
],
},
ModuleImport {
name: @94-104 ModuleName(
"JsonDecode",
),
alias: Some(
@108-110 ModuleName(
"JD",
),
),
exposed: [
@71-74 SpaceBefore(
ExposedName(
"map",
),
[
Newline,
LineComment(
" some comment",
),
],
),
@80-86 SpaceBefore(
SpaceAfter(
ExposedName(
"Decode",
),
[
Newline,
],
),
[
Newline,
],
),
],
},
],
}

View file

@ -1,6 +0,0 @@
import [map, Decode] from JsonDecode as JD
import [
# some comment
map,
Decode
] from JsonDecode as JD

View file

@ -1,5 +1,6 @@
import [map, Decoder] from Json import Json exposing [map, Decoder]
import [ import Json exposing [
map, map,
Decoder, Decoder,
] from Json ]
import Json exposing []

View file

@ -2,56 +2,83 @@ Defs {
tags: [ tags: [
Index(2147483648), Index(2147483648),
Index(2147483649), Index(2147483649),
Index(2147483650),
], ],
regions: [ regions: [
@0-31, @0-35,
@32-67, @36-75,
@76-99,
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 0, length = 1), Slice(start = 0, length = 1),
Slice(start = 1, length = 1),
], ],
space_after: [ space_after: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
Slice(start = 1, length = 0), Slice(start = 1, length = 0),
Slice(start = 2, length = 0),
], ],
spaces: [ spaces: [
Newline, Newline,
Newline,
], ],
type_defs: [], type_defs: [],
value_defs: [ value_defs: [
ModuleImport { ModuleImport {
name: @27-31 ModuleName( name: @7-11 ModuleName(
"Json", "Json",
), ),
alias: None, alias: None,
exposed: [ exposed: Some(
@8-11 ExposedName( (
"map", [],
),
@13-20 ExposedName(
"Decoder",
),
],
},
ModuleImport {
name: @63-67 ModuleName(
"Json",
),
alias: None,
exposed: [
@40-43 ExposedName(
"map",
),
@49-56 SpaceBefore(
ExposedName(
"Decoder",
),
[ [
Newline, @22-25 ExposedName(
"map",
),
@27-34 ExposedName(
"Decoder",
),
], ],
), ),
], ),
},
ModuleImport {
name: @43-47 ModuleName(
"Json",
),
alias: None,
exposed: Some(
(
[],
[
@58-61 ExposedName(
"map",
),
@67-74 SpaceBefore(
ExposedName(
"Decoder",
),
[
Newline,
],
),
],
),
),
},
ModuleImport {
name: @83-87 ModuleName(
"Json",
),
alias: None,
exposed: Some(
(
[],
[],
),
),
}, },
], ],
} }

View file

@ -1,3 +1,4 @@
import [map, Decoder] from Json import Json exposing [map, Decoder]
import [map, import Json exposing [map,
Decoder] from Json Decoder]
import Json exposing []

View file

@ -326,7 +326,7 @@ mod test_snapshots {
pass/if_def.expr, pass/if_def.expr,
pass/import.moduledefs, pass/import.moduledefs,
pass/import_with_alias.moduledefs, pass/import_with_alias.moduledefs,
pass/import_with_everything.moduledefs, pass/import_with_comments.moduledefs,
pass/import_with_exposed.moduledefs, pass/import_with_exposed.moduledefs,
pass/int_with_underscore.expr, pass/int_with_underscore.expr,
pass/interface_with_newline.header, pass/interface_with_newline.header,