Implement import aliases

Allows a module to be imported with an alias:

    import JsonDecode as JD

Import aliases must be unique and they cannot have the same name
as an imported module.
This commit is contained in:
Agus Zubiaga 2024-03-29 17:35:24 -03:00
parent d5a38a26db
commit 842a256907
No known key found for this signature in database
6 changed files with 443 additions and 61 deletions

View file

@ -1094,6 +1094,7 @@ fn canonicalize_value_defs<'a>(
imports_introduced.push(introduced_import);
}
PendingValue::InvalidIngestedFile => { /* skip */ }
PendingValue::ImportAliasConflict => { /* skip */ }
}
}
@ -2832,6 +2833,7 @@ enum PendingValue<'a> {
ModuleImport(IntroducedImport),
SignatureDefMismatch,
InvalidIngestedFile,
ImportAliasConflict,
}
struct PendingExpectOrDbg<'a> {
@ -2975,7 +2977,27 @@ fn to_pending_value_def<'a>(
.get_id(&module_name)
.expect("Module id should have been added in load");
scope.import_module(module_id);
match module_import.alias {
Some(alias) => {
let alias_str = alias.item.value.as_str();
if let Err(conflict) = scope.import_module_with_alias(
env.module_ids,
module_id,
alias_str,
alias.item.region,
) {
env.problems.push(Problem::ImportAliasConflict {
alias: alias_str.to_owned(),
conflict,
module_id,
region: alias.item.region,
});
return PendingValue::ImportAliasConflict;
}
}
None => scope.import_module(module_id, module_import.name.region),
}
let mut exposed_symbols;

View file

@ -72,18 +72,23 @@ impl<'a> Env<'a> {
let module_name = ModuleName::from(module_name_str);
match self.module_ids.get_id(&module_name) {
Some(module_id) => self.qualified_lookup_help(scope, module_id, ident, region),
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.module_ids
.available_modules()
.map(|string| string.as_ref().into())
.collect(),
region,
module_exists: false,
}),
match scope.get_alias_import_module_id(module_name_str) {
Some(module_id) => self.qualified_lookup_help(scope, *module_id, true, ident, region),
None => match self.module_ids.get_id(&module_name) {
Some(module_id) => {
self.qualified_lookup_help(scope, module_id, false, ident, region)
}
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.module_ids
.available_modules()
.map(|string| string.as_ref().into())
.collect(),
region,
module_exists: false,
}),
},
}
}
@ -94,7 +99,7 @@ impl<'a> Env<'a> {
ident: &str,
region: Region,
) -> Result<Symbol, RuntimeError> {
self.qualified_lookup_help(scope, module_id, ident, region)
self.qualified_lookup_help(scope, module_id, false, ident, region)
}
/// Returns Err if the symbol resolved, but it was not exposed by the given module
@ -102,6 +107,7 @@ impl<'a> Env<'a> {
&mut self,
scope: &Scope,
module_id: ModuleId,
is_alias_import: bool,
ident: &str,
region: Region,
) -> Result<Symbol, RuntimeError> {
@ -141,7 +147,9 @@ impl<'a> Env<'a> {
}
} else {
match self.dep_idents.get(&module_id) {
Some(exposed_ids) if scope.has_imported_module(&module_id) => {
Some(exposed_ids)
if is_alias_import || scope.has_imported_unaliased_module(&module_id) =>
{
match exposed_ids.get_id(ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);

View file

@ -1,8 +1,8 @@
use roc_collections::{VecMap, VecSet};
use roc_error_macros::internal_error;
use roc_module::ident::Ident;
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_module::symbol::{IdentId, IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{ImportAliasConflict, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{Alias, AliasKind, AliasVar, Type};
@ -30,7 +30,9 @@ pub struct Scope {
exposed_ident_count: usize,
/// Modules that are imported
imported_modules: Vec<ModuleId>,
unaliased_imported_modules: Vec<ModuleId>,
aliased_imported_modules: VecMap<String, ModuleId>,
import_regions: VecMap<ModuleId, Region>,
/// Identifiers that are imported
imported_symbols: Vec<(Ident, Symbol, Region)>,
@ -71,7 +73,9 @@ impl Scope {
aliases: VecMap::default(),
abilities_store: starting_abilities_store,
shadows: VecMap::default(),
imported_modules: Vec::default(),
unaliased_imported_modules: Vec::default(),
aliased_imported_modules: VecMap::default(),
import_regions: VecMap::default(),
imported_symbols: default_imports,
ignored_locals: VecMap::default(),
}
@ -206,8 +210,12 @@ impl Scope {
}
}
pub fn has_imported_module(&self, module_id: &ModuleId) -> bool {
module_id.is_builtin() || self.imported_modules.contains(module_id)
pub fn get_alias_import_module_id(&self, alias: &str) -> Option<&ModuleId> {
self.aliased_imported_modules.get(&alias.to_owned())
}
pub fn has_imported_unaliased_module(&self, module_id: &ModuleId) -> bool {
module_id.is_builtin() || self.unaliased_imported_modules.contains(module_id)
}
fn has_imported_symbol(&self, ident: &str) -> Option<(Symbol, Region)> {
@ -402,8 +410,46 @@ impl Scope {
Ok(())
}
pub fn import_module(&mut self, module_id: ModuleId) {
self.imported_modules.push(module_id);
pub fn import_module_with_alias(
&mut self,
module_ids: &ModuleIds,
module_id: ModuleId,
alias_name: &str,
region: Region,
) -> Result<(), ImportAliasConflict> {
let alias = alias_name.to_owned();
if let Some(first_module_id) = self.aliased_imported_modules.get(&alias) {
return Err(ImportAliasConflict::Alias(
*first_module_id,
*self.import_regions.get(first_module_id).unwrap(),
));
}
if let Some(same_name_module_id) = module_ids.get_id(&alias_name.into()) {
if same_name_module_id.is_builtin() {
return Err(ImportAliasConflict::Builtin);
}
if self
.unaliased_imported_modules
.contains(&same_name_module_id)
{
return Err(ImportAliasConflict::Module(
*self.import_regions.get(&same_name_module_id).unwrap(),
));
}
}
self.aliased_imported_modules.insert(alias, module_id);
self.import_regions.insert(module_id, region);
Ok(())
}
pub fn import_module(&mut self, module_id: ModuleId, region: Region) {
self.unaliased_imported_modules.push(module_id);
self.import_regions.insert(module_id, region);
}
pub fn add_alias(
@ -442,15 +488,21 @@ impl Scope {
let aliases_count = self.aliases.len();
let ignored_locals_count = self.ignored_locals.len();
let locals_snapshot = self.locals.in_scope.len();
let imported_modules_snapshot = self.imported_modules.len();
let imported_symbols_snapshot = self.imported_symbols.len();
let unaliased_imported_modules_snapshot = self.unaliased_imported_modules.len();
let aliased_imported_modules_snapshot = self.aliased_imported_modules.len();
let import_regions_snapshot = self.import_regions.len();
let result = f(self);
self.aliases.truncate(aliases_count);
self.ignored_locals.truncate(ignored_locals_count);
self.imported_modules.truncate(imported_modules_snapshot);
self.imported_symbols.truncate(imported_symbols_snapshot);
self.unaliased_imported_modules
.truncate(unaliased_imported_modules_snapshot);
self.aliased_imported_modules
.truncate(aliased_imported_modules_snapshot);
self.import_regions.truncate(import_regions_snapshot);
// anything added in the inner scope is no longer in scope now
for i in locals_snapshot..self.locals.in_scope.len() {