New module header

Implements the new `module` header syntax as described in "module and package changes" [1]:

```
module [Request, Response, req]
```

The old syntax should still work fine, and is automatically upgraded to the new one
when running `roc format`.

[1] https://docs.google.com/document/d/1E_77fO-44BtoBtXoVeWyGh1xN2KRTWTu8q6i25RNNx0/edit
This commit is contained in:
Agus Zubiaga 2024-02-18 19:10:54 -03:00
parent 7754dd7ef7
commit 057a18573a
No known key found for this signature in database
92 changed files with 1445 additions and 1563 deletions

View File

@ -8,7 +8,6 @@ use roc_fmt::def::fmt_defs;
use roc_fmt::module::fmt_module; use roc_fmt::module::fmt_module;
use roc_fmt::spaces::RemoveSpaces; use roc_fmt::spaces::RemoveSpaces;
use roc_fmt::{Ast, Buf}; use roc_fmt::{Ast, Buf};
use roc_parse::ast::Defs;
use roc_parse::module::parse_module_defs; use roc_parse::module::parse_module_defs;
use roc_parse::{module, parser::SyntaxError, state::State}; use roc_parse::{module, parser::SyntaxError, state::State};
@ -226,7 +225,9 @@ fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'
let (module, state) = module::parse_header(arena, State::new(src.as_bytes())) let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?; .map_err(|e| SyntaxError::Header(e.problem))?;
let defs = parse_module_defs(arena, state, Defs::default())?; let (module, defs) = module.upgrade_header_imports(arena);
let defs = parse_module_defs(arena, state, defs)?;
Ok(Ast { module, defs }) Ok(Ast { module, defs })
} }

View File

