Load and can new top-level imports

Previously, all imports were available in the header, so we could start
processing dependencies as soon as we parsed it. However, the new imports
are treated as defs, so we have to parse the whole module to find them.

This commit essentially moves the dependency resolution from the `LoadHeader`
phase to the `Parse` phase, and it updates canonicalization to introduce
module symbols into scope when a `ValueDef::ModuleImport` is encountered.

NOTE:
- The `imports` header still parses, but it's no longer wired up. I will remove
it in an upcoming commit.
- Ingested files and imports that appear in nested expressions are not
yet supported by load
This commit is contained in:
Agus Zubiaga 2023-12-29 15:36:31 -03:00
parent 11e0202eb9
commit 710d62f754
No known key found for this signature in database
43 changed files with 683 additions and 771 deletions

View file

@ -251,6 +251,12 @@ fn generate_entry_docs(
ValueDef::ExpectFx { .. } => {
// Don't generate docs for `expect-fx`s
}
ValueDef::ModuleImport { .. } => {
// Don't generate docs for module imports
}
ValueDef::IngestedFileImport { .. } => {
// Don't generate docs for ingested file imports
}
},
Ok(type_index) => match &defs.type_defs[type_index.index()] {
TypeDef::Alias {

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ use roc_module::symbol::{
};
use roc_mono::ir::{GlueLayouts, HostExposedLambdaSets, LambdaSetId, Proc, ProcLayout, ProcsBase};
use roc_mono::layout::{LayoutCache, STLayoutInterner};
use roc_parse::ast::{CommentOrNewline, Defs, TypeAnnotation, ValueDef};
use roc_parse::ast::{CommentOrNewline, Defs, TypeAnnotation};
use roc_parse::header::{HeaderType, PackageName};
use roc_region::all::{Loc, Region};
use roc_solve::module::Solved;
@ -83,19 +83,12 @@ pub(crate) struct ModuleHeader<'a> {
pub(crate) module_id: ModuleId,
pub(crate) module_path: PathBuf,
pub(crate) is_root_module: bool,
pub(crate) exposed_ident_ids: IdentIds,
pub(crate) deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
pub(crate) packages: MutMap<&'a str, PackageName<'a>>,
pub(crate) imported_modules: MutMap<ModuleId, Region>,
pub(crate) package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>,
pub(crate) exposes: Vec<Symbol>,
pub(crate) exposed_imports: MutMap<Ident, (Symbol, Region)>,
pub(crate) parse_state: roc_parse::state::State<'a>,
pub(crate) header_type: HeaderType<'a>,
pub(crate) header_comments: &'a [CommentOrNewline<'a>],
pub(crate) symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
pub(crate) module_timing: ModuleTiming,
pub(crate) defined_values: Vec<ValueDef<'a>>,
pub(crate) opt_shorthand: Option<&'a str>,
}
#[derive(Debug)]
@ -190,13 +183,16 @@ pub struct ParsedModule<'a> {
pub src: &'a str,
pub module_timing: ModuleTiming,
pub deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
pub imported_modules: MutMap<ModuleId, Region>,
pub exposed_ident_ids: IdentIds,
pub exposed_imports: MutMap<Ident, (Symbol, Region)>,
pub parsed_defs: Defs<'a>,
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
pub header_type: HeaderType<'a>,
pub header_comments: &'a [CommentOrNewline<'a>],
pub imported_modules: MutMap<ModuleId, Region>,
pub package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>,
pub packages: MutMap<&'a str, PackageName<'a>>,
pub initial_scope: MutMap<Ident, (Symbol, Region)>,
pub exposes: Vec<Symbol>,
}
#[derive(Debug)]

View file

