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

@ -1042,6 +1042,7 @@ fn canonicalize_value_defs<'a>(
let mut pending_dbgs = Vec::with_capacity(value_defs.len());
let mut pending_expects = Vec::with_capacity(value_defs.len());
let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
let mut pending_ingested_files = Vec::with_capacity(value_defs.len());
for loc_pending_def in value_defs {
match loc_pending_def.value {
@ -1062,6 +1063,10 @@ fn canonicalize_value_defs<'a>(
PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect);
}
PendingValue::ModuleImport => { /* nothing to do */ }
PendingValue::IngestedFileImport(pending_ingested_file) => {
pending_ingested_files.push(pending_ingested_file);
}
}
}
@ -1116,6 +1121,10 @@ fn canonicalize_value_defs<'a>(
def_ordering.insert_symbol_references(def_id as u32, &temp_output.references)
}
for _ in pending_ingested_files {
todo!("[modules-revamp]: canonicalize_ingested_file_import");
}
let mut dbgs = ExpectsOrDbgs::with_capacity(pending_dbgs.len());
let mut expects = ExpectsOrDbgs::with_capacity(pending_expects.len());
let mut expects_fx = ExpectsOrDbgs::with_capacity(pending_expects.len());
@ -2750,6 +2759,8 @@ enum PendingValue<'a> {
Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>),
ModuleImport,
IngestedFileImport(ast::IngestedFileImport<'a>),
SignatureDefMismatch,
}
@ -2874,6 +2885,44 @@ fn to_pending_value_def<'a>(
condition,
preceding_comment: *preceding_comment,
}),
ModuleImport(module_import) => {
match module_import.exposed {
None => {}
Some(exposed) if exposed.item.is_empty() => {}
Some(exposed) => {
for loc_name in exposed.item.items {
let exposed_name = loc_name.value.item();
let name = exposed_name.as_str();
let ident = name.into();
match env.qualified_lookup(
scope,
module_import.name.value.name,
name,
loc_name.region,
) {
Ok(imported_symbol) => {
match scope.import(ident, imported_symbol, loc_name.region) {
Ok(()) => {}
Err((_shadowed_symbol, _region)) => {
internal_error!(
"TODO gracefully handle shadowing in imports."
)
}
}
}
Err(problem) => {
env.problem(Problem::RuntimeError(problem.clone()));
}
}
}
}
}
PendingValue::ModuleImport
}
IngestedFileImport(module_import) => PendingValue::IngestedFileImport(*module_import),
}
}

View file

@ -282,7 +282,7 @@ pub fn canonicalize_module_defs<'a>(
dep_idents: &'a IdentIdsByModule,
aliases: MutMap<Symbol, Alias>,
imported_abilities_state: PendingAbilitiesStore,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
initial_scope: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: VecSet<Symbol>,
symbols_from_requires: &[(Loc<Symbol>, Loc<TypeAnnotation<'a>>)],
var_store: &mut VarStore,
@ -316,18 +316,13 @@ pub fn canonicalize_module_defs<'a>(
let mut rigid_variables = RigidVariables::default();
// Exposed values are treated like defs that appear before any others, e.g.
//
// imports [Foo.{ bar, baz }]
//
// ...is basically the same as if we'd added these extra defs at the start of the module:
//
// bar = Foo.bar
// baz = Foo.baz
// Iniital scope values are treated like defs that appear before any others.
// They include builtin types that are automatically imported, and for a platform
// package, the required values from the app.
//
// Here we essentially add those "defs" to "the beginning of the module"
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
for (ident, (symbol, region)) in exposed_imports {
for (ident, (symbol, region)) in initial_scope {
let first_char = ident.as_inline_str().as_str().chars().next().unwrap();
if first_char.is_lowercase() {
@ -335,7 +330,7 @@ pub fn canonicalize_module_defs<'a>(
Ok(()) => {
// Add an entry to exposed_imports using the current module's name
// as the key; e.g. if this is the Foo module and we have
// exposes [Bar.{ baz }] then insert Foo.baz as the key, so when
// Bar exposes [baz] then insert Foo.baz as the key, so when
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
can_exposed_imports.insert(symbol, region);
}

View file

@ -128,6 +128,13 @@ fn desugar_value_def<'a>(
preceding_comment: *preceding_comment,
}
}
ModuleImport(roc_parse::ast::ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
}) => *def,
IngestedFileImport(_) => *def,
}
}