Load and can imports inside defs

After parsing a module, we now recursively traverse the tree to find
all imports inside Defs, not just the top-level ones.

Previously, imported modules were available in the entire file,
but that's no longer the case. Therefore, Scope now keeps track of
imported modules and Env::qualified_lookup checks whether a module
is available in the provided scope.

Note: Unused import warnings are still global and need to be updated.
This commit is contained in:
Agus Zubiaga 2024-01-06 18:34:04 -03:00
parent 710d62f754
commit c617963b22
No known key found for this signature in database
10 changed files with 434 additions and 84 deletions

View file

@ -2148,6 +2148,7 @@ fn report_unused_imported_modules(
module_id: ModuleId,
constrained_module: &ConstrainedModule,
) {
// [modules-revamp] TODO: take expr-level into account
let mut unused_imported_modules = constrained_module.imported_modules.clone();
let mut unused_imports = constrained_module.module.exposed_imports.clone();
@ -5228,35 +5229,23 @@ fn parse<'a>(
let mut imported: Vec<(QualifiedModuleName, Region)> = vec![];
for (index, either_index) in parsed_defs.tags.iter().enumerate() {
if let Err(value_index) = either_index.split() {
let value_def = &parsed_defs.value_defs[value_index.index()];
let region = parsed_defs.regions[index];
for (def, region) in ast::RecursiveValueDefIter::new(&parsed_defs) {
match def {
ValueDef::ModuleImport(import) => {
let qualified_module_name = QualifiedModuleName {
opt_package: import.name.value.package,
module: import.name.value.name.into(),
};
match value_def {
ValueDef::Annotation(..) => {}
ValueDef::ModuleImport(import) => {
let qualified_module_name = QualifiedModuleName {
opt_package: import.name.value.package,
module: import.name.value.name.into(),
};
imported.push((qualified_module_name, region));
}
ValueDef::IngestedFileImport(_ingested_file) => {
todo!("[modules-revamp] do we need to do anything here?")
}
_ => {
// [modules-revamp] TODO: traverse exprs
}
imported.push((qualified_module_name, *region));
}
ValueDef::IngestedFileImport(_ingested_file) => {
todo!("[modules-revamp] do we need to do anything here?")
}
_ => {}
}
}
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
// Make sure the module_ids has ModuleIds for all our deps,

View file

@ -0,0 +1,8 @@
interface ExposedUsedOutsideScope exposes [good, bad] imports []
good =
import Dep2 exposing [two]
two
bad =
two

View file

@ -0,0 +1,12 @@
interface ImportInsideDef exposes [dep1Str, dep2TwoDobuled] imports []
dep1Str =
import Dep1
Dep1.str
dep2TwoDobuled =
2
* (
import Dep2 exposing [two]
two
)

View file

@ -0,0 +1,8 @@
interface ImportUsedOutsideScope exposes [good, bad] imports []
good =
import Dep2
Dep2.two
bad =
Dep2.two

View file

@ -438,6 +438,42 @@ fn import_alias() {
);
}
#[test]
fn import_inside_def() {
let subs_by_module = Default::default();
let loaded_module = load_fixture("interface_with_deps", "ImportInsideDef", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"dep1Str" => "Str",
"dep2TwoDobuled" => "Frac *",
},
);
}
#[test]
#[should_panic(expected = "LookupNotInScope")]
fn exposed_used_outside_scope() {
let subs_by_module = Default::default();
load_fixture(
"interface_with_deps",
"ExposedUsedOutsideScope",
subs_by_module,
);
}
#[test]
#[should_panic(expected = "ModuleNotImported")]
fn import_used_outside_scope() {
let subs_by_module = Default::default();
load_fixture(
"interface_with_deps",
"ImportUsedOutsideScope",
subs_by_module,
);
}
#[test]
fn test_load_and_typecheck() {
let subs_by_module = Default::default();
@ -878,6 +914,15 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
^^^
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
UNUSED IMPORT tmp/opaque_wrapped_unwrapped_outside_defining_module/Main
Nothing from Age is used in this module.
3 import Age exposing [Age]
^^^^^^^^^^^^^^^^^^^^^^^^^
Since Age isn't used, you don't need to import it.
"
),
"\n{}",