Skip to content

Commit

Permalink
Unify namespace import logic for parsed and typed declarations (#6057)
Browse files Browse the repository at this point in the history
## Description

This PR unifies the logic for namespace imports so it works for parsed
and typed declarations.

This allows the soon-to-be-introduced AST resolving pass and collection
context to correctly resolve imports.

## 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] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [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.
  • Loading branch information
tritao authored Jun 17, 2024
1 parent 36033cb commit 6e9130b
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 69 deletions.
15 changes: 9 additions & 6 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1645,17 +1645,20 @@ pub fn dependency_namespace(
if let Some(core_node) = find_core_dep(graph, node) {
let core_namespace = &lib_namespace_map[&core_node];
root_module.insert_submodule(CORE.to_string(), core_namespace.clone());
core_added = true;
}
}

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())),
&[],
);
if core_added {
let _ = root.star_import_with_reexports(
&Handler::default(),
engines,
&[CORE, PRELUDE].map(|s| Ident::new_no_span(s.into())),
&[],
);
}

if has_std_dep(graph, node) {
let _ = root.star_import_with_reexports(
Expand Down
6 changes: 4 additions & 2 deletions sway-core/src/language/parsed/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ impl Declaration {
}
Declaration::StructDeclaration(decl_id) => decl_engine.get_struct(decl_id).visibility,
Declaration::EnumDeclaration(decl_id) => decl_engine.get_enum(decl_id).visibility,
Declaration::EnumVariantDeclaration(decl) => {
decl_engine.get_enum(&decl.enum_ref).visibility
}
Declaration::FunctionDeclaration(decl_id) => {
decl_engine.get_function(decl_id).visibility
}
Expand All @@ -139,8 +142,7 @@ impl Declaration {
| Declaration::ImplSelf(_)
| Declaration::StorageDeclaration(_)
| Declaration::AbiDeclaration(_)
| Declaration::TraitTypeDeclaration(_)
| Declaration::EnumVariantDeclaration(_) => Visibility::Public,
| Declaration::TraitTypeDeclaration(_) => Visibility::Public,
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,16 +535,17 @@ pub fn parsed_to_ast(
// Build the dependency graph for the submodules.
build_module_dep_graph(handler, &mut parse_program.root)?;

let namespace = Namespace::init_root(initial_namespace);
// Collect the program symbols.
let _collection_ctx =
ty::TyProgram::collect(handler, engines, parse_program, initial_namespace.clone())?;
ty::TyProgram::collect(handler, engines, parse_program, namespace.clone())?;

// Type check the program.
let typed_program_opt = ty::TyProgram::type_check(
handler,
engines,
parse_program,
initial_namespace,
namespace,
package_name,
build_config,
);
Expand Down
103 changes: 102 additions & 1 deletion sway-core/src/semantic_analysis/ast_node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ impl ty::TyAstNode {
node: &AstNode,
) -> Result<(), ErrorEmitted> {
match node.content.clone() {
AstNodeContent::UseStatement(_stmt) => {}
AstNodeContent::UseStatement(stmt) => {
collect_use_statement(handler, engines, ctx, &stmt);
}
AstNodeContent::IncludeStatement(_i) => (),
AstNodeContent::Declaration(decl) => ty::TyDecl::collect(handler, engines, ctx, decl)?,
AstNodeContent::Expression(_expr) => (),
Expand Down Expand Up @@ -147,6 +149,8 @@ fn handle_item_trait_imports(
let dst_mod = ctx.namespace.module(engines);

for (_, (_, src, decl)) in dst_mod.current_items().use_item_synonyms.iter() {
let decl = decl.expect_typed_ref();

let src_mod = root_mod.lookup_submodule(handler, engines, src)?;

// if this is an enum or struct or function, import its implementations
Expand Down Expand Up @@ -184,6 +188,103 @@ fn handle_item_trait_imports(
Ok(())
}

fn collect_use_statement(
handler: &Handler,
engines: &Engines,
ctx: &mut SymbolCollectionContext,
stmt: &UseStatement,
) {
let mut is_external = false;
if let Some(submodule) = ctx
.namespace
.module(engines)
.submodule(engines, &[stmt.call_path[0].clone()])
{
is_external |= submodule.read(engines, |m| m.is_external);
}
// We create an inner module for each module being processed during the collection.
// This does not play well with the existing way we use to lookup an external module.
// So check again starting from the root to make sure we find the right module.
// Clean this up once paths are normalized before collection and we can just rely on
// absolute paths.
if let Some(submodule) = ctx
.namespace
.root_module()
.submodule(engines, &[stmt.call_path[0].clone()])
{
is_external |= submodule.read(engines, |m| m.is_external);
}
let path = if is_external || stmt.is_absolute {
stmt.call_path.clone()
} else {
ctx.namespace.prepend_module_path(&stmt.call_path)
};
let _ = match stmt.import_type {
ImportType::Star => {
// try a standard starimport first
let star_import_handler = Handler::default();
let import = ctx.star_import(&star_import_handler, engines, &path);
if import.is_ok() {
handler.append(star_import_handler);
import
} else {
// if it doesn't work it could be an enum star import
if let Some((enum_name, path)) = path.split_last() {
let variant_import_handler = Handler::default();
let variant_import =
ctx.variant_star_import(&variant_import_handler, engines, path, enum_name);
if variant_import.is_ok() {
handler.append(variant_import_handler);
variant_import
} else {
handler.append(star_import_handler);
import
}
} else {
handler.append(star_import_handler);
import
}
}
}
ImportType::SelfImport(_) => ctx.self_import(handler, engines, &path, stmt.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, engines, &path, s, stmt.alias.clone());

if import.is_ok() {
handler.append(item_import_handler);
import
} else {
// if it doesn't work it could be an enum variant import
if let Some((enum_name, path)) = path.split_last() {
let variant_import_handler = Handler::default();
let variant_import = ctx.variant_import(
&variant_import_handler,
engines,
path,
enum_name,
s,
stmt.alias.clone(),
);
if variant_import.is_ok() {
handler.append(variant_import_handler);
variant_import
} else {
handler.append(item_import_handler);
import
}
} else {
handler.append(item_import_handler);
import
}
}
}
};
}

// To be removed once TypeCheckContext is ported to use SymbolCollectionContext.
fn handle_use_statement(
ctx: &mut TypeCheckContext<'_>,
engines: &Engines,
Expand Down
90 changes: 90 additions & 0 deletions sway-core/src/semantic_analysis/collection_context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
language::{parsed::Declaration, Visibility},
namespace::ModulePath,
semantic_analysis::Namespace,
Engines,
};
Expand Down Expand Up @@ -90,4 +91,93 @@ impl SymbolCollectionContext {
)
})
}

/// Returns a mutable reference to the current namespace.
pub fn namespace_mut(&mut self) -> &mut Namespace {
&mut self.namespace
}

/// Returns a reference to the current namespace.
pub fn namespace(&self) -> &Namespace {
&self.namespace
}

/// Short-hand for performing a [Module::star_import] with `mod_path` as the destination.
pub(crate) fn star_import(
&mut self,
handler: &Handler,
engines: &Engines,
src: &ModulePath,
) -> Result<(), ErrorEmitted> {
let mod_path = self.namespace().mod_path.clone();
self.namespace_mut()
.root
.star_import(handler, engines, src, &mod_path)
}

/// Short-hand for performing a [Module::variant_star_import] with `mod_path` as the destination.
pub(crate) fn variant_star_import(
&mut self,
handler: &Handler,
engines: &Engines,
src: &ModulePath,
enum_name: &Ident,
) -> Result<(), ErrorEmitted> {
let mod_path = self.namespace().mod_path.clone();
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.
pub(crate) fn self_import(
&mut self,
handler: &Handler,
engines: &Engines,
src: &ModulePath,
alias: Option<Ident>,
) -> Result<(), ErrorEmitted> {
let mod_path = self.namespace().mod_path.clone();
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.
pub(crate) fn item_import(
&mut self,
handler: &Handler,
engines: &Engines,
src: &ModulePath,
item: &Ident,
alias: Option<Ident>,
) -> Result<(), ErrorEmitted> {
let mod_path = self.namespace().mod_path.clone();
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.
#[allow(clippy::too_many_arguments)]
pub(crate) fn variant_import(
&mut self,
handler: &Handler,
engines: &Engines,
src: &ModulePath,
enum_name: &Ident,
variant_name: &Ident,
alias: Option<Ident>,
) -> Result<(), ErrorEmitted> {
let mod_path = self.namespace().mod_path.clone();
self.namespace_mut().root.variant_import(
handler,
engines,
src,
enum_name,
variant_name,
&mod_path,
alias,
)
}
}
33 changes: 14 additions & 19 deletions sway-core/src/semantic_analysis/namespace/lexical_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ impl ResolvedFunctionDecl {

pub(super) type SymbolMap = im::OrdMap<Ident, ResolvedDeclaration>;
type SourceIdent = Ident;
pub(super) type GlobSynonyms = im::HashMap<Ident, Vec<(ModulePathBuf, ty::TyDecl)>>;
pub(super) type ItemSynonyms = im::HashMap<Ident, (Option<SourceIdent>, ModulePathBuf, ty::TyDecl)>;
pub(super) type GlobSynonyms = im::HashMap<Ident, Vec<(ModulePathBuf, ResolvedDeclaration)>>;
pub(super) type ItemSynonyms =
im::HashMap<Ident, (Option<SourceIdent>, ModulePathBuf, ResolvedDeclaration)>;

/// Represents a lexical scope integer-based identifier, which can be used to reference
/// specific a lexical scope.
Expand Down Expand Up @@ -66,23 +67,17 @@ pub struct Items {
/// An ordered map from `Ident`s to their associated declarations.
pub(crate) symbols: SymbolMap,
pub(crate) implemented_traits: TraitMap,
/// Represents the absolute path from which a symbol was imported.
///
/// For example, in `use ::foo::bar::Baz;`, we store a mapping from the symbol `Baz` to its
/// path `foo::bar::Baz`.
///
/// use_glob_synonyms contains symbols imported using star imports (`use foo::*`.).
/// Contains symbols imported using star imports (`use foo::*`.).
///
/// When star importing from multiple modules the same name may be imported more than once. This
/// is not an error, but it is an error to use the name without a module path. To represent
/// this, use_glob_synonyms maps identifiers to a vector of (module path, type declaration)
/// tuples.
///
/// use_item_synonyms contains symbols imported using item imports (`use foo::bar`).
pub(crate) use_glob_synonyms: GlobSynonyms,
/// Contains symbols imported using item imports (`use foo::bar`).
///
/// For aliased item imports `use ::foo::bar::Baz as Wiz` the map key is `Wiz`. `Baz` is stored
/// as the optional source identifier for error reporting purposes.
pub(crate) use_glob_synonyms: GlobSynonyms,
pub(crate) use_item_synonyms: ItemSynonyms,
/// If there is a storage declaration (which are only valid in contracts), store it here.
pub(crate) declared_storage: Option<DeclRefStorage>,
Expand Down Expand Up @@ -436,11 +431,11 @@ impl Items {
item: &ResolvedDeclaration,
const_shadowing_mode: ConstShadowingMode| {
match (decl, item) {
(ResolvedDeclaration::Parsed(_decl), ResolvedDeclaration::Parsed(_item)) => {
// TODO: Do not handle any shadowing errors while handling parsed declarations yet,
// or else we will emit errors in a different order from the source code order.
// Update this once the full AST resolving pass is in.
}
// TODO: Do not handle any shadowing errors while handling parsed declarations yet,
// or else we will emit errors in a different order from the source code order.
// Update this once the full AST resolving pass is in.
(ResolvedDeclaration::Typed(_decl), ResolvedDeclaration::Parsed(_item)) => {}
(ResolvedDeclaration::Parsed(_decl), ResolvedDeclaration::Parsed(_item)) => {}
(ResolvedDeclaration::Typed(decl), ResolvedDeclaration::Typed(item)) => {
append_shadowing_error_typed(
ident,
Expand Down Expand Up @@ -469,12 +464,12 @@ impl Items {
if let Some((ident, (imported_ident, _, decl))) =
self.use_item_synonyms.get_key_value(&name)
{
append_shadowing_error_typed(
append_shadowing_error(
ident,
decl,
true,
imported_ident.is_some(),
item.expect_typed_ref(),
&item,
const_shadowing_mode,
);
}
Expand All @@ -495,7 +490,7 @@ impl Items {
engines: &Engines,
symbol: Ident,
src_path: ModulePathBuf,
decl: &ty::TyDecl,
decl: &ResolvedDeclaration,
) {
if let Some(cur_decls) = self.use_glob_synonyms.get_mut(&symbol) {
// Name already bound. Check if the decl is already imported
Expand Down
Loading

0 comments on commit 6e9130b

Please sign in to comment.