@ -162,10 +162,6 @@ impl<'a> Dependencies<'a> {
output.insert((dep, Phase::LoadHeader));
}
// to parse and generate constraints, the headers of all dependencies must be loaded!
// otherwise, we don't know whether an imported symbol is actually exposed
self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader);
// to canonicalize a module, all its dependencies must be canonicalized
self.add_dependency(module_id, dep, Phase::CanonicalizeAndConstrain);
@ -427,10 +423,10 @@ impl<'a> Dependencies<'a> {
PrepareStartPhase::Recurse(new)
}
None => match phase {
Phase::LoadHeader => {
// this is fine, mark header loading as pending
Phase::LoadHeader | Phase::Parse => {
// this is fine, mark as pending
self.status
.insert(Job::Step(module_id, Phase::LoadHeader), Status::Pending);
.insert(Job::Step(module_id, phase), Status::Pending);
PrepareStartPhase::Continue
}

View file

@ -1,6 +1,8 @@
interface Dep1
exposes [three, str, Unit, Identity, one, two]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3Blah exposing [foo]
one = 1

View file

@ -1,10 +1,11 @@
interface Dep2
exposes [one, two, blah]
imports [Dep3.Blah.{ foo, bar }]
imports []
import Dep3Blah exposing [foo, bar]
one = 1
blah = foo
two = 2.0

View file

@ -1,6 +0,0 @@
interface Dep3.Other
exposes [foo, bar]
imports []
foo = "foo from Dep3.Other"
bar = "bar from Dep3.Other"

View file

@ -1,10 +1,12 @@
interface Dep3.Blah
interface Dep3Blah
exposes [one, two, foo, bar]
imports [Dep3.Other]
imports []
import Dep3Other
one = 1
two = 2
foo = "foo from Dep3"
bar = Dep3.Other.bar
bar = Dep3Other.bar

View file

@ -0,0 +1,6 @@
interface Dep3Other
exposes [foo, bar]
imports []
foo = "foo from Dep3Other"
bar = "bar from Dep3Other"

View file

@ -1,7 +1,8 @@
interface ImportAlias
exposes [unit]
imports [Dep1]
imports []
import Dep1
unit : Dep1.Unit
unit = Unit

View file

@ -1,5 +1,7 @@
interface OneDep
exposes [str]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3Blah exposing [foo]
str = foo

View file

@ -1,6 +1,11 @@
interface Primary
exposes [blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay]
imports [Dep1, Dep2.{ two }, Dep3.Blah.{ bar }, Res]
imports []
import Dep1
import Dep2 exposing [two]
import Dep3Blah exposing [bar]
import Res
blah2 = Dep2.two
blah3 = bar

View file

@ -1,6 +1,9 @@
interface WithBuiltins
exposes [floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2]
imports [Dep1, Dep2.{ two }]
imports []
import Dep1
import Dep2 exposing [two]
floatTest = Num.maxF64

View file

@ -1,6 +1,8 @@
interface Dep1
exposes [three, str, Unit, Identity, one, two]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3 exposing [foo]
one = 1

View file

@ -1,10 +1,11 @@
interface Dep2
exposes [one, two, blah]
imports [Dep3.Blah.{ foo, bar }]
imports []
import Dep3 exposing [foo, bar]
one = 1
blah = foo
two = 2.0

View file

@ -1,4 +1,4 @@
interface Dep3.Blah
interface Dep3
exposes [one, two, foo, bar]
imports []

View file

@ -1,7 +1,8 @@
interface ImportAlias
exposes [unit]
imports [Dep1]
imports []
import Dep1
unit : Dep1.Unit
unit = Unit

View file

@ -1,5 +1,7 @@
interface IngestedFile
exposes [str]
imports ["IngestedFile.roc" as foo : Str]
imports []
import "IngestedFile.roc" as foo : Str
str = foo

View file

@ -1,5 +1,7 @@
interface IngestedFileBytes
exposes [str]
imports ["IngestedFileBytes.roc" as foo : List U8]
imports []
import "IngestedFileBytes.roc" as foo : List U8
str = Str.fromUtf8 foo |> Result.withDefault ""

View file

@ -1,5 +1,7 @@
interface OneDep
exposes [str]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3 exposing [foo]
str = foo

View file

@ -1,6 +1,11 @@
interface Primary
exposes [blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay]
imports [Dep1, Dep2.{ two }, Dep3.Blah.{ bar }, Res]
imports []
import Dep1
import Dep2 exposing [two]
import Dep3 exposing [bar]
import Res
blah2 = Dep2.two
blah3 = bar

View file

@ -1,6 +1,9 @@
interface WithBuiltins
exposes [floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2]
imports [Dep1, Dep2.{ two }]
imports []
import Dep1
import Dep2 exposing [two]
floatTest = Num.maxF64

View file

@ -1,6 +1,8 @@
interface MissingDep
exposes [unit]
imports [ThisFileIsMissing]
imports []
import ThisFileIsMissing
Unit : [Unit]

View file

@ -1,6 +1,8 @@
interface MissingIngestedFile
exposes [unit]
imports ["ThisFileIsMissing" as data: List U8]
imports []
import "ThisFileIsMissing" as data : List U8
Unit : [Unit]

View file

@ -344,7 +344,9 @@ fn import_transitive_alias() {
"Other",
indoc!(
r"
interface Other exposes [empty] imports [RBTree]
interface Other exposes [empty] imports []
import RBTree
empty : RBTree.RedBlackTree I64 I64
empty = RBTree.empty
@ -831,7 +833,9 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
"Main",
indoc!(
r"
interface Main exposes [twenty, readAge] imports [Age.{ Age }]
interface Main exposes [twenty, readAge] imports []
import Age exposing [Age]
twenty = @Age 20
@ -851,13 +855,13 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
The unwrapped opaque type Age referenced here:
3 twenty = @Age 20
5 twenty = @Age 20
^^^^
is imported from another module:
1 interface Main exposes [twenty, readAge] imports [Age.{ Age }]
^^^
3 import Age exposing [Age]
^^^
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
@ -865,24 +869,15 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
The unwrapped opaque type Age referenced here:
5 readAge = \@Age n -> n
7 readAge = \@Age n -> n
^^^^
is imported from another module:
1 interface Main exposes [twenty, readAge] imports [Age.{ Age }]
^^^
3 import Age exposing [Age]
^^^
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
UNUSED IMPORT in tmp/opaque_wrapped_unwrapped_outside_defining_module/Main
Nothing from Age is used in this module.
1 interface Main exposes [twenty, readAge] imports [Age.{ Age }]
^^^^^^^^^^^
Since Age isn't used, you don't need to import it.
"
),
"\n{}",
@ -962,9 +957,11 @@ fn import_builtin_in_platform_and_check_app() {
requires {} { main : Str }
exposes []
packages {}
imports [Str]
imports []
provides [mainForHost]
import Str
mainForHost : Str
mainForHost = main
"#
@ -1029,7 +1026,9 @@ fn module_cyclic_import_itself() {
"Age",
indoc!(
r"
interface Age exposes [] imports [Age]
interface Age exposes [] imports []
import Age
"
),
)];
@ -1066,7 +1065,8 @@ fn module_cyclic_import_transitive() {
"Age",
indoc!(
r"
interface Age exposes [] imports [Person]
interface Age exposes [] imports []
import Person
"
),
),
@ -1074,7 +1074,8 @@ fn module_cyclic_import_transitive() {
"Person",
indoc!(
r"
interface Person exposes [] imports [Age]
interface Person exposes [] imports []
import Age
"
),
),
@ -1106,45 +1107,3 @@ fn module_cyclic_import_transitive() {
err
);
}
#[test]
fn nested_module_has_incorrect_name() {
let modules = vec![
(
"Dep/Foo.roc",
indoc!(
r"
interface Foo exposes [] imports []
"
),
),
(
"I.roc",
indoc!(
r"
interface I exposes [] imports [Dep.Foo]
"
),
),
];
let err = multiple_modules("nested_module_has_incorrect_name", modules).unwrap_err();
assert_eq!(
err,
indoc!(
r"
INCORRECT MODULE NAME in tmp/nested_module_has_incorrect_name/Dep/Foo.roc
This module has a different name than I expected:
1 interface Foo exposes [] imports []
^^^
Based on the nesting and use of this module, I expect it to have name
Dep.Foo"
),
"\n{}",
err
);
}