@ -1,6 +1,4 @@
interface Bool module [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
imports []
## Defines a type that can be compared for total equality. ## Defines a type that can be compared for total equality.
## ##

View File

@ -2,9 +2,7 @@
## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform). ## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform).
## - To improve performance in rare cases. ## - To improve performance in rare cases.
## ##
interface Box module [box, unbox]
exposes [box, unbox]
imports []
## Allocates a value on the heap. Boxing is an expensive process as it copies ## Allocates a value on the heap. Boxing is an expensive process as it copies
## the value from the stack to the heap. This may provide a performance ## the value from the stack to the heap. This may provide a performance

View File

@ -1,5 +1,4 @@
interface Decode module [
exposes [
DecodeError, DecodeError,
DecodeResult, DecodeResult,
Decoder, Decoder,
@ -30,7 +29,6 @@ interface Decode
fromBytes, fromBytes,
mapResult, mapResult,
] ]
imports []
import List import List
import Result exposing [Result] import Result exposing [Result]

View File

@ -1,5 +1,4 @@
interface Dict module [
exposes [
Dict, Dict,
empty, empty,
withCapacity, withCapacity,
@ -29,7 +28,6 @@ interface Dict
map, map,
joinMap, joinMap,
] ]
imports []
import Bool exposing [Bool, Eq] import Bool exposing [Bool, Eq]
import Result exposing [Result] import Result exposing [Result]

View File

@ -1,5 +1,4 @@
interface Encode module [
exposes [
Encoder, Encoder,
Encoding, Encoding,
toEncoder, toEncoder,
@ -28,7 +27,6 @@ interface Encode
append, append,
toBytes, toBytes,
] ]
imports []
import Num exposing [ import Num exposing [
U8, U8,

View File

@ -1,5 +1,4 @@
interface Hash module [
exposes [
Hash, Hash,
Hasher, Hasher,
hash, hash,
@ -20,7 +19,7 @@ interface Hash
hashStrBytes, hashStrBytes,
hashList, hashList,
hashUnordered, hashUnordered,
] imports [] ]
import Bool exposing [Bool] import Bool exposing [Bool]
import List import List

View File

@ -1,5 +1,4 @@
interface Inspect module [
exposes [
Inspect, Inspect,
Inspector, Inspector,
InspectFormatter, InspectFormatter,
@ -35,7 +34,6 @@ interface Inspect
toInspector, toInspector,
toStr, toStr,
] ]
imports []
import Bool exposing [Bool] import Bool exposing [Bool]
import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec] import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]

View File

@ -1,5 +1,4 @@
interface List module [
exposes [
isEmpty, isEmpty,
get, get,
set, set,
@ -71,7 +70,6 @@ interface List
countIf, countIf,
chunksOf, chunksOf,
] ]
imports []
import Bool exposing [Bool, Eq] import Bool exposing [Bool, Eq]
import Result exposing [Result] import Result exposing [Result]

View File

@ -1,5 +1,4 @@
interface Num module [
exposes [
Num, Num,
Int, Int,
Frac, Frac,
@ -157,7 +156,6 @@ interface Num
f32FromParts, f32FromParts,
f64FromParts, f64FromParts,
] ]
imports []
import Bool exposing [Bool] import Bool exposing [Bool]
import Result exposing [Result] import Result exposing [Result]

View File

@ -1,6 +1,4 @@
interface Result module [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
exposes [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
imports []
import Bool exposing [Bool] import Bool exposing [Bool]

View File

@ -1,5 +1,4 @@
interface Set module [
exposes [
Set, Set,
empty, empty,
withCapacity, withCapacity,
@ -24,7 +23,6 @@ interface Set
map, map,
joinMap, joinMap,
] ]
imports []
import List import List
import Bool exposing [Bool, Eq] import Bool exposing [Bool, Eq]

View File

@ -326,8 +326,7 @@
## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)). ## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)).
## ##
## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice. ## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice.
interface Str module [
exposes [
Utf8Problem, Utf8Problem,
Utf8ByteProblem, Utf8ByteProblem,
concat, concat,
@ -369,7 +368,6 @@ interface Str
withPrefix, withPrefix,
contains, contains,
] ]
imports []
import Bool exposing [Bool] import Bool exposing [Bool]
import Result exposing [Result] import Result exposing [Result]

View File

@ -1,12 +1,10 @@
## THIS MODULE IS DEPRECATED AND CURRENTLY IN THE PROCESS OF BEING REMOVED ## THIS MODULE IS DEPRECATED AND CURRENTLY IN THE PROCESS OF BEING REMOVED
## FROM STD LIBRARY ## FROM STD LIBRARY
interface TotallyNotJson module [
exposes [
Json, Json,
json, json,
jsonWithOptions, jsonWithOptions,
] ]
imports []
import List import List
import Str import Str

View File

@ -506,7 +506,7 @@ fn canonicalize_claimed_ability_impl<'a>(
// OPTION-1: The implementation identifier is the only identifier of that name in the // OPTION-1: The implementation identifier is the only identifier of that name in the
// scope. For example, // scope. For example,
// //
// interface F imports [] exposes [] // module []
// //
// Hello := {} implements [Encoding.{ toEncoder }] // Hello := {} implements [Encoding.{ toEncoder }]
// //
@ -518,7 +518,9 @@ fn canonicalize_claimed_ability_impl<'a>(
// OPTION-2: The implementation identifier is a unique shadow of the ability member, // OPTION-2: The implementation identifier is a unique shadow of the ability member,
// which has also been explicitly imported. For example, // which has also been explicitly imported. For example,
// //
// interface F imports [Encoding.{ toEncoder }] exposes [] // module []
//
// import Encoding exposing [toEncoder]
// //
// Hello := {} implements [Encoding.{ toEncoder }] // Hello := {} implements [Encoding.{ toEncoder }]
// //

View File

@ -8,7 +8,7 @@ use bumpalo::Bump;
use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces}; use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces};
use roc_parse::header::{ use roc_parse::header::{
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry, PackageHeader, ImportsKeyword, Keyword, KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader,
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires, PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
}; };
@ -18,8 +18,8 @@ use roc_region::all::Loc;
pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) { pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
fmt_comments_only(buf, module.comments.iter(), NewlineAt::Bottom, 0); fmt_comments_only(buf, module.comments.iter(), NewlineAt::Bottom, 0);
match &module.header { match &module.header {
Header::Interface(header) => { Header::Module(header) => {
fmt_interface_header(buf, header); fmt_module_header(buf, header);
} }
Header::App(header) => { Header::App(header) => {
fmt_app_header(buf, header); fmt_app_header(buf, header);
@ -171,20 +171,19 @@ impl<'a, K: Formattable, V: Formattable> Formattable for KeywordItem<'a, K, V> {
} }
} }
pub fn fmt_interface_header<'a>(buf: &mut Buf, header: &'a InterfaceHeader<'a>) { pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
buf.indent(0); buf.indent(0);
buf.push_str("interface"); buf.push_str("module");
if header.before_exposes.iter().all(|c| c.is_newline()) {
buf.spaces(1);
fmt_exposes(buf, header.exposes, 0);
} else {
let indent = INDENT; let indent = INDENT;
fmt_default_spaces(buf, header.before_name, indent);
// module name fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent); fmt_exposes(buf, header.exposes, indent);
buf.push_str(header.name.value.as_str()); };
header.exposes.keyword.format(buf, indent);
fmt_exposes(buf, header.exposes.item, indent);
header.imports.keyword.format(buf, indent);
fmt_imports(buf, header.imports.item, indent);
} }
pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) { pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {

View File

@ -10,9 +10,9 @@ use roc_parse::{
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
}, },
header::{ header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem, AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
ModuleName, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
ProvidesTo, To, TypedIdent, TypedIdent,
}, },
ident::{BadIdent, UppercaseIdent}, ident::{BadIdent, UppercaseIdent},
}; };
@ -283,11 +283,10 @@ impl<'a> RemoveSpaces<'a> for ProvidesTo<'a> {
impl<'a> RemoveSpaces<'a> for Module<'a> { impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, arena: &'a Bump) -> Self {
let header = match &self.header { let header = match &self.header {
Header::Interface(header) => Header::Interface(InterfaceHeader { Header::Module(header) => Header::Module(ModuleHeader {
before_name: &[], before_exposes: &[],
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena), exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena), interface_imports: header.interface_imports.remove_spaces(arena),
}), }),
Header::App(header) => Header::App(AppHeader { Header::App(header) => Header::App(AppHeader {
before_name: &[], before_name: &[],

View File

@ -6109,9 +6109,7 @@ In roc, functions are always written as a lambda, like{}
report_header_problem_as( report_header_problem_as(
indoc!( indoc!(
r" r"
interface Foobar module [main, @Foo]
exposes [main, @Foo]
imports [pf.Task, Base64]
" "
), ),
indoc!( indoc!(
@ -6120,39 +6118,12 @@ In roc, functions are always written as a lambda, like{}
I am partway through parsing an `exposes` list, but I got stuck here: I am partway through parsing an `exposes` list, but I got stuck here:
1 interface Foobar 1 module [main, @Foo]
2 exposes [main, @Foo]
^ ^
I was expecting a type name, value name or function name next, like I was expecting a type name, value name or function name next, like
exposes [Animal, default, tame] [Animal, default, tame]
"
),
)
}
#[test]
fn invalid_module_name() {
report_header_problem_as(
indoc!(
r"
interface foobar
exposes [main, @Foo]
imports [pf.Task, Base64]
"
),
indoc!(
r"
WEIRD MODULE NAME in /code/proj/Main.roc
I am partway through parsing a header, but got stuck here:
1 interface foobar
^
I am expecting a module name next, like BigNum or Main. Module names
must start with an uppercase letter.
" "
), ),
) )

View File

@ -49,8 +49,8 @@ use roc_mono::{drop_specialization, inc_dec};
use roc_packaging::cache::RocCacheDir; use roc_packaging::cache::RocCacheDir;
use roc_parse::ast::{self, CommentOrNewline, ExtractSpaces, Spaced, ValueDef}; use roc_parse::ast::{self, CommentOrNewline, ExtractSpaces, Spaced, ValueDef};
use roc_parse::header::{ use roc_parse::header::{
ExposedName, HeaderType, ImportsKeywordItem, PackageEntry, PackageHeader, PlatformHeader, To, self, ExposedName, HeaderType, ImportsKeywordItem, PackageEntry, PackageHeader, PlatformHeader,
TypedIdent, To, TypedIdent,
}; };
use roc_parse::module::parse_module_defs; use roc_parse::module::parse_module_defs;
use roc_parse::parser::{FileError, SourceError, SyntaxError}; use roc_parse::parser::{FileError, SourceError, SyntaxError};
@ -644,7 +644,7 @@ struct CanAndCon {
enum PlatformPath<'a> { enum PlatformPath<'a> {
NotSpecified, NotSpecified,
Valid(To<'a>), Valid(To<'a>),
RootIsInterface, RootIsModule,
RootIsHosted, RootIsHosted,
RootIsPlatformModule, RootIsPlatformModule,
} }
@ -1204,8 +1204,9 @@ fn adjust_header_paths<'a>(
{ {
debug_assert_eq!(*header_id, header_output.module_id); debug_assert_eq!(*header_id, header_output.module_id);
if let HeaderType::Interface { name, .. } = header_type { if let HeaderType::Module { name, .. } = header_type {
// Interface modules can have names like Foo.Bar.Baz, // [modules-revamp] TODO: Privacy changes
// Modules can have names like Foo.Bar.Baz,
// in which case we need to adjust the src_dir to // in which case we need to adjust the src_dir to
// remove the "Bar/Baz" directories in order to correctly // remove the "Bar/Baz" directories in order to correctly
// resolve this interface module's imports! // resolve this interface module's imports!
@ -2313,13 +2314,13 @@ fn update<'a>(
state.exposed_modules = exposes_ids; state.exposed_modules = exposes_ids;
} }
} }
Builtin { .. } | Interface { .. } => { Builtin { .. } | Module { .. } => {
if header.is_root_module { if header.is_root_module {
debug_assert!(matches!( debug_assert!(matches!(
state.platform_path, state.platform_path,
PlatformPath::NotSpecified PlatformPath::NotSpecified
)); ));
state.platform_path = PlatformPath::RootIsInterface; state.platform_path = PlatformPath::RootIsModule;
} }
} }
Hosted { .. } => { Hosted { .. } => {
@ -2956,7 +2957,7 @@ fn update<'a>(
// This happens due to abilities. In detail, consider // This happens due to abilities. In detail, consider
// //
// # Default module // # Default module
// interface Default exposes [default, getDefault] // module [default, getDefault]
// //
// Default implements default : {} -> a where a implements Default // Default implements default : {} -> a where a implements Default
// //
@ -3198,7 +3199,7 @@ fn finish_specialization<'a>(
let module_id = state.root_id; let module_id = state.root_id;
let uses_prebuilt_platform = match platform_data { let uses_prebuilt_platform = match platform_data {
Some(data) => data.is_prebuilt, Some(data) => data.is_prebuilt,
// If there's no platform data (e.g. because we're building an interface module) // If there's no platform data (e.g. because we're building a module)
// then there's no prebuilt platform either! // then there's no prebuilt platform either!
None => false, None => false,
}; };
@ -3346,12 +3347,12 @@ fn load_package_from_disk<'a>(
match parsed { match parsed {
Ok(( Ok((
ast::Module { ast::Module {
header: ast::Header::Interface(header), header: ast::Header::Module(header),
.. ..
}, },
_parse_state, _parse_state,
)) => Err(LoadingProblem::UnexpectedHeader(format!( )) => Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{header:?}" "expected platform/package module, got Module with header\n{header:?}"
))), ))),
Ok(( Ok((
ast::Module { ast::Module {
@ -3477,23 +3478,25 @@ fn load_builtin_module_help<'a>(
match parsed { match parsed {
Ok(( Ok((
ast::Module { ast::Module {
header: ast::Header::Interface(header), header: ast::Header::Module(header),
comments, comments,
}, },
parse_state, parse_state,
)) => { )) => {
let name_stem = arena.alloc_str(filename.file_stem().unwrap().to_str().unwrap());
let info = HeaderInfo { let info = HeaderInfo {
filename, filename,
is_root_module, is_root_module,
opt_shorthand, opt_shorthand,
packages: &[], packages: &[],
header_type: HeaderType::Builtin { header_type: HeaderType::Builtin {
name: header.name.value, name: header::ModuleName::new(name_stem),
exposes: unspace(arena, header.exposes.item.items), exposes: unspace(arena, header.exposes.items),
generates_with: &[], generates_with: &[],
}, },
module_comments: comments, module_comments: comments,
header_imports: Some(header.imports), header_imports: header.interface_imports,
}; };
(info, parse_state) (info, parse_state)
@ -3693,45 +3696,6 @@ fn find_task<T>(local: &Worker<T>, global: &Injector<T>, stealers: &[Stealer<T>]
}) })
} }
fn verify_interface_matches_file_path<'a>(
interface_name: Loc<roc_parse::header::ModuleName<'a>>,
path: &Path,
state: &roc_parse::state::State<'a>,
) -> Result<(), LoadingProblem<'a>> {
let module_parts = interface_name.value.as_str().split(MODULE_SEPARATOR).rev();
let mut is_mismatched = false;
let mut opt_path = Some(path);
for part in module_parts {
match opt_path.and_then(|path| path.file_stem().map(|fi| (path, fi))) {
None => {
is_mismatched = true;
break;
}
Some((path, fi)) => {
if fi != part {
is_mismatched = true;
break;
}
opt_path = path.parent();
}
}
}
if !is_mismatched {
return Ok(());
}
use roc_parse::parser::EHeader;
let syntax_problem =
SyntaxError::Header(EHeader::InconsistentModuleName(interface_name.region));
let problem = LoadingProblem::ParsingFailed(FileError {
problem: SourceError::new(syntax_problem, state),
filename: path.to_path_buf(),
});
Err(problem)
}
#[derive(Debug)] #[derive(Debug)]
struct HeaderOutput<'a> { struct HeaderOutput<'a> {
module_id: ModuleId, module_id: ModuleId,
@ -3798,48 +3762,35 @@ fn parse_header<'a>(
match parsed { match parsed {
Ok(( Ok((
ast::Module { ast::Module {
header: ast::Header::Interface(header), header: ast::Header::Module(header),
comments, comments,
}, },
parse_state, parse_state,
)) => { )) => {
verify_interface_matches_file_path(header.name, &filename, &parse_state)?; let module_name = match opt_expected_module_name {
Some(pq_name) => arena.alloc_str(pq_name.as_inner().as_str()),
None => {
// [modules-revamp] [privacy-changes] TODO: Support test/check on nested modules
arena.alloc_str(filename.file_stem().unwrap().to_str().unwrap())
}
};
let header_name_region = header.name.region;
let info = HeaderInfo { let info = HeaderInfo {
filename, filename,
is_root_module, is_root_module,
opt_shorthand, opt_shorthand,
packages: &[], packages: &[],
header_type: HeaderType::Interface { header_type: HeaderType::Module {
name: header.name.value, name: roc_parse::header::ModuleName::new(module_name),
exposes: unspace(arena, header.exposes.item.items), exposes: unspace(arena, header.exposes.items),
}, },
module_comments: comments, module_comments: comments,
header_imports: Some(header.imports), header_imports: header.interface_imports,
}; };
let (module_id, module_name, header) = let (module_id, _, header) =
build_header(info, parse_state.clone(), module_ids, module_timing)?; build_header(info, parse_state.clone(), module_ids, module_timing)?;
if let Some(expected_module_name) = opt_expected_module_name {
if expected_module_name != module_name {
let problem = SourceError::new(
IncorrectModuleName {
module_id,
found: Loc::at(header_name_region, module_name),
expected: expected_module_name,
},
&parse_state,
);
let problem = LoadingProblem::IncorrectModuleName(FileError {
problem,
filename: header.module_path,
});
return Err(problem);
}
}
Ok(HeaderOutput { Ok(HeaderOutput {
module_id, module_id,
msg: Msg::Header(header), msg: Msg::Header(header),
@ -4217,14 +4168,9 @@ fn build_header<'a>(
// Package modules do not have names. // Package modules do not have names.
String::new().into() String::new().into()
} }
HeaderType::Interface { name, .. } HeaderType::Module { name, .. }
| HeaderType::Builtin { name, .. } | HeaderType::Builtin { name, .. }
| HeaderType::Hosted { name, .. } => { | HeaderType::Hosted { name, .. } => name.as_str().into(),
// TODO check to see if name is consistent with filename.
// If it isn't, report a problem!
name.as_str().into()
}
}; };
let (name, home) = { let (name, home) = {
@ -5172,7 +5118,7 @@ fn parse<'a>(
} }
HeaderType::App { .. } HeaderType::App { .. }
| HeaderType::Package { .. } | HeaderType::Package { .. }
| HeaderType::Interface { .. } | HeaderType::Module { .. }
| HeaderType::Builtin { .. } | HeaderType::Builtin { .. }
| HeaderType::Hosted { .. } => {} | HeaderType::Hosted { .. } => {}
}; };
@ -6408,12 +6354,12 @@ fn report_cannot_run(
severity: Severity::RuntimeError, severity: Severity::RuntimeError,
} }
} }
RootIsInterface => { RootIsModule => {
let doc = alloc.stack([ let doc = alloc.stack([
alloc.reflow( alloc.reflow(
r"The input file is an `interface` module, but only `app` modules can be run.", r"The input file is a module, but only `app` modules can be run.",
), ),
alloc.reflow(r"Tip: You can use `roc check` or `roc test` to verify an interface module like this one."), alloc.reflow(r"Tip: You can use `roc check` or `roc test` to verify a module like this one."),
]); ]);
Report { Report {

View File

@ -339,7 +339,7 @@ fn import_transitive_alias() {
"RBTree.roc", "RBTree.roc",
indoc!( indoc!(
r" r"
interface RBTree exposes [RedBlackTree, empty] imports [] module [RedBlackTree, empty]
# The color of a node. Leaves are considered Black. # The color of a node. Leaves are considered Black.
NodeColor : [Red, Black] NodeColor : [Red, Black]
@ -357,7 +357,7 @@ fn import_transitive_alias() {
"Other.roc", "Other.roc",
indoc!( indoc!(
r" r"
interface Other exposes [empty] imports [] module [empty]
import RBTree import RBTree
@ -372,9 +372,9 @@ fn import_transitive_alias() {
} }
#[test] #[test]
fn interface_with_deps() { fn module_with_deps() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let src_dir = fixtures_dir().join("interface_with_deps"); let src_dir = fixtures_dir().join("module_with_deps");
let filename = src_dir.join("Primary.roc"); let filename = src_dir.join("Primary.roc");
let arena = Bump::new(); let arena = Bump::new();
let loaded = load_and_typecheck( let loaded = load_and_typecheck(
@ -488,7 +488,7 @@ fn load_docs() {
#[test] #[test]
fn import_alias() { fn import_alias() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "ImportAlias", subs_by_module); let loaded_module = load_fixture("module_with_deps", "ImportAlias", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -501,7 +501,7 @@ fn import_alias() {
#[test] #[test]
fn import_inside_def() { fn import_inside_def() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "ImportInsideDef", subs_by_module); let loaded_module = load_fixture("module_with_deps", "ImportInsideDef", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -517,7 +517,7 @@ fn import_inside_def() {
fn exposed_used_outside_scope() { fn exposed_used_outside_scope() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
load_fixture( load_fixture(
"interface_with_deps", "module_with_deps",
"ExposedUsedOutsideScope", "ExposedUsedOutsideScope",
subs_by_module, subs_by_module,
); );
@ -527,17 +527,13 @@ fn exposed_used_outside_scope() {
#[should_panic(expected = "MODULE NOT IMPORTED")] #[should_panic(expected = "MODULE NOT IMPORTED")]
fn import_used_outside_scope() { fn import_used_outside_scope() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
load_fixture( load_fixture("module_with_deps", "ImportUsedOutsideScope", subs_by_module);
"interface_with_deps",
"ImportUsedOutsideScope",
subs_by_module,
);
} }
#[test] #[test]
fn test_load_and_typecheck() { fn test_load_and_typecheck() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "WithBuiltins", subs_by_module); let loaded_module = load_fixture("module_with_deps", "WithBuiltins", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -558,7 +554,7 @@ fn test_load_and_typecheck() {
#[test] #[test]
fn iface_quicksort() { fn iface_quicksort() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "Quicksort", subs_by_module); let loaded_module = load_fixture("module_with_deps", "Quicksort", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -574,7 +570,7 @@ fn iface_quicksort() {
#[test] #[test]
fn load_astar() { fn load_astar() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "AStar", subs_by_module); let loaded_module = load_fixture("module_with_deps", "AStar", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -606,7 +602,7 @@ fn load_principal_types() {
#[test] #[test]
fn iface_dep_types() { fn iface_dep_types() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "Primary", subs_by_module); let loaded_module = load_fixture("module_with_deps", "Primary", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -650,7 +646,7 @@ fn app_dep_types() {
#[test] #[test]
fn imported_dep_regression() { fn imported_dep_regression() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "OneDep", subs_by_module); let loaded_module = load_fixture("module_with_deps", "OneDep", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -663,7 +659,7 @@ fn imported_dep_regression() {
#[test] #[test]
fn ingested_file() { fn ingested_file() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "IngestedFile", subs_by_module); let loaded_module = load_fixture("module_with_deps", "IngestedFile", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -678,7 +674,7 @@ fn ingested_file() {
#[test] #[test]
fn ingested_file_bytes() { fn ingested_file_bytes() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "IngestedFileBytes", subs_by_module); let loaded_module = load_fixture("module_with_deps", "IngestedFileBytes", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -695,7 +691,7 @@ fn parse_problem() {
"Main.roc", "Main.roc",
indoc!( indoc!(
r" r"
interface Main exposes [main] imports [] module [main]
main = [ main = [
" "
@ -732,7 +728,7 @@ fn parse_problem() {
#[should_panic(expected = "FILE NOT FOUND")] #[should_panic(expected = "FILE NOT FOUND")]
fn file_not_found() { fn file_not_found() {
let subs_by_module = Default::default(); let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module); let loaded_module = load_fixture("module_with_deps", "invalid$name", subs_by_module);
expect_types( expect_types(
loaded_module, loaded_module,
@ -887,7 +883,7 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
"Age.roc", "Age.roc",
indoc!( indoc!(
r" r"
interface Age exposes [Age] imports [] module [Age]
Age := U32 Age := U32
" "
@ -897,7 +893,7 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
"Main.roc", "Main.roc",
indoc!( indoc!(
r" r"
interface Main exposes [twenty, readAge] imports [] module [twenty, readAge]
import Age exposing [Age] import Age exposing [Age]
@ -965,7 +961,8 @@ fn unused_imports() {
"Dep1.roc", "Dep1.roc",
indoc!( indoc!(
r#" r#"
interface Dep1 exposes [one] imports [] module [one]
one = 1 one = 1
"# "#
), ),
@ -974,7 +971,8 @@ fn unused_imports() {
"Dep2.roc", "Dep2.roc",
indoc!( indoc!(
r#" r#"
interface Dep2 exposes [two] imports [] module [two]
two = 2 two = 2
"# "#
), ),
@ -983,7 +981,7 @@ fn unused_imports() {
"Dep3.roc", "Dep3.roc",
indoc!( indoc!(
r#" r#"
interface Dep3 exposes [Three, three] imports [] module [Three, three]
Three : [Three] Three : [Three]
@ -995,7 +993,7 @@ fn unused_imports() {
"Main.roc", "Main.roc",
indoc!( indoc!(
r#" r#"
interface Main exposes [usedModule, unusedModule, unusedExposed, unusedWithAlias, usingThreeValue] imports [] module [usedModule, unusedModule, unusedExposed, usingThreeValue, unusedWithAlias]
import Dep1 import Dep1
import Dep3 exposing [Three] import Dep3 exposing [Three]
@ -1663,48 +1661,13 @@ fn import_builtin_in_platform_and_check_app() {
assert!(result.is_ok(), "should check"); assert!(result.is_ok(), "should check");
} }
#[test]
fn module_doesnt_match_file_path() {
let modules = vec![(
"Age.roc",
indoc!(
r"
interface NotAge exposes [Age] imports []
Age := U32
"
),
)];
let err = multiple_modules("module_doesnt_match_file_path", modules).unwrap_err();
assert_eq!(
err,
indoc!(
r"
WEIRD MODULE NAME in tmp/module_doesnt_match_file_path/Age.roc
This module name does not correspond with the file path it is defined
in:
1 interface NotAge exposes [Age] imports []
^^^^^^
Module names must correspond with the file paths they are defined in.
For example, I expect to see BigNum defined in BigNum.roc, or Math.Sin
defined in Math/Sin.roc."
),
"\n{}",
err
);
}
#[test] #[test]
fn module_cyclic_import_itself() { fn module_cyclic_import_itself() {
let modules = vec![( let modules = vec![(
"Age.roc", "Age.roc",
indoc!( indoc!(
r" r"
interface Age exposes [] imports [] module []
import Age import Age
" "
@ -1742,7 +1705,8 @@ fn module_cyclic_import_transitive() {
"Age.roc", "Age.roc",
indoc!( indoc!(
r" r"
interface Age exposes [] imports [] module []
import Person import Person
" "
), ),
@ -1751,7 +1715,8 @@ fn module_cyclic_import_transitive() {
"Person.roc", "Person.roc",
indoc!( indoc!(
r" r"
interface Person exposes [] imports [] module []
import Age import Age
" "
), ),

View File

@ -1,7 +1,7 @@
use std::fmt::Debug; use std::fmt::Debug;
use crate::header::{ use crate::header::{
self, AppHeader, HostedHeader, InterfaceHeader, ModuleName, PackageHeader, PlatformHeader, self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader,
}; };
use crate::ident::Accessor; use crate::ident::Accessor;
use crate::parser::ESingleQuote; use crate::parser::ESingleQuote;
@ -99,6 +99,21 @@ pub struct Module<'a> {
} }
impl<'a> Module<'a> { impl<'a> Module<'a> {
pub fn upgrade_header_imports(self, arena: &'a Bump) -> (Self, Defs<'a>) {
let (header, defs) = match self.header {
Header::Module(header) => (
Header::Module(ModuleHeader {
interface_imports: None,
..header
}),
Self::header_imports_to_defs(arena, header.interface_imports),
),
header => (header, Defs::default()),
};
(Module { header, ..self }, defs)
}
pub fn header_imports_to_defs( pub fn header_imports_to_defs(
arena: &'a Bump, arena: &'a Bump,
imports: Option< imports: Option<
@ -209,7 +224,7 @@ impl<'a> Module<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Header<'a> { pub enum Header<'a> {
Interface(InterfaceHeader<'a>), Module(ModuleHeader<'a>),
App(AppHeader<'a>), App(AppHeader<'a>),
Package(PackageHeader<'a>), Package(PackageHeader<'a>),
Platform(PlatformHeader<'a>), Platform(PlatformHeader<'a>),
@ -2272,7 +2287,7 @@ impl<'a> Malformed for Module<'a> {
impl<'a> Malformed for Header<'a> { impl<'a> Malformed for Header<'a> {
fn is_malformed(&self) -> bool { fn is_malformed(&self) -> bool {
match self { match self {
Header::Interface(header) => header.is_malformed(), Header::Module(header) => header.is_malformed(),
Header::App(header) => header.is_malformed(), Header::App(header) => header.is_malformed(),
Header::Package(header) => header.is_malformed(), Header::Package(header) => header.is_malformed(),
Header::Platform(header) => header.is_malformed(), Header::Platform(header) => header.is_malformed(),

View File

@ -19,7 +19,7 @@ impl<'a> HeaderType<'a> {
} }
| HeaderType::Hosted { exposes, .. } | HeaderType::Hosted { exposes, .. }
| HeaderType::Builtin { exposes, .. } | HeaderType::Builtin { exposes, .. }
| HeaderType::Interface { exposes, .. } => exposes, | HeaderType::Module { exposes, .. } => exposes,
HeaderType::Platform { .. } | HeaderType::Package { .. } => &[], HeaderType::Platform { .. } | HeaderType::Package { .. } => &[],
} }
} }
@ -30,7 +30,7 @@ impl<'a> HeaderType<'a> {
HeaderType::Builtin { .. } => "builtin", HeaderType::Builtin { .. } => "builtin",
HeaderType::Package { .. } => "package", HeaderType::Package { .. } => "package",
HeaderType::Platform { .. } => "platform", HeaderType::Platform { .. } => "platform",
HeaderType::Interface { .. } => "interface", HeaderType::Module { .. } => "module",
} }
} }
} }
@ -73,7 +73,7 @@ pub enum HeaderType<'a> {
/// usually `pf` /// usually `pf`
config_shorthand: &'a str, config_shorthand: &'a str,
}, },
Interface { Module {
name: ModuleName<'a>, name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>], exposes: &'a [Loc<ExposedName<'a>>],
}, },
@ -82,9 +82,9 @@ pub enum HeaderType<'a> {
impl<'a> HeaderType<'a> { impl<'a> HeaderType<'a> {
pub fn get_name(self) -> Option<&'a str> { pub fn get_name(self) -> Option<&'a str> {
match self { match self {
Self::Interface { name, .. } Self::Module { name, .. } | Self::Builtin { name, .. } | Self::Hosted { name, .. } => {
| Self::Builtin { name, .. } Some(name.into())
| Self::Hosted { name, .. } => Some(name.into()), }
Self::App { Self::App {
output_name: StrLiteral::PlainLine(name), output_name: StrLiteral::PlainLine(name),
.. ..
@ -106,13 +106,11 @@ impl<'a> HeaderType<'a> {
pub fn to_maybe_builtin(self, module_id: ModuleId) -> Self { pub fn to_maybe_builtin(self, module_id: ModuleId) -> Self {
match self { match self {
HeaderType::Interface { name, exposes } if module_id.is_builtin() => { HeaderType::Module { name, exposes } if module_id.is_builtin() => HeaderType::Builtin {
HeaderType::Builtin {
name, name,
exposes, exposes,
generates_with: &[], generates_with: &[],
} },
}
_ => self, _ => self,
} }
} }
@ -246,12 +244,12 @@ pub struct KeywordItem<'a, K, V> {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> { pub struct ModuleHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>], pub before_exposes: &'a [CommentOrNewline<'a>],
pub name: Loc<ModuleName<'a>>, pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>, // Keeping this so we can format old interface header into module headers
pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>, pub interface_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
} }
pub type ImportsKeywordItem<'a> = KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>; pub type ImportsKeywordItem<'a> = KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>;
@ -430,7 +428,7 @@ where
} }
} }
impl<'a> Malformed for InterfaceHeader<'a> { impl<'a> Malformed for ModuleHeader<'a> {
fn is_malformed(&self) -> bool { fn is_malformed(&self) -> bool {
false false
} }

View File

@ -1,10 +1,11 @@
use crate::ast::{Collection, Defs, Header, Module, Spaced, Spaces}; use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::header::{ use crate::header::{
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
HostedHeader, ImportsEntry, ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, HostedHeader, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword, KeywordItem,
PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader, PlatformRequires, ModuleHeader, ModuleName, PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent,
WithKeyword,
}; };
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent}; use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
@ -60,12 +61,20 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
record!(Module { record!(Module {
comments: space0_e(EHeader::IndentStart), comments: space0_e(EHeader::IndentStart),
header: one_of![ header: one_of![
map!(
skip_first!(
keyword("module", EHeader::Start),
increment_min_indent(module_header())
),
Header::Module
),
// Old headers
map!( map!(
skip_first!( skip_first!(
keyword("interface", EHeader::Start), keyword("interface", EHeader::Start),
increment_min_indent(interface_header()) increment_min_indent(interface_header())
), ),
Header::Interface Header::Module
), ),
map!( map!(
skip_first!( skip_first!(
@ -100,22 +109,65 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
} }
#[inline(always)] #[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> { fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
record!(InterfaceHeader { record!(ModuleHeader {
before_name: space0_e(EHeader::IndentStart), before_exposes: space0_e(EHeader::IndentStart),
name: loc!(module_name_help(EHeader::ModuleName)), exposes: specialize_err(EHeader::Exposes, exposes_list()),
exposes: specialize_err(EHeader::Exposes, exposes_values()), interface_imports: succeed!(None)
imports: specialize_err(EHeader::Imports, imports()), })
.trace("module_header")
}
/// 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>> {
use bumpalo::collections::Vec;
let before_exposes = map_with_arena!(
and!(
skip_second!(
space0_e(EHeader::IndentStart),
loc!(module_name_help(EHeader::ModuleName))
),
specialize_err(EHeader::Exposes, exposes_kw())
),
|arena: &'a bumpalo::Bump,
(before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| {
let mut combined: Vec<CommentOrNewline> =
Vec::with_capacity_in(before_name.len() + kw.before.len() + kw.after.len(), arena);
combined.extend(before_name);
combined.extend(kw.before);
combined.extend(kw.after);
arena.alloc(combined)
}
);
record!(ModuleHeader {
before_exposes: before_exposes,
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
interface_imports: map!(
specialize_err(EHeader::Imports, imports()),
imports_none_if_empty
)
.trace("imports"),
}) })
.trace("interface_header") .trace("interface_header")
} }
fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option<ImportsKeywordItem<'_>> {
if value.item.is_empty() {
None
} else {
Some(value)
}
}
#[inline(always)] #[inline(always)]
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> { fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
record!(HostedHeader { record!(HostedHeader {
before_name: space0_e(EHeader::IndentStart), before_name: space0_e(EHeader::IndentStart),
name: loc!(module_name_help(EHeader::ModuleName)), name: loc!(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()), exposes: specialize_err(EHeader::Exposes, exposes_values_kw()),
imports: specialize_err(EHeader::Imports, imports()), imports: specialize_err(EHeader::Imports, imports()),
generates: specialize_err(EHeader::Generates, generates()), generates: specialize_err(EHeader::Generates, generates()),
generates_with: specialize_err(EHeader::GeneratesWith, generates_with()), generates_with: specialize_err(EHeader::GeneratesWith, generates_with()),
@ -388,26 +440,37 @@ fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>
} }
#[inline(always)] #[inline(always)]
fn exposes_values<'a>() -> impl Parser< fn exposes_values_kw<'a>() -> impl Parser<
'a, 'a,
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>, KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
EExposes, EExposes,
> { > {
record!(KeywordItem { record!(KeywordItem {
keyword: spaces_around_keyword( keyword: exposes_kw(),
item: exposes_list()
})
}
#[inline(always)]
fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> {
spaces_around_keyword(
ExposesKeyword, ExposesKeyword,
EExposes::Exposes, EExposes::Exposes,
EExposes::IndentExposes, EExposes::IndentExposes,
EExposes::IndentListStart EExposes::IndentListStart,
), )
item: collection_trailing_sep_e!( }
#[inline(always)]
fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, EExposes>
{
collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart), byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier), exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd), byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd), byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore Spaced::SpaceBefore
) )
})
} }
pub fn spaces_around_keyword<'a, K: Keyword, E>( pub fn spaces_around_keyword<'a, K: Keyword, E>(

View File

@ -1 +0,0 @@
interface Foo exposes [] imports []

View File

@ -1,27 +0,0 @@
Module {
comments: [],
header: Interface(
InterfaceHeader {
before_name: [],
name: @10-13 ModuleName(
"Foo",
),
exposes: KeywordItem {
keyword: Spaces {
before: [],
item: ExposesKeyword,
after: [],
},
item: [],
},
imports: KeywordItem {
keyword: Spaces {
before: [],
item: ImportsKeyword,
after: [],
},
item: [],
},
},
),
}

View File

@ -1 +0,0 @@
interface Foo exposes [] imports []

View File

@ -0,0 +1,10 @@
Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
exposes: [],
interface_imports: None,
},
),
}

View File

@ -1 +0,0 @@
interface T exposes [] imports []

View File

@ -1,27 +0,0 @@
Module {
comments: [],
header: Interface(
InterfaceHeader {
before_name: [],
name: @10-11 ModuleName(
"T",
),
exposes: KeywordItem {
keyword: Spaces {
before: [],
item: ExposesKeyword,
after: [],
},
item: [],
},
imports: KeywordItem {
keyword: Spaces {
before: [],
item: ImportsKeyword,
after: [],
},
item: [],
},
},
),
}

View File

@ -1 +0,0 @@
interface T exposes [] imports []

View File

@ -0,0 +1,25 @@
Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
exposes: [
@8-9 ExposedName(
"a",
),
@11-12 ExposedName(
"b",
),
@18-19 SpaceBefore(
ExposedName(
"c",
),
[
Newline,
],
),
],
interface_imports: None,
},
),
}

View File

@ -0,0 +1,2 @@
module [a, b,
c]

View File

@ -0,0 +1,10 @@
Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
exposes: [],
interface_imports: None,
},
),
}

View File

@ -1 +0,0 @@
interface Foo.Bar.Baz exposes [] imports []

View File

@ -1,27 +0,0 @@
Module {
comments: [],
header: Interface(
InterfaceHeader {
before_name: [],
name: @10-21 ModuleName(
"Foo.Bar.Baz",
),
exposes: KeywordItem {
keyword: Spaces {
before: [],
item: ExposesKeyword,
after: [],
},
item: [],
},
imports: KeywordItem {
keyword: Spaces {
before: [],
item: ImportsKeyword,
after: [],
},
item: [],
},
},
),
}

View File

@ -1 +0,0 @@
interface Foo.Bar.Baz exposes [] imports []

View File

@ -0,0 +1 @@
module [Foo, foo, bar]

View File

@ -0,0 +1,20 @@
Module {
comments: [],
header: Module(
ModuleHeader {
before_exposes: [],
exposes: [
@23-26 ExposedName(
"Foo",
),
@28-31 ExposedName(
"foo",
),
@33-36 ExposedName(
"bar",
),
],
interface_imports: None,
},
),
}

View File

@ -0,0 +1 @@
interface Foo exposes [Foo, foo, bar] imports []

View File

@ -4768,10 +4768,10 @@ mod test_fmt {
// MODULES // MODULES
#[test] #[test]
fn single_line_interface() { fn single_line_module() {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
interface Foo exposes [] imports []" module []"
)); ));
} }
@ -4781,12 +4781,14 @@ mod test_fmt {
module_formats_to( module_formats_to(
indoc!( indoc!(
r" r"
interface Foo exposes [] imports [] module []
a = 42 # Yay greetings" a = 42 # Yay greetings"
), ),
indoc!( indoc!(
r" r"
interface Foo exposes [] imports [] module []
a = 42 # Yay greetings a = 42 # Yay greetings
" "
), ),
@ -4794,49 +4796,25 @@ mod test_fmt {
} }
#[test] #[test]
fn multiline_interface() { fn module_exposing() {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
interface Foo module [Bar, Baz, a, b]"
exposes []
imports []"
)); ));
} }
#[test] #[test]
fn interface_exposing() { fn module_exposing_multiline() {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
interface Foo module [
exposes [Bar, Baz, a, b]
imports []"
));
}
#[test]
fn interface_importing() {
module_formats_same(indoc!(
r"
interface Foo
exposes [Bar, Baz, a, b]
imports [Blah, Thing.{ foo, bar }, Stuff]"
));
}
#[test]
fn multi_line_interface() {
module_formats_same(indoc!(
r"
interface Foo
exposes [
Stuff, Stuff,
Things, Things,
somethingElse, somethingElse,
] ]
imports [
Blah, import Blah
Baz.{ stuff, things }, import Baz exposing [stuff, things]"
]"
)); ));
} }
@ -4866,9 +4844,7 @@ mod test_fmt {
&format!( &format!(
indoc!( indoc!(
r#" r#"
interface Foo module []
exposes []
imports []
# comment 1{space} # comment 1{space}
def = "" # comment 2{space} def = "" # comment 2{space}
@ -4879,9 +4855,7 @@ mod test_fmt {
), ),
indoc!( indoc!(
r#" r#"
interface Foo module []
exposes []
imports []
# comment 1 # comment 1
def = "" # comment 2 def = "" # comment 2
@ -5700,7 +5674,7 @@ mod test_fmt {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
interface Foo exposes [] imports [] module []
expect x == y expect x == y
@ -5727,7 +5701,7 @@ mod test_fmt {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
interface Foo exposes [] imports [] module []
expect expect
foo bar foo bar
@ -5831,7 +5805,7 @@ mod test_fmt {
fn ability_member_doc_comments() { fn ability_member_doc_comments() {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
interface Foo exposes [] imports [] module []
A implements A implements
## This is member ab ## This is member ab
@ -5850,9 +5824,7 @@ mod test_fmt {
module_formats_same(indoc!( module_formats_same(indoc!(
r" r"
# hello world # hello world
interface Foo module []
exposes []
imports []
" "
)); ));
@ -5876,6 +5848,17 @@ mod test_fmt {
)); ));
} }
#[test]
fn comments_before_exposes_preserved() {
module_formats_same(indoc!(
r"
module
# comment
[a, b]
"
));
}
#[test] #[test]
fn clauses_with_multiple_abilities() { fn clauses_with_multiple_abilities() {
expr_formats_same(indoc!( expr_formats_same(indoc!(

View File

@ -301,8 +301,8 @@ mod test_snapshots {
pass/def_without_newline.expr, pass/def_without_newline.expr,
pass/destructure_tag_assignment.expr, pass/destructure_tag_assignment.expr,
pass/empty_app_header.header, pass/empty_app_header.header,
pass/empty_module_header.header,
pass/empty_hosted_header.header, pass/empty_hosted_header.header,
pass/empty_interface_header.header,
pass/empty_list.expr, pass/empty_list.expr,
pass/empty_package_header.header, pass/empty_package_header.header,
pass/empty_platform_header.header, pass/empty_platform_header.header,
@ -333,7 +333,7 @@ mod test_snapshots {
pass/inline_import.expr, pass/inline_import.expr,
pass/inline_ingested_file.expr, pass/inline_ingested_file.expr,
pass/int_with_underscore.expr, pass/int_with_underscore.expr,
pass/interface_with_newline.header, pass/module_with_newline.header,
pass/lambda_in_chain.expr, pass/lambda_in_chain.expr,
pass/lambda_indent.expr, pass/lambda_indent.expr,
pass/list_closing_indent_not_enough.expr, pass/list_closing_indent_not_enough.expr,
@ -348,6 +348,7 @@ mod test_snapshots {
pass/minus_twelve_minus_five.expr, pass/minus_twelve_minus_five.expr,
pass/mixed_docs.expr, pass/mixed_docs.expr,
pass/module_def_newline.moduledefs, pass/module_def_newline.moduledefs,
pass/module_multiline_exposes.header,
pass/multi_backpassing.expr, pass/multi_backpassing.expr,
pass/multi_backpassing_in_def.moduledefs, pass/multi_backpassing_in_def.moduledefs,
pass/multi_backpassing_with_apply.expr, pass/multi_backpassing_with_apply.expr,
@ -369,7 +370,6 @@ mod test_snapshots {
pass/nested_def_annotation.moduledefs, pass/nested_def_annotation.moduledefs,
pass/nested_def_without_newline.expr, pass/nested_def_without_newline.expr,
pass/nested_if.expr, pass/nested_if.expr,
pass/nested_module.header,
pass/newline_after_equals.expr, // Regression test for https://github.com/roc-lang/roc/issues/51 pass/newline_after_equals.expr, // Regression test for https://github.com/roc-lang/roc/issues/51
pass/newline_after_mul.expr, pass/newline_after_mul.expr,
pass/newline_after_paren.expr, pass/newline_after_paren.expr,
@ -409,6 +409,7 @@ mod test_snapshots {
pass/outdented_colon_in_record.expr, pass/outdented_colon_in_record.expr,
pass/outdented_list.expr, pass/outdented_list.expr,
pass/outdented_record.expr, pass/outdented_record.expr,
pass/old_interface_header.header,
pass/packed_singleton_list.expr, pass/packed_singleton_list.expr,
pass/parens_in_type_def_apply.expr, pass/parens_in_type_def_apply.expr,
pass/parens_in_value_def_annotation.expr, pass/parens_in_value_def_annotation.expr,

View File

@ -11,9 +11,8 @@ use roc_parse::{
WhenBranch, WhenBranch,
}, },
header::{ header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, AppHeader, ExposedName, HostedHeader, ImportsEntry, ModuleHeader, ModuleName, PackageEntry,
PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To, TypedIdent,
TypedIdent,
}, },
ident::{Accessor, UppercaseIdent}, ident::{Accessor, UppercaseIdent},
}; };
@ -202,7 +201,7 @@ impl IterTokens for Module<'_> {
impl IterTokens for Header<'_> { impl IterTokens for Header<'_> {
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> { fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
match self { match self {
Header::Interface(ih) => ih.iter_tokens(arena), Header::Module(mh) => mh.iter_tokens(arena),
Header::App(app) => app.iter_tokens(arena), Header::App(app) => app.iter_tokens(arena),
Header::Package(pkg) => pkg.iter_tokens(arena), Header::Package(pkg) => pkg.iter_tokens(arena),
Header::Platform(pf) => pf.iter_tokens(arena), Header::Platform(pf) => pf.iter_tokens(arena),
@ -211,19 +210,15 @@ impl IterTokens for Header<'_> {
} }
} }
impl IterTokens for InterfaceHeader<'_> { impl IterTokens for ModuleHeader<'_> {
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> { fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
let Self { let Self {
before_name: _, before_exposes: _,
name,
exposes, exposes,
imports, interface_imports: _,
} = self; } = self;
(name.iter_tokens(arena).into_iter()) exposes.iter_tokens(arena)
.chain(exposes.item.iter_tokens(arena))
.chain(imports.item.iter_tokens(arena))
.collect_in(arena)
} }
} }

View File

@ -128,7 +128,7 @@ fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
// TODO use this when finding .roc files by discovering them from the root module. // TODO use this when finding .roc files by discovering them from the root module.
// let other_modules: &[Module<'_>] = // let other_modules: &[Module<'_>] =
match read_header(&arena, &mut buf, path)?.header { match read_header(&arena, &mut buf, path)?.header {
Header::Interface(_) => { Header::Module(_) => {
todo!(); todo!();
// TODO report error // TODO report error
} }

View File

@ -3621,7 +3621,7 @@ fn to_exposes_report<'a>(
"I was expecting a type name, value name or function name next, like", "I was expecting a type name, value name or function name next, like",
)]), )]),
alloc alloc
.parser_suggestion("exposes [Animal, default, tame]") .parser_suggestion("[Animal, default, tame]")
.indent(4), .indent(4),
]); ]);
@ -3646,7 +3646,7 @@ fn to_exposes_report<'a>(
alloc.reflow(" keyword next, like"), alloc.reflow(" keyword next, like"),
]), ]),
alloc alloc
.parser_suggestion("exposes [Animal, default, tame]") .parser_suggestion("[Animal, default, tame]")
.indent(4), .indent(4),
]); ]);

View File

@ -1,5 +1,4 @@
interface Community module [
exposes [
Community, Community,
empty, empty,
addPerson, addPerson,
@ -7,7 +6,6 @@ interface Community
Person, Person,
walkFriendNames, walkFriendNames,
] ]
imports []
## Datatype representing a community for demonstration purposes in inspect-gui.roc and inspect-logging.roc ## Datatype representing a community for demonstration purposes in inspect-gui.roc and inspect-logging.roc

View File

@ -1,9 +1,7 @@
interface GuiFormatter module [
exposes [
GuiFormatter, GuiFormatter,
toGui, toGui,
] ]
imports []
## Creates GUI representations of Roc values, for use in inspect-gui.roc ## Creates GUI representations of Roc values, for use in inspect-gui.roc

View File

@ -1,6 +1,8 @@
interface Context module [Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope]
exposes [Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope]
imports [pf.File, pf.Task.{ Task }, Variable.{ Variable }] import pf.File
import pf.Task exposing [Task]
import Variable exposing [Variable]
Option a : [Some a, None] Option a : [Some a, None]

View File

@ -1,6 +1,4 @@
interface Variable module [Variable, fromUtf8, toIndex, totalCount, toStr]
exposes [Variable, fromUtf8, toIndex, totalCount, toStr]
imports []
# Variables in False can only be single letters. Thus, the valid variables are "a" to "z". # Variables in False can only be single letters. Thus, the valid variables are "a" to "z".
# This opaque type deals with ensure we always have valid variables. # This opaque type deals with ensure we always have valid variables.

View File

@ -1,6 +1,7 @@
interface File module [line, Handle, withOpen, chunk]
exposes [line, Handle, withOpen, chunk]
imports [pf.Effect, Task.{ Task }] import pf.Effect
import Task exposing [Task]
Handle := U64 Handle := U64

View File

@ -1,6 +1,7 @@
interface Stdin module [char]
exposes [char]
imports [pf.Effect, Task] import pf.Effect
import Task
# line : Task.Task Str * # line : Task.Task Str *
# line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice # line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice

View File

@ -1,6 +1,7 @@
interface Stdout module [line, raw]
exposes [line, raw]
imports [pf.Effect, Task.{ Task }] import pf.Effect
import Task exposing [Task]
line : Str -> Task {} * line : Str -> Task {} *
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})

View File

@ -1,6 +1,6 @@
interface Task module [Task, succeed, fail, await, map, onFail, attempt, fromResult, loop]
exposes [Task, succeed, fail, await, map, onFail, attempt, fromResult, loop]
imports [pf.Effect] import pf.Effect
Task ok err : Effect.Effect (Result ok err) Task ok err : Effect.Effect (Result ok err)

View File

@ -1,6 +1,4 @@
interface Program module [Program]
exposes [Program]
imports []
Program model : { Program model : {
init : {} -> model, init : {} -> model,

View File

@ -1,6 +1,4 @@
interface Action module [Action, none, update, map]
exposes [Action, none, update, map]
imports []
Action state : [None, Update state] Action state : [None, Update state]

View File

@ -1,6 +1,6 @@
interface Elem module [Elem, PressEvent, row, col, text, button, none, translate, list]
exposes [Elem, PressEvent, row, col, text, button, none, translate, list]
imports [Action.{ Action }] import Action exposing [Action]
Elem state : [ Elem state : [
# PERFORMANCE NOTE: # PERFORMANCE NOTE:

View File

@ -1,6 +1,4 @@
interface Game module [Bounds, Elem, Event]
exposes [Bounds, Elem, Event]
imports []
Rgba : { r : F32, g : F32, b : F32, a : F32 } Rgba : { r : F32, g : F32, b : F32, a : F32 }

View File

@ -1,6 +1,4 @@
interface Action module [Action, none, update, map]
exposes [Action, none, update, map]
imports []
Action state : [None, Update state] Action state : [None, Update state]

View File

@ -1,6 +1,6 @@
interface Elem module [Elem, PressEvent, row, col, text, button, none, translate, list]
exposes [Elem, PressEvent, row, col, text, button, none, translate, list]
imports [Action.{ Action }] import Action exposing [Action]
Elem state : [ Elem state : [
# PERFORMANCE NOTE: # PERFORMANCE NOTE:

View File

@ -1,5 +1,4 @@
interface Html module [
exposes [
Node, Node,
Attribute, Attribute,
render, render,
@ -122,7 +121,8 @@ interface Html
slot, slot,
template, template,
] ]
imports [Html.Attributes]
import Html.Attributes
Node : [ Node : [
Text Str, Text Str,

View File

@ -1,5 +1,4 @@
interface Html.Attributes module [
exposes [
Attribute, Attribute,
attribute, attribute,
accept, accept,
@ -138,7 +137,6 @@ interface Html.Attributes
width, width,
wrap, wrap,
] ]
imports []
Attribute : [Attribute Str Str] Attribute : [Attribute Str Str]

View File

@ -1,8 +1,6 @@
interface ExampleApp module [exampleApp, State]
exposes [exampleApp, State]
imports [ import pf.Html exposing [App, Html, html, head, body, div, text, h1]
pf.Html.{ App, Html, html, head, body, div, text, h1 },
]
State : { State : {
answer : U32, answer : U32,

View File

@ -1,6 +1,4 @@
interface Action module [Action, none, update, map]
exposes [Action, none, update, map]
imports []
Action state : [None, Update state] Action state : [None, Update state]

View File

@ -1,5 +1,4 @@
interface Html module [
exposes [
App, App,
Html, Html,
Attribute, Attribute,
@ -123,7 +122,9 @@ interface Html
slot, slot,
template, template,
] ]
imports [Html.Internal.Shared, Html.Internal.Server]
import Html.Internal.Shared
import Html.Internal.Server
App state initData : Html.Internal.Shared.App state initData App state initData : Html.Internal.Shared.App state initData
Html state : Html.Internal.Shared.Html state Html state : Html.Internal.Shared.Html state

View File

@ -1,5 +1,4 @@
interface Html.Attributes module [
exposes [
attribute, attribute,
accept, accept,
acceptCharset, acceptCharset,
@ -134,7 +133,8 @@ interface Html.Attributes
width, width,
wrap, wrap,
] ]
imports [Html.Internal.Shared.{ Attribute }]
import Html.Internal.Shared exposing [Attribute]
attribute : Str -> (Str -> Attribute state) attribute : Str -> (Str -> Attribute state)
attribute = \attrType -> attribute = \attrType ->

View File

@ -1,5 +1,4 @@
interface Html.Event module [
exposes [
Handler, Handler,
CyclicStructureAccessor, CyclicStructureAccessor,
on, on,
@ -18,10 +17,9 @@ interface Html.Event
onInput, onInput,
onSubmit, onSubmit,
] ]
imports [
Action.{ Action }, import Action exposing [Action]
Html.Internal.Shared.{ Attribute }, import Html.Internal.Shared exposing [Attribute]
]
Handler state : Html.Internal.Shared.Handler state Handler state : Html.Internal.Shared.Handler state
CyclicStructureAccessor : Html.Internal.Shared.CyclicStructureAccessor CyclicStructureAccessor : Html.Internal.Shared.CyclicStructureAccessor

View File

@ -1,29 +1,27 @@
interface Html.Internal.Client module [
exposes [
PlatformState, PlatformState,
initClientApp, initClientApp,
dispatchEvent, dispatchEvent,
] ]
imports [
Effect.{ import Effect exposing [
Effect, Effect,
NodeId, NodeId,
HandlerId, HandlerId,
TagName, TagName,
AttrType, AttrType,
EventType, EventType,
}, ]
Html.Internal.Shared.{ import Html.Internal.Shared exposing [
App, App,
Html, Html,
Attribute, Attribute,
CyclicStructureAccessor, CyclicStructureAccessor,
Handler, Handler,
translateStatic, translateStatic,
},
TotallyNotJson,
Action,
] ]
import TotallyNotJson
import Action
PlatformState state initData : { PlatformState state initData : {
app : App state initData, app : App state initData,

View File

@ -1,12 +1,10 @@
interface Html.Internal.Server module [
exposes [
appendRenderedStatic, appendRenderedStatic,
initServerApp, initServerApp,
] ]
imports [
Html.Internal.Shared.{ Html, Attribute, App, translateStatic, text, element }, import Html.Internal.Shared exposing [Html, Attribute, App, translateStatic, text, element]
TotallyNotJson, import TotallyNotJson
]
# ------------------------------- # -------------------------------
# STATIC HTML # STATIC HTML

View File

@ -1,5 +1,4 @@
interface Html.Internal.Shared module [
exposes [
App, App,
Html, Html,
Attribute, Attribute,
@ -13,9 +12,8 @@ interface Html.Internal.Shared
translateStatic, translateStatic,
nodeSize, nodeSize,
] ]
imports [
Action.{ Action }, import Action exposing [Action]
]
App state initData : { App state initData : {
init : DecodingResult initData -> state, init : DecodingResult initData -> state,