From 13731d98b364f53c29b3bb1463c3895cae605868 Mon Sep 17 00:00:00 2001 From: jjcnn <38888011+jjcnn@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:36:57 +0100 Subject: [PATCH] Refactor: Imports must use absolute paths (#5697) ## Description This PR contains the final bit of refactoring before I start fixing the import problems mentioned in #5498. The semantics of imports has so far been implemented in the `Module` struct, and has assumed that the destination path of an import was relative to `self`. In particular, this allows imports to any submodule of the current module. However, whenever that logic was triggered by the compiler the destination path was always absolute. The only reason this worked was that the logic was only ever triggered on the root module. To reflect this behavior I have therefore moved the import semantics to the `Root` struct, and made it clear in the comments that the paths are assumed to be absolute. This also reflects the fact that once the module structure has been populated with the imported entities we don't actually need the import logic anymore, so it shouldn't be located in the `Module` struct. (Name resolution should obviously stay in the `Module` struct, so that logic has not been moved). I have left a couple of TODOs in the code to remind myself where I need to make changes when I start implementing `pub use` (see #3113 ). ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: IGI-111 --- forc-pkg/src/pkg.rs | 26 +- sway-core/src/ir_generation/const_eval.rs | 4 +- sway-core/src/language/call_path.rs | 2 +- sway-core/src/lib.rs | 8 +- .../ast_node/expression/typed_expression.rs | 3 +- .../src/semantic_analysis/ast_node/mod.rs | 16 +- .../namespace/lexical_scope.rs | 7 +- .../src/semantic_analysis/namespace/module.rs | 457 +----------------- .../semantic_analysis/namespace/namespace.rs | 5 +- .../src/semantic_analysis/namespace/root.rs | 446 ++++++++++++++++- sway-core/src/semantic_analysis/program.rs | 2 +- .../semantic_analysis/type_check_context.rs | 45 +- test/src/ir_generation/mod.rs | 14 +- 13 files changed, 501 insertions(+), 534 deletions(-) diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 6ae375d4015..59f7c798dd4 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -1595,11 +1595,11 @@ pub fn dependency_namespace( engines: &Engines, contract_id_value: Option, experimental: sway_core::ExperimentalFlags, -) -> Result> { +) -> Result> { // TODO: Clean this up when config-time constants v1 are removed. let node_idx = &graph[node]; let name = Some(Ident::new_no_span(node_idx.name.clone())); - let mut namespace = if let Some(contract_id_value) = contract_id_value { + let mut root_module = if let Some(contract_id_value) = contract_id_value { namespace::Module::default_with_contract_id( engines, name.clone(), @@ -1610,9 +1610,9 @@ pub fn dependency_namespace( namespace::Module::default() }; - namespace.is_external = true; - namespace.name = name; - namespace.visibility = Visibility::Public; + root_module.is_external = true; + root_module.name = name; + root_module.visibility = Visibility::Public; // Add direct dependencies. let mut core_added = false; @@ -1647,7 +1647,7 @@ pub fn dependency_namespace( ns } }; - namespace.insert_submodule(dep_name, dep_namespace); + root_module.insert_submodule(dep_name, dep_namespace); let dep = &graph[dep_node]; if dep.name == CORE { core_added = true; @@ -1658,29 +1658,29 @@ pub fn dependency_namespace( if !core_added { if let Some(core_node) = find_core_dep(graph, node) { let core_namespace = &lib_namespace_map[&core_node]; - namespace.insert_submodule(CORE.to_string(), core_namespace.clone()); + root_module.insert_submodule(CORE.to_string(), core_namespace.clone()); } } - let _ = namespace.star_import_with_reexports( + let mut root = namespace::Root::from(root_module); + + let _ = root.star_import_with_reexports( &Handler::default(), engines, &[CORE, PRELUDE].map(|s| Ident::new_no_span(s.into())), &[], - true, ); if has_std_dep(graph, node) { - let _ = namespace.star_import_with_reexports( + let _ = root.star_import_with_reexports( &Handler::default(), engines, &[STD, PRELUDE].map(|s| Ident::new_no_span(s.into())), &[], - true, ); } - Ok(namespace) + Ok(root) } /// Find the `std` dependency, if it is a direct one, of the given node. @@ -1757,7 +1757,7 @@ pub fn compile( pkg: &PackageDescriptor, profile: &BuildProfile, engines: &Engines, - namespace: namespace::Module, + namespace: namespace::Root, source_map: &mut SourceMap, ) -> Result { let mut metrics = PerformanceData::default(); diff --git a/sway-core/src/ir_generation/const_eval.rs b/sway-core/src/ir_generation/const_eval.rs index 35ef5cdbaaf..fb4cb968c8c 100644 --- a/sway-core/src/ir_generation/const_eval.rs +++ b/sway-core/src/ir_generation/const_eval.rs @@ -1185,12 +1185,12 @@ mod tests { let handler = Handler::default(); let mut context = Context::new(engines.se(), sway_ir::ExperimentalFlags::default()); let mut md_mgr = MetadataManager::default(); - let core_lib = namespace::Module { + let core_lib = namespace::Root::from(namespace::Module { name: Some(sway_types::Ident::new_no_span( "assert_is_constant_test".to_string(), )), ..Default::default() - }; + }); let r = crate::compile_to_ast( &handler, diff --git a/sway-core/src/language/call_path.rs b/sway-core/src/language/call_path.rs index d19c5c404ed..1fd6bf49445 100644 --- a/sway-core/src/language/call_path.rs +++ b/sway-core/src/language/call_path.rs @@ -320,7 +320,7 @@ impl CallPath { .get(&self.suffix) { synonym_prefixes = use_synonym.0.clone(); - is_absolute = use_synonym.3; + is_absolute = true; let submodule = namespace.module().submodule(&[use_synonym.0[0].clone()]); if let Some(submodule) = submodule { is_external = submodule.is_external; diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index a30d2b92660..1f2615a1e42 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -474,7 +474,7 @@ pub fn parsed_to_ast( handler: &Handler, engines: &Engines, parse_program: &parsed::ParseProgram, - initial_namespace: namespace::Module, + initial_namespace: namespace::Root, build_config: Option<&BuildConfig>, package_name: &str, retrigger_compilation: Option>, @@ -638,7 +638,7 @@ pub fn compile_to_ast( handler: &Handler, engines: &Engines, input: Arc, - initial_namespace: namespace::Module, + initial_namespace: namespace::Root, build_config: Option<&BuildConfig>, package_name: &str, retrigger_compilation: Option>, @@ -735,7 +735,7 @@ pub fn compile_to_asm( handler: &Handler, engines: &Engines, input: Arc, - initial_namespace: namespace::Module, + initial_namespace: namespace::Root, build_config: BuildConfig, package_name: &str, ) -> Result { @@ -892,7 +892,7 @@ pub fn compile_to_bytecode( handler: &Handler, engines: &Engines, input: Arc, - initial_namespace: namespace::Module, + initial_namespace: namespace::Root, build_config: BuildConfig, source_map: &mut SourceMap, package_name: &str, diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 77b7b61193c..0d01bdea725 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -2433,7 +2433,8 @@ mod tests { type_annotation: TypeId, experimental: ExperimentalFlags, ) -> Result { - let mut namespace = Namespace::init_root(namespace::Module::default()); + let root_module = namespace::Root::from(namespace::Module::default()); + let mut namespace = Namespace::init_root(root_module); let ctx = TypeCheckContext::from_namespace(&mut namespace, engines, experimental) .with_type_annotation(type_annotation); ty::TyExpression::type_check(handler, ctx, expr) diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index 73e3650962e..84ae5481756 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -49,8 +49,7 @@ impl ty::TyAstNode { ImportType::Star => { // try a standard starimport first let star_import_handler = Handler::default(); - let import = - ctx.star_import(&star_import_handler, &path, a.is_absolute); + let import = ctx.star_import(&star_import_handler, &path); if import.is_ok() { handler.append(star_import_handler); import @@ -62,7 +61,6 @@ impl ty::TyAstNode { &variant_import_handler, path, enum_name, - a.is_absolute, ); if variant_import.is_ok() { handler.append(variant_import_handler); @@ -78,18 +76,13 @@ impl ty::TyAstNode { } } ImportType::SelfImport(_) => { - ctx.self_import(handler, &path, a.alias.clone(), a.is_absolute) + ctx.self_import(handler, &path, a.alias.clone()) } ImportType::Item(ref s) => { // try a standard item import first let item_import_handler = Handler::default(); - let import = ctx.item_import( - &item_import_handler, - &path, - s, - a.alias.clone(), - a.is_absolute, - ); + let import = + ctx.item_import(&item_import_handler, &path, s, a.alias.clone()); if import.is_ok() { handler.append(item_import_handler); @@ -104,7 +97,6 @@ impl ty::TyAstNode { enum_name, s, a.alias.clone(), - a.is_absolute, ); if variant_import.is_ok() { handler.append(variant_import_handler); diff --git a/sway-core/src/semantic_analysis/namespace/lexical_scope.rs b/sway-core/src/semantic_analysis/namespace/lexical_scope.rs index f0665e1a670..932c3128707 100644 --- a/sway-core/src/semantic_analysis/namespace/lexical_scope.rs +++ b/sway-core/src/semantic_analysis/namespace/lexical_scope.rs @@ -28,8 +28,8 @@ pub(crate) enum GlobImport { } pub(super) type SymbolMap = im::OrdMap; -// The final `bool` field of `UseSynonyms` is true if the `Vec` path is absolute. -pub(super) type UseSynonyms = im::HashMap, GlobImport, ty::TyDecl, bool)>; +// The `Vec` path is absolute. +pub(super) type UseSynonyms = im::HashMap, GlobImport, ty::TyDecl)>; pub(super) type UseAliases = im::HashMap; /// Represents a lexical scope integer-based identifier, which can be used to reference @@ -261,8 +261,7 @@ impl Items { append_shadowing_error(ident, decl, false, false, &item, const_shadowing_mode); } - if let Some((ident, (_, GlobImport::No, decl, _))) = self.use_synonyms.get_key_value(&name) - { + if let Some((ident, (_, GlobImport::No, decl))) = self.use_synonyms.get_key_value(&name) { append_shadowing_error( ident, decl, diff --git a/sway-core/src/semantic_analysis/namespace/module.rs b/sway-core/src/semantic_analysis/namespace/module.rs index d0d03f5f511..4d43813e407 100644 --- a/sway-core/src/semantic_analysis/namespace/module.rs +++ b/sway-core/src/semantic_analysis/namespace/module.rs @@ -3,7 +3,7 @@ use crate::{ engine_threading::Engines, language::{ parsed::*, - ty::{self, TyDecl, TyTraitItem}, + ty::{self, TyTraitItem}, CallPath, Visibility, }, semantic_analysis::*, @@ -12,9 +12,8 @@ use crate::{ }; use super::{ - lexical_scope::{GlobImport, Items, LexicalScope, SymbolMap}, + lexical_scope::{Items, LexicalScope, SymbolMap}, root::Root, - trait_map::TraitMap, LexicalScopeId, ModuleName, Path, PathBuf, }; @@ -23,7 +22,6 @@ use sway_error::handler::Handler; use sway_error::{error::CompileError, handler::ErrorEmitted}; use sway_parse::{lex, Parser}; use sway_types::{span::Span, Spanned}; -use sway_utils::iter_prefixes; /// A single `Module` within a Sway project. /// @@ -162,7 +160,8 @@ impl Module { content: AstNodeContent::Declaration(Declaration::ConstantDeclaration(const_decl_id)), span: const_item_span.clone(), }; - let mut ns = Namespace::init_root(Default::default()); + let root = Root::from(Module::default()); + let mut ns = Namespace::init_root(root); // This is pretty hacky but that's okay because of this code is being removed pretty soon ns.root.module.name = ns_name; ns.root.module.is_external = true; @@ -288,446 +287,6 @@ impl Module { self.current_lexical_scope_id = parent_scope_id.unwrap_or(0); } - /// Given a path to a `src` module, create synonyms to every symbol in that module to the given - /// `dst` module. - /// - /// This is used when an import path contains an asterisk. - /// - /// Paths are assumed to be relative to `self`. - pub(crate) fn star_import( - &mut self, - handler: &Handler, - engines: &Engines, - src: &Path, - dst: &Path, - is_src_absolute: bool, - ) -> Result<(), ErrorEmitted> { - self.check_module_privacy(handler, src)?; - - let decl_engine = engines.de(); - - let src_mod = self.check_submodule(handler, src)?; - - let implemented_traits = src_mod.current_items().implemented_traits.clone(); - let mut symbols_and_decls = vec![]; - for (symbol, decl) in src_mod.current_items().symbols.iter() { - if is_ancestor(src, dst) || decl.visibility(decl_engine).is_public() { - symbols_and_decls.push((symbol.clone(), decl.clone())); - } - } - - let dst_mod = &mut self[dst]; - dst_mod - .current_items_mut() - .implemented_traits - .extend(implemented_traits, engines); - for symbol_and_decl in symbols_and_decls { - dst_mod.current_items_mut().use_synonyms.insert( - symbol_and_decl.0, - ( - src.to_vec(), - GlobImport::Yes, - symbol_and_decl.1, - is_src_absolute, - ), - ); - } - - Ok(()) - } - - /// Given a path to a `src` module, create synonyms to every symbol in that module to the given - /// `dst` module. - /// - /// This is used when an import path contains an asterisk. - /// - /// Paths are assumed to be relative to `self`. - pub fn star_import_with_reexports( - &mut self, - handler: &Handler, - engines: &Engines, - src: &Path, - dst: &Path, - is_src_absolute: bool, - ) -> Result<(), ErrorEmitted> { - self.check_module_privacy(handler, src)?; - - let decl_engine = engines.de(); - - let src_mod = self.check_submodule(handler, src)?; - - let implemented_traits = src_mod.current_items().implemented_traits.clone(); - let use_synonyms = src_mod.current_items().use_synonyms.clone(); - let mut symbols_and_decls = src_mod - .current_items() - .use_synonyms - .iter() - .map(|(symbol, (_, _, decl, _))| (symbol.clone(), decl.clone())) - .collect::>(); - for (symbol, decl) in src_mod.current_items().symbols.iter() { - if is_ancestor(src, dst) || decl.visibility(decl_engine).is_public() { - symbols_and_decls.push((symbol.clone(), decl.clone())); - } - } - - let mut symbols_paths_and_decls = vec![]; - for (symbol, (mod_path, _, decl, _)) in use_synonyms { - let mut is_external = false; - let submodule = src_mod.submodule(&[mod_path[0].clone()]); - if let Some(submodule) = submodule { - is_external = submodule.is_external - }; - - let mut path = src[..1].to_vec(); - if is_external { - path = mod_path; - } else { - path.extend(mod_path); - } - - symbols_paths_and_decls.push((symbol, path, decl)); - } - - let dst_mod = &mut self[dst]; - dst_mod - .current_items_mut() - .implemented_traits - .extend(implemented_traits, engines); - - let mut try_add = |symbol, path, decl: ty::TyDecl| { - dst_mod - .current_items_mut() - .use_synonyms - .insert(symbol, (path, GlobImport::Yes, decl, is_src_absolute)); - }; - - for (symbol, decl) in symbols_and_decls { - try_add(symbol, src.to_vec(), decl); - } - - for (symbol, path, decl) in symbols_paths_and_decls { - try_add(symbol, path, decl); - } - - Ok(()) - } - - /// Pull a single item from a `src` module and import it into the `dst` module. - /// - /// The item we want to import is basically the last item in path because this is a `self` - /// import. - pub(crate) fn self_import( - &mut self, - handler: &Handler, - engines: &Engines, - src: &Path, - dst: &Path, - alias: Option, - is_src_absolute: bool, - ) -> Result<(), ErrorEmitted> { - let (last_item, src) = src.split_last().expect("guaranteed by grammar"); - self.item_import( - handler, - engines, - src, - last_item, - dst, - alias, - is_src_absolute, - ) - } - - /// Pull a single `item` from the given `src` module and import it into the `dst` module. - /// - /// Paths are assumed to be relative to `self`. - #[allow(clippy::too_many_arguments)] - pub(crate) fn item_import( - &mut self, - handler: &Handler, - engines: &Engines, - src: &Path, - item: &Ident, - dst: &Path, - alias: Option, - is_src_absolute: bool, - ) -> Result<(), ErrorEmitted> { - self.check_module_privacy(handler, src)?; - - let decl_engine = engines.de(); - - let src_mod = self.check_submodule(handler, src)?; - let mut impls_to_insert = TraitMap::default(); - match src_mod.current_items().symbols.get(item).cloned() { - Some(decl) => { - if !decl.visibility(decl_engine).is_public() && !is_ancestor(src, dst) { - handler.emit_err(CompileError::ImportPrivateSymbol { - name: item.clone(), - span: item.span(), - }); - } - - // if this is an enum or struct or function, import its implementations - if let Ok(type_id) = decl.return_type(&Handler::default(), engines) { - impls_to_insert.extend( - src_mod - .current_items() - .implemented_traits - .filter_by_type_item_import(type_id, engines), - engines, - ); - } - // if this is a trait, import its implementations - let decl_span = decl.span(); - if let TyDecl::TraitDecl(_) = &decl { - // TODO: we only import local impls from the source namespace - // this is okay for now but we'll need to device some mechanism to collect all available trait impls - impls_to_insert.extend( - src_mod - .current_items() - .implemented_traits - .filter_by_trait_decl_span(decl_span), - engines, - ); - } - // no matter what, import it this way though. - let dst_mod = &mut self[dst]; - let add_synonym = |name| { - if let Some((_, GlobImport::No, _, _)) = - dst_mod.current_items().use_synonyms.get(name) - { - handler.emit_err(CompileError::ShadowsOtherSymbol { name: name.into() }); - } - dst_mod.current_items_mut().use_synonyms.insert( - name.clone(), - (src.to_vec(), GlobImport::No, decl, is_src_absolute), - ); - }; - match alias { - Some(alias) => { - add_synonym(&alias); - dst_mod - .current_items_mut() - .use_aliases - .insert(alias.as_str().to_string(), item.clone()); - } - None => add_synonym(item), - }; - } - None => { - return Err(handler.emit_err(CompileError::SymbolNotFound { - name: item.clone(), - span: item.span(), - })); - } - }; - - let dst_mod = &mut self[dst]; - dst_mod - .current_items_mut() - .implemented_traits - .extend(impls_to_insert, engines); - - Ok(()) - } - - /// Pull a single variant `variant` from the enum `enum_name` from the given `src` module and import it into the `dst` module. - /// - /// Paths are assumed to be relative to `self`. - #[allow(clippy::too_many_arguments)] // TODO: remove lint bypass once private modules are no longer experimental - pub(crate) fn variant_import( - &mut self, - handler: &Handler, - engines: &Engines, - src: &Path, - enum_name: &Ident, - variant_name: &Ident, - dst: &Path, - alias: Option, - is_src_absolute: bool, - ) -> Result<(), ErrorEmitted> { - self.check_module_privacy(handler, src)?; - - let decl_engine = engines.de(); - - let src_mod = self.check_submodule(handler, src)?; - match src_mod.current_items().symbols.get(enum_name).cloned() { - Some(decl) => { - if !decl.visibility(decl_engine).is_public() && !is_ancestor(src, dst) { - handler.emit_err(CompileError::ImportPrivateSymbol { - name: enum_name.clone(), - span: enum_name.span(), - }); - } - - if let TyDecl::EnumDecl(ty::EnumDecl { - decl_id, - subst_list: _, - .. - }) = decl - { - let enum_decl = decl_engine.get_enum(&decl_id); - let enum_ref = DeclRef::new( - enum_decl.call_path.suffix.clone(), - decl_id, - enum_decl.span(), - ); - - if let Some(variant_decl) = - enum_decl.variants.iter().find(|v| v.name == *variant_name) - { - // import it this way. - let dst_mod = &mut self[dst]; - let mut add_synonym = |name| { - if let Some((_, GlobImport::No, _, _)) = - dst_mod.current_items().use_synonyms.get(name) - { - handler.emit_err(CompileError::ShadowsOtherSymbol { - name: name.into(), - }); - } - dst_mod.current_items_mut().use_synonyms.insert( - name.clone(), - ( - src.to_vec(), - GlobImport::No, - TyDecl::EnumVariantDecl(ty::EnumVariantDecl { - enum_ref: enum_ref.clone(), - variant_name: variant_name.clone(), - variant_decl_span: variant_decl.span.clone(), - }), - is_src_absolute, - ), - ); - }; - match alias { - Some(alias) => { - add_synonym(&alias); - dst_mod - .current_items_mut() - .use_aliases - .insert(alias.as_str().to_string(), variant_name.clone()); - } - None => add_synonym(variant_name), - }; - } else { - return Err(handler.emit_err(CompileError::SymbolNotFound { - name: variant_name.clone(), - span: variant_name.span(), - })); - } - } else { - return Err(handler.emit_err(CompileError::Internal( - "Attempting to import variants of something that isn't an enum", - enum_name.span(), - ))); - } - } - None => { - return Err(handler.emit_err(CompileError::SymbolNotFound { - name: enum_name.clone(), - span: enum_name.span(), - })); - } - }; - - Ok(()) - } - - /// Pull all variants from the enum `enum_name` from the given `src` module and import them all into the `dst` module. - /// - /// Paths are assumed to be relative to `self`. - pub(crate) fn variant_star_import( - &mut self, - handler: &Handler, - engines: &Engines, - src: &Path, - dst: &Path, - enum_name: &Ident, - is_src_absolute: bool, - ) -> Result<(), ErrorEmitted> { - self.check_module_privacy(handler, src)?; - - let decl_engine = engines.de(); - - let src_mod = self.check_submodule(handler, src)?; - match src_mod.current_items().symbols.get(enum_name).cloned() { - Some(decl) => { - if !decl.visibility(decl_engine).is_public() && !is_ancestor(src, dst) { - handler.emit_err(CompileError::ImportPrivateSymbol { - name: enum_name.clone(), - span: enum_name.span(), - }); - } - - if let TyDecl::EnumDecl(ty::EnumDecl { - decl_id, - subst_list: _, - .. - }) = decl - { - let enum_decl = decl_engine.get_enum(&decl_id); - let enum_ref = DeclRef::new( - enum_decl.call_path.suffix.clone(), - decl_id, - enum_decl.span(), - ); - - for variant_decl in enum_decl.variants.iter() { - let variant_name = &variant_decl.name; - - // import it this way. - let dst_mod = &mut self[dst]; - dst_mod.current_items_mut().use_synonyms.insert( - variant_name.clone(), - ( - src.to_vec(), - GlobImport::Yes, - TyDecl::EnumVariantDecl(ty::EnumVariantDecl { - enum_ref: enum_ref.clone(), - variant_name: variant_name.clone(), - variant_decl_span: variant_decl.span.clone(), - }), - is_src_absolute, - ), - ); - } - } else { - return Err(handler.emit_err(CompileError::Internal( - "Attempting to import variants of something that isn't an enum", - enum_name.span(), - ))); - } - } - None => { - return Err(handler.emit_err(CompileError::SymbolNotFound { - name: enum_name.clone(), - span: enum_name.span(), - })); - } - }; - - Ok(()) - } - - fn check_module_privacy(&self, handler: &Handler, src: &Path) -> Result<(), ErrorEmitted> { - let dst = &self.mod_path; - // you are always allowed to access your ancestor's symbols - if !is_ancestor(src, dst) { - // we don't check the first prefix because direct children are always accessible - for prefix in iter_prefixes(src).skip(1) { - let module = self.check_submodule(handler, prefix)?; - if module.visibility.is_private() { - let prefix_last = prefix[prefix.len() - 1].clone(); - handler.emit_err(CompileError::ImportPrivateModule { - span: prefix_last.span(), - name: prefix_last, - }); - } - } - } - Ok(()) - } - /// Resolve a symbol that is potentially prefixed with some path, e.g. `foo::bar::symbol`. /// /// This is short-hand for concatenating the `mod_path` with the `call_path`'s prefixes and @@ -1041,8 +600,8 @@ impl Module { .get(symbol.as_str()) .unwrap_or(symbol); match module.current_items().use_synonyms.get(symbol) { - Some((_, _, decl @ ty::TyDecl::EnumVariantDecl { .. }, _)) => Ok(decl.clone()), - Some((src_path, _, _, _)) if mod_path != src_path => { + Some((_, _, decl @ ty::TyDecl::EnumVariantDecl { .. })) => Ok(decl.clone()), + Some((src_path, _, _)) if mod_path != src_path => { // If the symbol is imported, before resolving to it, // we need to check if there is a local symbol withing the module with // the same name, and if yes resolve to the local symbol, because it @@ -1103,7 +662,3 @@ fn module_not_found(path: &[Ident]) -> CompileError { .join("::"), } } - -fn is_ancestor(src: &Path, dst: &Path) -> bool { - dst.len() >= src.len() && src.iter().zip(dst).all(|(src, dst)| src == dst) -} diff --git a/sway-core/src/semantic_analysis/namespace/namespace.rs b/sway-core/src/semantic_analysis/namespace/namespace.rs index 89d3b9bfae6..e548a65024c 100644 --- a/sway-core/src/semantic_analysis/namespace/namespace.rs +++ b/sway-core/src/semantic_analysis/namespace/namespace.rs @@ -42,11 +42,10 @@ pub struct Namespace { impl Namespace { /// Initialise the namespace at its root from the given initial namespace. - pub fn init_root(init: Module) -> Self { - let root = Root::from(init.clone()); + pub fn init_root(root: Root) -> Self { let mod_path = vec![]; Self { - init, + init: root.module.clone(), root, mod_path, } diff --git a/sway-core/src/semantic_analysis/namespace/root.rs b/sway-core/src/semantic_analysis/namespace/root.rs index c0840c05744..da552e68183 100644 --- a/sway-core/src/semantic_analysis/namespace/root.rs +++ b/sway-core/src/semantic_analysis/namespace/root.rs @@ -1,4 +1,18 @@ -use super::{module::Module, namespace::Namespace}; +use super::{ + lexical_scope::GlobImport, module::Module, namespace::Namespace, trait_map::TraitMap, Ident, +}; +use crate::{ + decl_engine::DeclRef, + engine_threading::*, + language::ty::{self, TyDecl}, + namespace::Path, +}; +use sway_error::{ + error::CompileError, + handler::{ErrorEmitted, Handler}, +}; +use sway_types::Spanned; +use sway_utils::iter_prefixes; /// The root module, from which all other modules can be accessed. /// @@ -13,6 +27,432 @@ pub struct Root { pub(crate) module: Module, } +impl Root { + /// Given a path to a `src` module, create synonyms to every symbol in that module to the given + /// `dst` module. + /// + /// This is used when an import path contains an asterisk. + /// + /// Paths are assumed to be absolute. + pub(crate) fn star_import( + &mut self, + handler: &Handler, + engines: &Engines, + src: &Path, + dst: &Path, + ) -> Result<(), ErrorEmitted> { + self.check_module_privacy(handler, src)?; + + let decl_engine = engines.de(); + + let src_mod = self.module.check_submodule(handler, src)?; + + let implemented_traits = src_mod.current_items().implemented_traits.clone(); + let mut symbols_and_decls = vec![]; + for (symbol, decl) in src_mod.current_items().symbols.iter() { + if is_ancestor(src, dst) || decl.visibility(decl_engine).is_public() { + symbols_and_decls.push((symbol.clone(), decl.clone())); + } + } + + let dst_mod = &mut self.module[dst]; + dst_mod + .current_items_mut() + .implemented_traits + .extend(implemented_traits, engines); // TODO: No difference made between imported and declared items + for symbol_and_decl in symbols_and_decls { + dst_mod.current_items_mut().use_synonyms.insert( + // TODO: No difference made between imported and declared items + symbol_and_decl.0, + (src.to_vec(), GlobImport::Yes, symbol_and_decl.1), + ); + } + + Ok(()) + } + + /// Pull a single item from a `src` module and import it into the `dst` module. + /// + /// The item we want to import is basically the last item in path because this is a `self` + /// import. + pub(crate) fn self_import( + &mut self, + handler: &Handler, + engines: &Engines, + src: &Path, + dst: &Path, + alias: Option, + ) -> Result<(), ErrorEmitted> { + let (last_item, src) = src.split_last().expect("guaranteed by grammar"); + self.item_import(handler, engines, src, last_item, dst, alias) + } + + /// Pull a single `item` from the given `src` module and import it into the `dst` module. + /// + /// Paths are assumed to be absolute. + #[allow(clippy::too_many_arguments)] + pub(crate) fn item_import( + &mut self, + handler: &Handler, + engines: &Engines, + src: &Path, + item: &Ident, + dst: &Path, + alias: Option, + ) -> Result<(), ErrorEmitted> { + self.check_module_privacy(handler, src)?; + + let decl_engine = engines.de(); + + let src_mod = self.module.check_submodule(handler, src)?; + let mut impls_to_insert = TraitMap::default(); + match src_mod.current_items().symbols.get(item).cloned() { + Some(decl) => { + if !decl.visibility(decl_engine).is_public() && !is_ancestor(src, dst) { + handler.emit_err(CompileError::ImportPrivateSymbol { + name: item.clone(), + span: item.span(), + }); + } + + // if this is an enum or struct or function, import its implementations + if let Ok(type_id) = decl.return_type(&Handler::default(), engines) { + impls_to_insert.extend( + src_mod + .current_items() + .implemented_traits + .filter_by_type_item_import(type_id, engines), + engines, + ); + } + // if this is a trait, import its implementations + let decl_span = decl.span(); + if let TyDecl::TraitDecl(_) = &decl { + // TODO: we only import local impls from the source namespace + // this is okay for now but we'll need to device some mechanism to collect all available trait impls + impls_to_insert.extend( + src_mod + .current_items() + .implemented_traits + .filter_by_trait_decl_span(decl_span), + engines, + ); + } + // no matter what, import it this way though. + let dst_mod = &mut self.module[dst]; + let add_synonym = |name| { + if let Some((_, GlobImport::No, _)) = + dst_mod.current_items().use_synonyms.get(name) + { + handler.emit_err(CompileError::ShadowsOtherSymbol { name: name.into() }); + } + dst_mod.current_items_mut().use_synonyms.insert( + // TODO: No difference made between imported and declared items + name.clone(), + (src.to_vec(), GlobImport::No, decl), + ); + }; + match alias { + Some(alias) => { + add_synonym(&alias); + dst_mod + .current_items_mut() + .use_aliases + .insert(alias.as_str().to_string(), item.clone()); // TODO: No difference made between imported and declared items + } + None => add_synonym(item), + }; + } + None => { + return Err(handler.emit_err(CompileError::SymbolNotFound { + name: item.clone(), + span: item.span(), + })); + } + }; + + let dst_mod = &mut self.module[dst]; + dst_mod + .current_items_mut() + .implemented_traits + .extend(impls_to_insert, engines); // TODO: No difference made between imported and declared items + + Ok(()) + } + + /// Pull a single variant `variant` from the enum `enum_name` from the given `src` module and import it into the `dst` module. + /// + /// Paths are assumed to be absolute. + #[allow(clippy::too_many_arguments)] // TODO: remove lint bypass once private modules are no longer experimental + pub(crate) fn variant_import( + &mut self, + handler: &Handler, + engines: &Engines, + src: &Path, + enum_name: &Ident, + variant_name: &Ident, + dst: &Path, + alias: Option, + ) -> Result<(), ErrorEmitted> { + self.check_module_privacy(handler, src)?; + + let decl_engine = engines.de(); + + let src_mod = self.module.check_submodule(handler, src)?; + match src_mod.current_items().symbols.get(enum_name).cloned() { + Some(decl) => { + if !decl.visibility(decl_engine).is_public() && !is_ancestor(src, dst) { + handler.emit_err(CompileError::ImportPrivateSymbol { + name: enum_name.clone(), + span: enum_name.span(), + }); + } + + if let TyDecl::EnumDecl(ty::EnumDecl { + decl_id, + subst_list: _, + .. + }) = decl + { + let enum_decl = decl_engine.get_enum(&decl_id); + let enum_ref = DeclRef::new( + enum_decl.call_path.suffix.clone(), + decl_id, + enum_decl.span(), + ); + + if let Some(variant_decl) = + enum_decl.variants.iter().find(|v| v.name == *variant_name) + { + // import it this way. + let dst_mod = &mut self.module[dst]; + let mut add_synonym = |name| { + if let Some((_, GlobImport::No, _)) = + dst_mod.current_items().use_synonyms.get(name) + { + handler.emit_err(CompileError::ShadowsOtherSymbol { + name: name.into(), + }); + } + dst_mod.current_items_mut().use_synonyms.insert( + // TODO: No difference made between imported and declared items + name.clone(), + ( + src.to_vec(), + GlobImport::No, + TyDecl::EnumVariantDecl(ty::EnumVariantDecl { + enum_ref: enum_ref.clone(), + variant_name: variant_name.clone(), + variant_decl_span: variant_decl.span.clone(), + }), + ), + ); + }; + match alias { + Some(alias) => { + add_synonym(&alias); + dst_mod + .current_items_mut() + .use_aliases + .insert(alias.as_str().to_string(), variant_name.clone()); + // TODO: No difference made between imported and declared items + } + None => add_synonym(variant_name), + }; + } else { + return Err(handler.emit_err(CompileError::SymbolNotFound { + name: variant_name.clone(), + span: variant_name.span(), + })); + } + } else { + return Err(handler.emit_err(CompileError::Internal( + "Attempting to import variants of something that isn't an enum", + enum_name.span(), + ))); + } + } + None => { + return Err(handler.emit_err(CompileError::SymbolNotFound { + name: enum_name.clone(), + span: enum_name.span(), + })); + } + }; + + Ok(()) + } + + /// Pull all variants from the enum `enum_name` from the given `src` module and import them all into the `dst` module. + /// + /// Paths are assumed to be absolute. + pub(crate) fn variant_star_import( + &mut self, + handler: &Handler, + engines: &Engines, + src: &Path, + dst: &Path, + enum_name: &Ident, + ) -> Result<(), ErrorEmitted> { + self.check_module_privacy(handler, src)?; + + let decl_engine = engines.de(); + + let src_mod = self.module.check_submodule(handler, src)?; + match src_mod.current_items().symbols.get(enum_name).cloned() { + Some(decl) => { + if !decl.visibility(decl_engine).is_public() && !is_ancestor(src, dst) { + handler.emit_err(CompileError::ImportPrivateSymbol { + name: enum_name.clone(), + span: enum_name.span(), + }); + } + + if let TyDecl::EnumDecl(ty::EnumDecl { + decl_id, + subst_list: _, + .. + }) = decl + { + let enum_decl = decl_engine.get_enum(&decl_id); + let enum_ref = DeclRef::new( + enum_decl.call_path.suffix.clone(), + decl_id, + enum_decl.span(), + ); + + for variant_decl in enum_decl.variants.iter() { + let variant_name = &variant_decl.name; + + // import it this way. + let dst_mod = &mut self.module[dst]; + dst_mod.current_items_mut().use_synonyms.insert( + // TODO: No difference made between imported and declared items + variant_name.clone(), + ( + src.to_vec(), + GlobImport::Yes, + TyDecl::EnumVariantDecl(ty::EnumVariantDecl { + enum_ref: enum_ref.clone(), + variant_name: variant_name.clone(), + variant_decl_span: variant_decl.span.clone(), + }), + ), + ); + } + } else { + return Err(handler.emit_err(CompileError::Internal( + "Attempting to import variants of something that isn't an enum", + enum_name.span(), + ))); + } + } + None => { + return Err(handler.emit_err(CompileError::SymbolNotFound { + name: enum_name.clone(), + span: enum_name.span(), + })); + } + }; + + Ok(()) + } + + /// Given a path to a `src` module, create synonyms to every symbol in that module to the given + /// `dst` module. + /// + /// This is used when an import path contains an asterisk. + /// + /// Paths are assumed to be absolute. + pub fn star_import_with_reexports( + &mut self, + handler: &Handler, + engines: &Engines, + src: &Path, + dst: &Path, + ) -> Result<(), ErrorEmitted> { + self.check_module_privacy(handler, src)?; + + let decl_engine = engines.de(); + + let src_mod = self.module.check_submodule(handler, src)?; + + let implemented_traits = src_mod.current_items().implemented_traits.clone(); + let use_synonyms = src_mod.current_items().use_synonyms.clone(); + let mut symbols_and_decls = src_mod + .current_items() + .use_synonyms + .iter() + .map(|(symbol, (_, _, decl))| (symbol.clone(), decl.clone())) + .collect::>(); + for (symbol, decl) in src_mod.current_items().symbols.iter() { + if is_ancestor(src, dst) || decl.visibility(decl_engine).is_public() { + symbols_and_decls.push((symbol.clone(), decl.clone())); + } + } + + let mut symbols_paths_and_decls = vec![]; + for (symbol, (mod_path, _, decl)) in use_synonyms { + let mut is_external = false; + let submodule = src_mod.submodule(&[mod_path[0].clone()]); + if let Some(submodule) = submodule { + is_external = submodule.is_external + }; + + let mut path = src[..1].to_vec(); + if is_external { + path = mod_path; + } else { + path.extend(mod_path); + } + + symbols_paths_and_decls.push((symbol, path, decl)); + } + + let dst_mod = &mut self.module[dst]; + dst_mod + .current_items_mut() + .implemented_traits + .extend(implemented_traits, engines); // TODO: No difference made between imported and declared items + + let mut try_add = |symbol, path, decl: ty::TyDecl| { + dst_mod + .current_items_mut() + .use_synonyms + .insert(symbol, (path, GlobImport::Yes, decl)); // TODO: No difference made between imported and declared items + }; + + for (symbol, decl) in symbols_and_decls { + try_add(symbol, src.to_vec(), decl); + } + + for (symbol, path, decl) in symbols_paths_and_decls { + try_add(symbol, path, decl); + } + + Ok(()) + } + + fn check_module_privacy(&self, handler: &Handler, src: &Path) -> Result<(), ErrorEmitted> { + let dst = &self.module.mod_path; + // you are always allowed to access your ancestor's symbols + if !is_ancestor(src, dst) { + // we don't check the first prefix because direct children are always accessible + for prefix in iter_prefixes(src).skip(1) { + let module = self.module.check_submodule(handler, prefix)?; + if module.visibility.is_private() { + let prefix_last = prefix[prefix.len() - 1].clone(); + handler.emit_err(CompileError::ImportPrivateModule { + span: prefix_last.span(), + name: prefix_last, + }); + } + } + } + Ok(()) + } +} + impl From for Root { fn from(module: Module) -> Self { Root { module } @@ -24,3 +464,7 @@ impl From for Root { namespace.root } } + +fn is_ancestor(src: &Path, dst: &Path) -> bool { + dst.len() >= src.len() && src.iter().zip(dst).all(|(src, dst)| src == dst) +} diff --git a/sway-core/src/semantic_analysis/program.rs b/sway-core/src/semantic_analysis/program.rs index 4244abc3eda..021f7a9fefd 100644 --- a/sway-core/src/semantic_analysis/program.rs +++ b/sway-core/src/semantic_analysis/program.rs @@ -27,7 +27,7 @@ impl TyProgram { handler: &Handler, engines: &Engines, parsed: &ParseProgram, - initial_namespace: namespace::Module, + initial_namespace: namespace::Root, package_name: &str, build_config: Option<&BuildConfig>, ) -> Result { diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index 20a0f2a29a3..43cb1eaad27 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -1317,14 +1317,12 @@ impl<'a> TypeCheckContext<'a> { &mut self, handler: &Handler, src: &Path, - is_absolute: bool, ) -> Result<(), ErrorEmitted> { let engines = self.engines; let mod_path = self.namespace().mod_path.clone(); self.namespace_mut() .root - .module - .star_import(handler, engines, src, &mod_path, is_absolute) + .star_import(handler, engines, src, &mod_path) } /// Short-hand for performing a [Module::variant_star_import] with `mod_path` as the destination. @@ -1333,18 +1331,12 @@ impl<'a> TypeCheckContext<'a> { handler: &Handler, src: &Path, enum_name: &Ident, - is_absolute: bool, ) -> Result<(), ErrorEmitted> { let engines = self.engines; let mod_path = self.namespace().mod_path.clone(); - self.namespace_mut().root.module.variant_star_import( - handler, - engines, - src, - &mod_path, - enum_name, - is_absolute, - ) + self.namespace_mut() + .root + .variant_star_import(handler, engines, src, &mod_path, enum_name) } /// Short-hand for performing a [Module::self_import] with `mod_path` as the destination. @@ -1353,18 +1345,12 @@ impl<'a> TypeCheckContext<'a> { handler: &Handler, src: &Path, alias: Option, - is_absolute: bool, ) -> Result<(), ErrorEmitted> { let engines = self.engines; let mod_path = self.namespace().mod_path.clone(); - self.namespace_mut().root.module.self_import( - handler, - engines, - src, - &mod_path, - alias, - is_absolute, - ) + self.namespace_mut() + .root + .self_import(handler, engines, src, &mod_path, alias) } /// Short-hand for performing a [Module::item_import] with `mod_path` as the destination. @@ -1374,19 +1360,12 @@ impl<'a> TypeCheckContext<'a> { src: &Path, item: &Ident, alias: Option, - is_absolute: bool, ) -> Result<(), ErrorEmitted> { let engines = self.engines; let mod_path = self.namespace().mod_path.clone(); - self.namespace_mut().root.module.item_import( - handler, - engines, - src, - item, - &mod_path, - alias, - is_absolute, - ) + self.namespace_mut() + .root + .item_import(handler, engines, src, item, &mod_path, alias) } /// Short-hand for performing a [Module::variant_import] with `mod_path` as the destination. @@ -1398,11 +1377,10 @@ impl<'a> TypeCheckContext<'a> { enum_name: &Ident, variant_name: &Ident, alias: Option, - is_absolute: bool, ) -> Result<(), ErrorEmitted> { let engines = self.engines; let mod_path = self.namespace().mod_path.clone(); - self.namespace_mut().root.module.variant_import( + self.namespace_mut().root.variant_import( handler, engines, src, @@ -1410,7 +1388,6 @@ impl<'a> TypeCheckContext<'a> { variant_name, &mod_path, alias, - is_absolute, ) } diff --git a/test/src/ir_generation/mod.rs b/test/src/ir_generation/mod.rs index 855282968a9..a723a2efd16 100644 --- a/test/src/ir_generation/mod.rs +++ b/test/src/ir_generation/mod.rs @@ -171,7 +171,12 @@ pub(super) async fn run( // Compile core library and reuse it when compiling tests. let engines = Engines::default(); let build_target = BuildTarget::default(); - let core_lib = compile_core(build_target, &engines, experimental); + let mut core_lib = compile_core(build_target, &engines, experimental); + // Create new initial namespace for every test by reusing the precompiled + // standard libraries. The namespace, thus its root module, must have the + // name set. + const PACKAGE_NAME: &str = "test_lib"; + core_lib.name = Some(sway_types::Ident::new_no_span(PACKAGE_NAME.to_string())); // Find all the tests. let all_tests = discover_test_files(); @@ -226,12 +231,7 @@ pub(super) async fn run( let sway_str = String::from_utf8_lossy(&sway_str); let handler = Handler::default(); - // Create new initial namespace for every test by reusing the precompiled - // standard libraries. The namespace, thus its root module, must have the - // name set. - const PACKAGE_NAME: &str = "test_lib"; - let mut initial_namespace = core_lib.clone(); - initial_namespace.name = Some(sway_types::Ident::new_no_span(PACKAGE_NAME.to_string())); + let initial_namespace = namespace::Root::from(core_lib.clone()); let compile_res = compile_to_ast( &handler, &engines,