Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Split contract-specific namespace module code into its own file. #5948

Merged
merged 1 commit into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 2 additions & 7 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1596,12 +1596,7 @@ pub fn dependency_namespace(
let node_idx = &graph[node];
let name = Some(Ident::new_no_span(node_idx.name.clone()));
let mut root_module = if let Some(contract_id_value) = contract_id_value {
namespace::Module::default_with_contract_id(
engines,
name.clone(),
contract_id_value,
experimental,
)?
namespace::default_with_contract_id(engines, name.clone(), contract_id_value, experimental)?
} else {
namespace::Module::default()
};
Expand Down Expand Up @@ -1634,7 +1629,7 @@ pub fn dependency_namespace(
let contract_id_value = format!("0x{dep_contract_id}");
let node_idx = &graph[dep_node];
let name = Some(Ident::new_no_span(node_idx.name.clone()));
let mut module = namespace::Module::default_with_contract_id(
let mut module = namespace::default_with_contract_id(
engines,
name.clone(),
contract_id_value,
Expand Down
125 changes: 125 additions & 0 deletions sway-core/src/semantic_analysis/namespace/contract_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use sway_ast::ItemConst;
use sway_error::{
error::CompileError,
handler::{ErrorEmitted, Handler},
};
use sway_parse::{lex, Parser};
use sway_types::{Ident, Spanned};

use crate::{
language::{
parsed::{AstNode, AstNodeContent, Declaration, ExpressionKind},
ty::{TyAstNode, TyAstNodeContent},
Visibility,
},
semantic_analysis::TypeCheckContext,
transform::to_parsed_lang,
Engines, Namespace,
};

use super::{lexical_scope::SymbolMap, Module, Root};

/// `contract_id_value` is injected here via forc-pkg when producing the `dependency_namespace` for a contract which has tests enabled.
/// This allows us to provide a contract's `CONTRACT_ID` constant to its own unit tests.
///
/// This will eventually be refactored out of `sway-core` in favor of creating temporary package dependencies for providing these
/// `CONTRACT_ID`-containing modules: https://github.com/FuelLabs/sway/issues/3077
pub fn default_with_contract_id(
engines: &Engines,
name: Option<Ident>,
contract_id_value: String,
experimental: crate::ExperimentalFlags,
) -> Result<Module, vec1::Vec1<CompileError>> {
let handler = <_>::default();
default_with_contract_id_inner(&handler, engines, name, contract_id_value, experimental)
.map_err(|_| {
let (errors, warnings) = handler.consume();
assert!(warnings.is_empty());

// Invariant: `.value == None` => `!errors.is_empty()`.
vec1::Vec1::try_from_vec(errors).unwrap()
})
}

fn default_with_contract_id_inner(
handler: &Handler,
engines: &Engines,
ns_name: Option<Ident>,
contract_id_value: String,
experimental: crate::ExperimentalFlags,
) -> Result<Module, ErrorEmitted> {
// it would be nice to one day maintain a span from the manifest file, but
// we don't keep that around so we just use the span from the generated const decl instead.
let mut compiled_constants: SymbolMap = Default::default();
// this for loop performs a miniature compilation of each const item in the config
// FIXME(Centril): Stop parsing. Construct AST directly instead!
// parser config
let const_item = format!("pub const CONTRACT_ID: b256 = {contract_id_value};");
let const_item_len = const_item.len();
let input_arc = std::sync::Arc::from(const_item);
let token_stream = lex(handler, &input_arc, 0, const_item_len, None).unwrap();
let mut parser = Parser::new(handler, &token_stream);
// perform the parse
let const_item: ItemConst = parser.parse()?;
let const_item_span = const_item.span();

// perform the conversions from parser code to parse tree types
let name = const_item.name.clone();
let attributes = Default::default();
// convert to const decl
let const_decl_id = to_parsed_lang::item_const_to_constant_declaration(
&mut to_parsed_lang::Context::new(crate::BuildTarget::EVM, experimental),
handler,
engines,
const_item,
attributes,
true,
)?;

// Temporarily disallow non-literals. See https://github.com/FuelLabs/sway/issues/2647.
let const_decl = engines.pe().get_constant(&const_decl_id);
let has_literal = match &const_decl.value {
Some(value) => {
matches!(value.kind, ExpressionKind::Literal(_))
}
None => false,
};

if !has_literal {
return Err(handler.emit_err(CompileError::ContractIdValueNotALiteral {
span: const_item_span,
}));
}

let ast_node = AstNode {
content: AstNodeContent::Declaration(Declaration::ConstantDeclaration(const_decl_id)),
span: const_item_span.clone(),
};
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;
ns.root.module.visibility = Visibility::Public;
let type_check_ctx = TypeCheckContext::from_namespace(&mut ns, engines, experimental);
let typed_node = TyAstNode::type_check(handler, type_check_ctx, &ast_node).unwrap();
// get the decl out of the typed node:
// we know as an invariant this must be a const decl, as we hardcoded a const decl in
// the above `format!`. if it isn't we report an
// error that only constant items are allowed, defensive programming etc...
let typed_decl = match typed_node.content {
TyAstNodeContent::Declaration(decl) => decl,
_ => {
return Err(
handler.emit_err(CompileError::ContractIdConstantNotAConstDecl {
span: const_item_span,
}),
);
}
};
compiled_constants.insert(name, typed_decl);

let mut ret = Module::default();
ret.current_lexical_scope_mut().items.symbols = compiled_constants;
Ok(ret)
}
2 changes: 2 additions & 0 deletions sway-core/src/semantic_analysis/namespace/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod contract_helpers;
mod lexical_scope;
mod module;
#[allow(clippy::module_inception)]
Expand All @@ -6,6 +7,7 @@ mod root;
mod submodule_namespace;
mod trait_map;

pub use contract_helpers::*;
pub use lexical_scope::{Items, LexicalScope, LexicalScopeId, LexicalScopePath};
pub use module::Module;
pub use namespace::Namespace;
Expand Down
125 changes: 3 additions & 122 deletions sway-core/src/semantic_analysis/namespace/module.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
use crate::{
engine_threading::Engines,
language::{parsed::*, ty, Visibility},
semantic_analysis::*,
transform::to_parsed_lang,
Ident, Namespace,
};
use crate::{engine_threading::Engines, language::Visibility, Ident};

use super::{
lexical_scope::{Items, LexicalScope, SymbolMap},
lexical_scope::{Items, LexicalScope},
root::Root,
LexicalScopeId, ModuleName, ModulePath, ModulePathBuf,
};

use sway_ast::ItemConst;
use sway_error::handler::Handler;
use sway_error::{error::CompileError, handler::ErrorEmitted};
use sway_parse::{lex, Parser};
use sway_types::{span::Span, Spanned};

/// A single `Module` within a Sway project.
Expand Down Expand Up @@ -93,117 +85,6 @@ impl Module {
self.mod_path.clone()
}

/// `contract_id_value` is injected here via forc-pkg when producing the `dependency_namespace` for a contract which has tests enabled.
/// This allows us to provide a contract's `CONTRACT_ID` constant to its own unit tests.
///
/// This will eventually be refactored out of `sway-core` in favor of creating temporary package dependencies for providing these
/// `CONTRACT_ID`-containing modules: https://github.com/FuelLabs/sway/issues/3077
pub fn default_with_contract_id(
engines: &Engines,
name: Option<Ident>,
contract_id_value: String,
experimental: crate::ExperimentalFlags,
) -> Result<Self, vec1::Vec1<CompileError>> {
let handler = <_>::default();
Module::default_with_contract_id_inner(
&handler,
engines,
name,
contract_id_value,
experimental,
)
.map_err(|_| {
let (errors, warnings) = handler.consume();
assert!(warnings.is_empty());

// Invariant: `.value == None` => `!errors.is_empty()`.
vec1::Vec1::try_from_vec(errors).unwrap()
})
}

fn default_with_contract_id_inner(
handler: &Handler,
engines: &Engines,
ns_name: Option<Ident>,
contract_id_value: String,
experimental: crate::ExperimentalFlags,
) -> Result<Self, ErrorEmitted> {
// it would be nice to one day maintain a span from the manifest file, but
// we don't keep that around so we just use the span from the generated const decl instead.
let mut compiled_constants: SymbolMap = Default::default();
// this for loop performs a miniature compilation of each const item in the config
// FIXME(Centril): Stop parsing. Construct AST directly instead!
// parser config
let const_item = format!("pub const CONTRACT_ID: b256 = {contract_id_value};");
let const_item_len = const_item.len();
let input_arc = std::sync::Arc::from(const_item);
let token_stream = lex(handler, &input_arc, 0, const_item_len, None).unwrap();
let mut parser = Parser::new(handler, &token_stream);
// perform the parse
let const_item: ItemConst = parser.parse()?;
let const_item_span = const_item.span();

// perform the conversions from parser code to parse tree types
let name = const_item.name.clone();
let attributes = Default::default();
// convert to const decl
let const_decl_id = to_parsed_lang::item_const_to_constant_declaration(
&mut to_parsed_lang::Context::new(crate::BuildTarget::EVM, experimental),
handler,
engines,
const_item,
attributes,
true,
)?;

// Temporarily disallow non-literals. See https://github.com/FuelLabs/sway/issues/2647.
let const_decl = engines.pe().get_constant(&const_decl_id);
let has_literal = match &const_decl.value {
Some(value) => {
matches!(value.kind, ExpressionKind::Literal(_))
}
None => false,
};

if !has_literal {
return Err(handler.emit_err(CompileError::ContractIdValueNotALiteral {
span: const_item_span,
}));
}

let ast_node = AstNode {
content: AstNodeContent::Declaration(Declaration::ConstantDeclaration(const_decl_id)),
span: const_item_span.clone(),
};
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;
ns.root.module.visibility = Visibility::Public;
let type_check_ctx = TypeCheckContext::from_namespace(&mut ns, engines, experimental);
let typed_node = ty::TyAstNode::type_check(handler, type_check_ctx, &ast_node).unwrap();
// get the decl out of the typed node:
// we know as an invariant this must be a const decl, as we hardcoded a const decl in
// the above `format!`. if it isn't we report an
// error that only constant items are allowed, defensive programming etc...
let typed_decl = match typed_node.content {
ty::TyAstNodeContent::Declaration(decl) => decl,
_ => {
return Err(
handler.emit_err(CompileError::ContractIdConstantNotAConstDecl {
span: const_item_span,
}),
);
}
};
compiled_constants.insert(name, typed_decl);

let mut ret = Self::default();
ret.current_lexical_scope_mut().items.symbols = compiled_constants;
Ok(ret)
}

/// Immutable access to this module's submodules.
pub fn submodules(&self) -> &im::OrdMap<ModuleName, Module> {
&self.submodules
Expand Down Expand Up @@ -276,7 +157,7 @@ impl Module {
}

/// Returns the mutable current lexical scope associated with this module.
fn current_lexical_scope_mut(&mut self) -> &mut LexicalScope {
pub fn current_lexical_scope_mut(&mut self) -> &mut LexicalScope {
self.lexical_scopes
.get_mut(self.current_lexical_scope_id)
.unwrap()
Expand Down