From 1ecc5e79d6d5a400b365235b2bccd9ea4a79ef6c Mon Sep 17 00:00:00 2001 From: IGI-111 Date: Fri, 28 Apr 2023 11:30:53 +0200 Subject: [PATCH] Implement module privacy rules (#4474) ## Description This change mainly adds checks to enforce the new module privacy rules and supporting changes for it. Changes include updating std and core to use public modules, updating the parser to allow the use of the `pub mod` syntax and adding an error type for private modules. This change is implemented behind a `--experimental-private-modules` experimental flag and not enabled by default. It implements part of https://github.com/FuelLabs/sway/issues/4446, the `pub use` syntax is yet to be implemented. ## 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: Joshua Batty --- forc-pkg/src/manifest.rs | 3 + forc-pkg/src/pkg.rs | 18 ++- forc-plugins/forc-client/src/op/deploy.rs | 1 + forc-plugins/forc-client/src/op/run.rs | 1 + forc-plugins/forc-doc/src/main.rs | 1 + forc-test/src/lib.rs | 3 + forc/src/cli/commands/check.rs | 3 + forc/src/cli/commands/test.rs | 1 + forc/src/cli/shared.rs | 2 + forc/src/ops/forc_build.rs | 1 + forc/src/ops/forc_check.rs | 10 +- forc/src/ops/forc_contract_id.rs | 1 + forc/src/ops/forc_predicate_root.rs | 1 + sway-ast/src/submodule.rs | 1 + sway-core/src/build_config.rs | 9 ++ sway-core/src/language/parsed/module.rs | 6 +- sway-core/src/lib.rs | 26 +++- .../ast_node/declaration/trait.rs | 2 +- .../ast_node/expression/typed_expression.rs | 1 + .../typed_expression/method_application.rs | 3 + .../src/semantic_analysis/ast_node/mod.rs | 34 +++-- sway-core/src/semantic_analysis/module.rs | 3 +- .../src/semantic_analysis/namespace/module.rs | 130 +++++++++++++++++- .../semantic_analysis/namespace/namespace.rs | 95 +++++++++++-- .../src/semantic_analysis/namespace/root.rs | 60 +++++--- sway-core/src/semantic_analysis/program.rs | 6 +- .../semantic_analysis/type_check_context.rs | 37 ++++- .../src/type_system/ast_elements/binding.rs | 24 +++- sway-core/src/type_system/engine.rs | 26 +++- sway-error/src/error.rs | 3 + sway-error/src/warning.rs | 4 + sway-lib-core/src/lib.sw | 14 +- sway-lib-std/src/experimental.sw | 2 +- sway-lib-std/src/lib.sw | 76 +++++----- sway-lib-std/src/storage.sw | 12 +- sway-lib-std/src/vm.sw | 2 +- sway-lib-std/src/vm/evm.sw | 4 +- sway-lsp/src/core/session.rs | 1 + sway-lsp/src/traverse/typed_tree.rs | 5 +- sway-parse/src/item/mod.rs | 3 +- sway-parse/src/submodule.rs | 2 + sway-utils/src/helpers.rs | 17 +++ test/src/e2e_vm_tests/harness.rs | 1 + .../should_fail/dep_annotated/test.toml | 3 - .../enum_variant_unit/src/lib_a.sw | 2 +- .../generics_not_supported/src/lib_a.sw | 2 +- .../should_fail/module_privacy/Forc.lock | 13 ++ .../should_fail/module_privacy/Forc.toml | 8 ++ .../should_fail/module_privacy/src/alpha.sw | 4 + .../should_fail/module_privacy/src/beta.sw | 10 ++ .../module_privacy/src/beta/gamma.sw | 4 + .../should_fail/module_privacy/src/main.sw | 47 +++++++ .../should_fail/module_privacy/test.toml | 31 +++++ .../generic_impl_self_where/src/traits.sw | 2 +- .../language/multi_item_import/src/bar.sw | 2 +- .../primitive_type_argument/src/foo.sw | 2 +- .../primitive_type_argument/src/foo/bar.sw | 2 +- .../language/same_const_name/src/pkga.sw | 2 +- .../language/same_const_name_lib/src/main.sw | 2 +- .../language/same_const_name_lib/src/pkga.sw | 2 +- test/src/ir_generation/mod.rs | 1 + 61 files changed, 647 insertions(+), 147 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/alpha.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta/gamma.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/test.toml diff --git a/forc-pkg/src/manifest.rs b/forc-pkg/src/manifest.rs index 6ebc728d15f..9aefd9cb076 100644 --- a/forc-pkg/src/manifest.rs +++ b/forc-pkg/src/manifest.rs @@ -214,6 +214,7 @@ pub struct BuildProfile { pub include_tests: bool, pub json_abi_with_callpaths: bool, pub error_on_warnings: bool, + pub experimental_private_modules: bool, } impl Dependency { @@ -657,6 +658,7 @@ impl BuildProfile { include_tests: false, json_abi_with_callpaths: false, error_on_warnings: false, + experimental_private_modules: false, } } @@ -673,6 +675,7 @@ impl BuildProfile { include_tests: false, json_abi_with_callpaths: false, error_on_warnings: false, + experimental_private_modules: false, } } } diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 1f79918a868..2451a4ce192 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -39,7 +39,7 @@ use sway_core::{ language::{ lexed::LexedProgram, parsed::{ParseProgram, TreeType}, - ty, + ty, Visibility, }, semantic_analysis::namespace, source_map::SourceMap, @@ -302,6 +302,8 @@ pub struct BuildOpts { pub tests: bool, /// The set of options to filter by member project kind. pub member_filter: MemberFilter, + /// Enable the experimental module privacy enforcement. + pub experimental_private_modules: bool, } /// The set of options to filter type of projects to build in a workspace. @@ -1544,7 +1546,8 @@ pub fn sway_build_config( .print_finalized_asm(build_profile.print_finalized_asm) .print_intermediate_asm(build_profile.print_intermediate_asm) .print_ir(build_profile.print_ir) - .include_tests(build_profile.include_tests); + .include_tests(build_profile.include_tests) + .experimental_private_modules(build_profile.experimental_private_modules); Ok(build_config) } @@ -1570,6 +1573,7 @@ pub fn dependency_namespace( node: NodeIx, engines: Engines<'_>, contract_id_value: Option, + experimental_private_modules: bool, ) -> Result> { // TODO: Clean this up when config-time constants v1 are removed. let node_idx = &graph[node]; @@ -1582,6 +1586,7 @@ pub fn dependency_namespace( namespace.is_external = true; namespace.name = name; + namespace.visibility = Visibility::Public; // Add direct dependencies. let mut core_added = false; @@ -1611,6 +1616,7 @@ pub fn dependency_namespace( )?; ns.is_external = true; ns.name = name; + ns.visibility = Visibility::Public; ns } }; @@ -1633,6 +1639,7 @@ pub fn dependency_namespace( &[CORE, PRELUDE].map(|s| Ident::new_no_span(s.into())), &[], engines, + experimental_private_modules, ); if has_std_dep(graph, node) { @@ -1640,6 +1647,7 @@ pub fn dependency_namespace( &[STD, PRELUDE].map(|s| Ident::new_no_span(s.into())), &[], engines, + experimental_private_modules, ); } @@ -1986,6 +1994,7 @@ fn build_profile_from_opts( time_phases, tests, error_on_warnings, + experimental_private_modules, .. } = build_options; let mut selected_build_profile = BuildProfile::DEBUG; @@ -2036,6 +2045,7 @@ fn build_profile_from_opts( profile.include_tests |= tests; profile.json_abi_with_callpaths |= pkg.json_abi_with_callpaths; profile.error_on_warnings |= error_on_warnings; + profile.experimental_private_modules |= experimental_private_modules; Ok((selected_build_profile.to_string(), profile)) } @@ -2268,6 +2278,7 @@ pub fn build( node, engines, None, + profile.experimental_private_modules, ) { Ok(o) => o, Err(errs) => return fail(&[], &errs), @@ -2323,6 +2334,7 @@ pub fn build( node, engines, contract_id_value.clone(), + profile.experimental_private_modules, ) { Ok(o) => o, Err(errs) => return fail(&[], &errs), @@ -2506,6 +2518,7 @@ pub fn check( terse_mode: bool, include_tests: bool, engines: Engines<'_>, + experimental_private_modules: bool, ) -> anyhow::Result>> { let mut lib_namespace_map = Default::default(); let mut source_map = SourceMap::new(); @@ -2523,6 +2536,7 @@ pub fn check( node, engines, None, + experimental_private_modules, ) .expect("failed to create dependency namespace"); diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index 0037820044d..88849f83b3a 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -255,6 +255,7 @@ fn build_opts_from_cmd(cmd: &cmd::Deploy) -> pkg::BuildOpts { build_target: BuildTarget::default(), tests: false, member_filter: pkg::MemberFilter::only_contracts(), + experimental_private_modules: cmd.build_profile.experimental_private_modules, } } diff --git a/forc-plugins/forc-client/src/op/run.rs b/forc-plugins/forc-client/src/op/run.rs index fd7e828d372..546a89e0763 100644 --- a/forc-plugins/forc-client/src/op/run.rs +++ b/forc-plugins/forc-client/src/op/run.rs @@ -176,5 +176,6 @@ fn build_opts_from_cmd(cmd: &cmd::Run) -> pkg::BuildOpts { debug_outfile: cmd.build_output.debug_file.clone(), tests: false, member_filter: pkg::MemberFilter::only_scripts(), + experimental_private_modules: cmd.build_profile.experimental_private_modules, } } diff --git a/forc-plugins/forc-doc/src/main.rs b/forc-plugins/forc-doc/src/main.rs index f493e08d110..a67b40570e2 100644 --- a/forc-plugins/forc-doc/src/main.rs +++ b/forc-plugins/forc-doc/src/main.rs @@ -88,6 +88,7 @@ pub fn main() -> Result<()> { silent, tests_enabled, engines, + true, )? .pop() .and_then(|compilation| compilation.value) diff --git a/forc-test/src/lib.rs b/forc-test/src/lib.rs index a67b312b1eb..9b3eebda93c 100644 --- a/forc-test/src/lib.rs +++ b/forc-test/src/lib.rs @@ -151,6 +151,8 @@ pub struct Opts { pub error_on_warnings: bool, /// Output the time elapsed over each part of the compilation process. pub time_phases: bool, + /// Enable the experimental module privacy enforcement. + pub experimental_private_modules: bool, } /// The set of options provided for controlling logs printed for each test. @@ -480,6 +482,7 @@ impl Opts { time_phases: self.time_phases, tests: true, member_filter: Default::default(), + experimental_private_modules: self.experimental_private_modules, } } } diff --git a/forc/src/cli/commands/check.rs b/forc/src/cli/commands/check.rs index 6ec7349d498..eedf4479c51 100644 --- a/forc/src/cli/commands/check.rs +++ b/forc/src/cli/commands/check.rs @@ -29,6 +29,9 @@ pub struct Command { /// Disable checking unit tests. #[clap(long = "disable-tests")] pub disable_tests: bool, + /// Enable the experimental module privacy enforcement. + #[clap(long)] + pub experimental_private_modules: bool, } pub(crate) fn exec(command: Command) -> ForcResult<()> { diff --git a/forc/src/cli/commands/test.rs b/forc/src/cli/commands/test.rs index 2b7eea02542..1549282f410 100644 --- a/forc/src/cli/commands/test.rs +++ b/forc/src/cli/commands/test.rs @@ -199,5 +199,6 @@ fn opts_from_cmd(cmd: Command) -> forc_test::Opts { binary_outfile: cmd.build.output.bin_file, debug_outfile: cmd.build.output.debug_file, build_target: cmd.build.build_target, + experimental_private_modules: cmd.build.profile.experimental_private_modules, } } diff --git a/forc/src/cli/shared.rs b/forc/src/cli/shared.rs index ab8acd2b064..4c3cf350cb3 100644 --- a/forc/src/cli/shared.rs +++ b/forc/src/cli/shared.rs @@ -49,6 +49,8 @@ pub struct BuildProfile { /// Treat warnings as errors. #[clap(long)] pub error_on_warnings: bool, + #[clap(long)] + pub experimental_private_modules: bool, } /// Options related to printing stages of compiler output. diff --git a/forc/src/ops/forc_build.rs b/forc/src/ops/forc_build.rs index bde935b2b7f..548908ef675 100644 --- a/forc/src/ops/forc_build.rs +++ b/forc/src/ops/forc_build.rs @@ -39,5 +39,6 @@ fn opts_from_cmd(cmd: BuildCommand) -> pkg::BuildOpts { build_target: cmd.build.build_target, tests: cmd.tests, member_filter: Default::default(), + experimental_private_modules: cmd.build.profile.experimental_private_modules, } } diff --git a/forc/src/ops/forc_check.rs b/forc/src/ops/forc_check.rs index 3dd7cd81ad3..ccd0683cd2a 100644 --- a/forc/src/ops/forc_check.rs +++ b/forc/src/ops/forc_check.rs @@ -13,6 +13,7 @@ pub fn check(command: CheckCommand, engines: Engines<'_>) -> Result) -> Result pkg::BuildOpts { build_target: BuildTarget::default(), tests: false, member_filter: pkg::MemberFilter::only_contracts(), + experimental_private_modules: cmd.build_profile.experimental_private_modules, } } diff --git a/forc/src/ops/forc_predicate_root.rs b/forc/src/ops/forc_predicate_root.rs index 166ade58970..3c2bf661bb0 100644 --- a/forc/src/ops/forc_predicate_root.rs +++ b/forc/src/ops/forc_predicate_root.rs @@ -43,5 +43,6 @@ fn build_opts_from_cmd(cmd: PredicateRootCommand) -> pkg::BuildOpts { build_target: BuildTarget::default(), tests: false, member_filter: pkg::MemberFilter::only_predicates(), + experimental_private_modules: cmd.build_profile.experimental_private_modules, } } diff --git a/sway-ast/src/submodule.rs b/sway-ast/src/submodule.rs index a80dd36c994..b812a6de076 100644 --- a/sway-ast/src/submodule.rs +++ b/sway-ast/src/submodule.rs @@ -5,6 +5,7 @@ pub struct Submodule { pub mod_token: ModToken, pub name: Ident, pub semicolon_token: SemicolonToken, + pub visibility: Option, } impl Spanned for Submodule { diff --git a/sway-core/src/build_config.rs b/sway-core/src/build_config.rs index a387ad839a4..970dc23ad45 100644 --- a/sway-core/src/build_config.rs +++ b/sway-core/src/build_config.rs @@ -46,6 +46,7 @@ pub struct BuildConfig { pub(crate) print_finalized_asm: bool, pub(crate) print_ir: bool, pub(crate) include_tests: bool, + pub(crate) experimental_private_modules: bool, } impl BuildConfig { @@ -88,6 +89,7 @@ impl BuildConfig { print_finalized_asm: false, print_ir: false, include_tests: false, + experimental_private_modules: false, } } @@ -126,6 +128,13 @@ impl BuildConfig { } } + pub fn experimental_private_modules(self, a: bool) -> Self { + Self { + experimental_private_modules: a, + ..self + } + } + /// Whether or not to include test functions in parsing, type-checking and codegen. /// /// This should be set to `true` by invocations like `forc test` or `forc check --tests`. diff --git a/sway-core/src/language/parsed/module.rs b/sway-core/src/language/parsed/module.rs index 17f2114351c..777bf28f694 100644 --- a/sway-core/src/language/parsed/module.rs +++ b/sway-core/src/language/parsed/module.rs @@ -1,4 +1,7 @@ -use crate::{language::ModName, transform}; +use crate::{ + language::{ModName, Visibility}, + transform, +}; use super::ParseTree; use sway_types::Span; @@ -22,4 +25,5 @@ pub struct ParseModule { pub struct ParseSubmodule { pub module: ParseModule, pub mod_name_span: Span, + pub visibility: Visibility, } diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index 17b407dd1df..01d6d9c874f 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -48,11 +48,11 @@ pub mod types; pub use error::CompileResult; use sway_error::error::CompileError; -use sway_error::warning::CompileWarning; +use sway_error::warning::{CompileWarning, Warning}; use sway_types::{ident::Ident, span, Spanned}; pub use type_system::*; -use language::{lexed, parsed, ty}; +use language::{lexed, parsed, ty, Visibility}; use transform::to_parsed_lang::{self, convert_module_kind}; pub mod fuel_prelude { @@ -244,6 +244,10 @@ fn parse_submodules( let parse_submodule = parsed::ParseSubmodule { module: parse_module, + visibility: match submod.visibility { + Some(..) => Visibility::Public, + None => Visibility::Private, + }, mod_name_span: submod.name.span(), }; let lexed_submodule = lexed::LexedSubmodule { @@ -334,12 +338,28 @@ pub fn parsed_to_ast( build_config: Option<&BuildConfig>, package_name: &str, ) -> CompileResult { + let experimental_private_modules = + build_config.map_or(true, |b| b.experimental_private_modules); // Type check the program. let CompileResult { value: typed_program_opt, mut warnings, mut errors, - } = ty::TyProgram::type_check(engines, parse_program, initial_namespace, package_name); + } = ty::TyProgram::type_check( + engines, + parse_program, + initial_namespace, + package_name, + experimental_private_modules, + ); + + if !experimental_private_modules { + warnings.push(CompileWarning { + span: parse_program.root.span.clone(), + warning_content: Warning::ModulePrivacyDisabled, + }) + } + let mut typed_program = match typed_program_opt { Some(typed_program) => typed_program, None => return err(warnings, errors), diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs index fab29636cac..afbba427498 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs @@ -158,7 +158,7 @@ impl ty::TyTraitDecl { let mut new_items = vec![]; for method in methods.into_iter() { let method = check!( - ty::TyFunctionDecl::type_check(ctx.by_ref(), method.clone(), true, false), + ty::TyFunctionDecl::type_check(ctx.by_ref(), method.clone(), true, false,), ty::TyFunctionDecl::error(method), warnings, errors 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 b93567ef14c..a91f5207898 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 @@ -1353,6 +1353,7 @@ impl ty::TyExpression { &suffix, ctx.self_type(), ctx.engines(), + ctx.experimental_private_modules_enabled() ), return None, const_probe_warnings, diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 6d82fda8af2..0b60ddab34c 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -440,6 +440,7 @@ pub(crate) fn resolve_method_name( ctx.self_type(), &arguments, engines, + ctx.experimental_private_modules_enabled() ), return err(warnings, errors), warnings, @@ -467,6 +468,7 @@ pub(crate) fn resolve_method_name( ctx.self_type(), &arguments, engines, + ctx.experimental_private_modules_enabled(), ), return err(warnings, errors), warnings, @@ -494,6 +496,7 @@ pub(crate) fn resolve_method_name( ctx.self_type(), &arguments, engines, + ctx.experimental_private_modules_enabled(), ), return err(warnings, errors), warnings, diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index 8e7627db7c3..1e2146e2ec7 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -38,14 +38,22 @@ impl ty::TyAstNode { let mut res = match a.import_type { ImportType::Star => { // try a standard starimport first - let import = ctx.namespace.star_import(&path, engines); + let import = ctx.namespace.star_import( + &path, + engines, + ctx.experimental_private_modules_enabled(), + ); if import.is_ok() { 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 = - ctx.namespace.variant_star_import(path, engines, enum_name); + let variant_import = ctx.namespace.variant_star_import( + path, + engines, + enum_name, + ctx.experimental_private_modules_enabled(), + ); if variant_import.is_ok() { variant_import } else { @@ -56,14 +64,21 @@ impl ty::TyAstNode { } } } - ImportType::SelfImport(_) => { - ctx.namespace.self_import(engines, &path, a.alias.clone()) - } + ImportType::SelfImport(_) => ctx.namespace.self_import( + engines, + &path, + a.alias.clone(), + ctx.experimental_private_modules_enabled(), + ), ImportType::Item(ref s) => { // try a standard item import first - let import = - ctx.namespace - .item_import(engines, &path, s, a.alias.clone()); + let import = ctx.namespace.item_import( + engines, + &path, + s, + a.alias.clone(), + ctx.experimental_private_modules_enabled(), + ); if import.is_ok() { import @@ -76,6 +91,7 @@ impl ty::TyAstNode { enum_name, s, a.alias.clone(), + ctx.experimental_private_modules_enabled(), ); if variant_import.is_ok() { variant_import diff --git a/sway-core/src/semantic_analysis/module.rs b/sway-core/src/semantic_analysis/module.rs index 38001340257..9c323a2e947 100644 --- a/sway-core/src/semantic_analysis/module.rs +++ b/sway-core/src/semantic_analysis/module.rs @@ -72,8 +72,9 @@ impl ty::TySubmodule { let ParseSubmodule { module, mod_name_span, + visibility, } = submodule; - parent_ctx.enter_submodule(mod_name, module.span.clone(), |submod_ctx| { + parent_ctx.enter_submodule(mod_name, *visibility, module.span.clone(), |submod_ctx| { let module_res = ty::TyModule::type_check(submod_ctx, module); module_res.map(|module| ty::TySubmodule { module, diff --git a/sway-core/src/semantic_analysis/namespace/module.rs b/sway-core/src/semantic_analysis/namespace/module.rs index 1a02b169081..b34ec0a1649 100644 --- a/sway-core/src/semantic_analysis/namespace/module.rs +++ b/sway-core/src/semantic_analysis/namespace/module.rs @@ -5,6 +5,7 @@ use crate::{ language::{ parsed::*, ty::{self, TyDecl}, + Visibility, }, semantic_analysis::*, transform::to_parsed_lang, @@ -23,6 +24,7 @@ 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. /// @@ -32,7 +34,7 @@ use sway_types::{span::Span, Spanned}; /// /// A `Module` contains a set of all items that exist within the lexical scope via declaration or /// importing, along with a map of each of its submodules. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Module { /// Submodules of the current module represented as an ordered map from each submodule's name /// to the associated `Module`. @@ -47,6 +49,8 @@ pub struct Module { /// Name of the module, package name for root module, module name for other modules. /// Module name used is the same as declared in `mod name;`. pub name: Option, + /// Whether or not this is a `pub` module + pub visibility: Visibility, /// Empty span at the beginning of the file implementing the module pub span: Option, /// Indicates whether the module is external to the current package. External modules are @@ -54,6 +58,19 @@ pub struct Module { pub is_external: bool, } +impl Default for Module { + fn default() -> Self { + Self { + visibility: Visibility::Private, + submodules: Default::default(), + items: Default::default(), + name: Default::default(), + span: Default::default(), + is_external: Default::default(), + } + } +} + impl Module { /// `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. @@ -133,6 +150,7 @@ impl Module { // 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_root(&mut ns, engines); let typed_node = ty::TyAstNode::type_check(type_check_ctx, ast_node).unwrap(&mut vec![], &mut vec![]); @@ -212,10 +230,18 @@ impl Module { src: &Path, dst: &Path, engines: Engines<'_>, + experimental_private_modules: bool, ) -> CompileResult<()> { let mut warnings = vec![]; let mut errors = vec![]; + check!( + self.check_module_privacy(src, dst, experimental_private_modules), + return err(warnings, errors), + warnings, + errors + ); + let decl_engine = engines.de(); let src_ns = check!( @@ -228,7 +254,9 @@ impl Module { let implemented_traits = src_ns.implemented_traits.clone(); let mut symbols_and_decls = vec![]; for (symbol, decl) in src_ns.symbols.iter() { - if decl.visibility(decl_engine).is_public() { + if is_ancestor(src, dst, experimental_private_modules) + || decl.visibility(decl_engine).is_public() + { symbols_and_decls.push((symbol.clone(), decl.clone())); } } @@ -258,10 +286,18 @@ impl Module { src: &Path, dst: &Path, engines: Engines<'_>, + experimental_private_modules: bool, ) -> CompileResult<()> { let mut warnings = vec![]; let mut errors = vec![]; + check!( + self.check_module_privacy(src, dst, experimental_private_modules), + return err(warnings, errors), + warnings, + errors + ); + let decl_engine = engines.de(); let src_ns = check!( @@ -279,7 +315,9 @@ impl Module { .map(|(symbol, (_, _, decl))| (symbol.clone(), decl.clone())) .collect::>(); for (symbol, decl) in src_ns.symbols.iter() { - if decl.visibility(decl_engine).is_public() { + if is_ancestor(src, dst, experimental_private_modules) + || decl.visibility(decl_engine).is_public() + { symbols_and_decls.push((symbol.clone(), decl.clone())); } } @@ -334,9 +372,17 @@ impl Module { src: &Path, dst: &Path, alias: Option, + experimental_private_modules: bool, ) -> CompileResult<()> { let (last_item, src) = src.split_last().expect("guaranteed by grammar"); - self.item_import(engines, src, last_item, dst, alias) + self.item_import( + engines, + src, + last_item, + dst, + alias, + experimental_private_modules, + ) } /// Pull a single `item` from the given `src` module and import it into the `dst` module. @@ -349,10 +395,18 @@ impl Module { item: &Ident, dst: &Path, alias: Option, + experimental_private_modules: bool, ) -> CompileResult<()> { let mut warnings = vec![]; let mut errors = vec![]; + check!( + self.check_module_privacy(src, dst, experimental_private_modules), + return err(warnings, errors), + warnings, + errors + ); + let decl_engine = engines.de(); let src_ns = check!( @@ -364,7 +418,9 @@ impl Module { let mut impls_to_insert = TraitMap::default(); match src_ns.symbols.get(item).cloned() { Some(decl) => { - if !decl.visibility(decl_engine).is_public() { + if !decl.visibility(decl_engine).is_public() + && !is_ancestor(src, dst, experimental_private_modules) + { errors.push(CompileError::ImportPrivateSymbol { name: item.clone(), span: item.span(), @@ -419,6 +475,7 @@ impl Module { /// 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, engines: Engines<'_>, @@ -427,10 +484,18 @@ impl Module { variant_name: &Ident, dst: &Path, alias: Option, + experimental_private_modules: bool, ) -> CompileResult<()> { let mut warnings = vec![]; let mut errors = vec![]; + check!( + self.check_module_privacy(src, dst, experimental_private_modules), + return err(warnings, errors), + warnings, + errors + ); + let decl_engine = engines.de(); let src_ns = check!( @@ -441,7 +506,9 @@ impl Module { ); match src_ns.symbols.get(enum_name).cloned() { Some(decl) => { - if !decl.visibility(decl_engine).is_public() { + if !decl.visibility(decl_engine).is_public() + && !is_ancestor(src, dst, experimental_private_modules) + { errors.push(CompileError::ImportPrivateSymbol { name: enum_name.clone(), span: enum_name.span(), @@ -529,10 +596,18 @@ impl Module { dst: &Path, engines: Engines<'_>, enum_name: &Ident, + experimental_private_modules: bool, ) -> CompileResult<()> { let mut warnings = vec![]; let mut errors = vec![]; + check!( + self.check_module_privacy(src, dst, experimental_private_modules), + return err(warnings, errors), + warnings, + errors + ); + let decl_engine = engines.de(); let src_ns = check!( @@ -543,7 +618,9 @@ impl Module { ); match src_ns.symbols.get(enum_name).cloned() { Some(decl) => { - if !decl.visibility(decl_engine).is_public() { + if !decl.visibility(decl_engine).is_public() + && !is_ancestor(src, dst, experimental_private_modules) + { errors.push(CompileError::ImportPrivateSymbol { name: enum_name.clone(), span: enum_name.span(), @@ -600,6 +677,39 @@ impl Module { ok((), warnings, errors) } + + fn check_module_privacy( + &self, + src: &Path, + dst: &Path, + experimental_private_modules: bool, + ) -> CompileResult<()> { + let mut warnings = vec![]; + let mut errors = vec![]; + + if experimental_private_modules { + // you are always allowed to access your ancestor's symbols + if !is_ancestor(src, dst, experimental_private_modules) { + // we don't check the first prefix because direct children are always accessible + for prefix in iter_prefixes(src).skip(1) { + let module = check!( + self.check_submodule(prefix), + return err(warnings, errors), + warnings, + errors + ); + if module.visibility.is_private() { + let prefix_last = prefix[prefix.len() - 1].clone(); + errors.push(CompileError::ImportPrivateModule { + span: prefix_last.span(), + name: prefix_last, + }); + } + } + } + } + ok((), warnings, errors) + } } impl std::ops::Deref for Module { @@ -652,3 +762,9 @@ fn module_not_found(path: &[Ident]) -> CompileError { .join("::"), } } + +fn is_ancestor(src: &Path, dst: &Path, experimental_private_modules: bool) -> bool { + experimental_private_modules + && 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 c397c2f4951..16c21a40841 100644 --- a/sway-core/src/semantic_analysis/namespace/namespace.rs +++ b/sway-core/src/semantic_analysis/namespace/namespace.rs @@ -2,7 +2,7 @@ use crate::{ decl_engine::{DeclRefConstant, DeclRefFunction}, engine_threading::*, error::*, - language::{ty, CallPath}, + language::{ty, CallPath, Visibility}, type_system::*, CompileResult, Ident, }; @@ -107,12 +107,18 @@ impl Namespace { &self, engines: Engines<'_>, call_path: &CallPath, + experimental_private_modules: bool, ) -> CompileResult<&ty::TyDecl> { - self.root - .resolve_call_path_with_visibility_check(engines, &self.mod_path, call_path) + self.root.resolve_call_path_with_visibility_check( + engines, + &self.mod_path, + call_path, + experimental_private_modules, + ) } /// Short-hand for calling [Root::resolve_type_with_self] on `root` with the `mod_path`. + #[allow(clippy::too_many_arguments)] // TODO: remove lint bypass once private modules are no longer experimental pub(crate) fn resolve_type_with_self( &mut self, engines: Engines<'_>, @@ -121,6 +127,7 @@ impl Namespace { span: &Span, enforce_type_arguments: EnforceTypeArguments, type_info_prefix: Option<&Path>, + experimental_private_modules: bool, ) -> CompileResult { let mod_path = self.mod_path.clone(); engines.te().resolve_with_self( @@ -132,6 +139,7 @@ impl Namespace { type_info_prefix, self, &mod_path, + experimental_private_modules, ) } @@ -142,6 +150,7 @@ impl Namespace { type_id: TypeId, span: &Span, type_info_prefix: Option<&Path>, + experimental_private_modules: bool, ) -> CompileResult { let mod_path = self.mod_path.clone(); engines.te().resolve( @@ -152,6 +161,7 @@ impl Namespace { type_info_prefix, self, &mod_path, + experimental_private_modules, ) } @@ -164,6 +174,7 @@ impl Namespace { item_name: &Ident, self_type: TypeId, engines: Engines<'_>, + experimental_private_modules: bool, ) -> CompileResult> { let mut warnings = vec![]; let mut errors = vec![]; @@ -200,7 +211,8 @@ impl Namespace { EnforceTypeArguments::No, None, self, - item_prefix + item_prefix, + experimental_private_modules, ), type_engine.insert(decl_engine, TypeInfo::ErrorRecovery), warnings, @@ -248,6 +260,7 @@ impl Namespace { /// /// This function will generate a missing method error if the method is not /// found. + #[allow(clippy::too_many_arguments)] // TODO: remove lint bypass once private modules are no longer experimental pub(crate) fn find_method_for_type( &mut self, type_id: TypeId, @@ -256,6 +269,7 @@ impl Namespace { self_type: TypeId, args_buf: &VecDeque, engines: Engines<'_>, + experimental_private_modules: bool, ) -> CompileResult { let mut warnings = vec![]; let mut errors = vec![]; @@ -264,7 +278,14 @@ impl Namespace { let type_engine = engines.te(); let matching_item_decl_refs = check!( - self.find_items_for_type(type_id, method_prefix, method_name, self_type, engines), + self.find_items_for_type( + type_id, + method_prefix, + method_name, + self_type, + engines, + experimental_private_modules + ), return err(warnings, errors), warnings, errors @@ -343,12 +364,20 @@ impl Namespace { item_name: &Ident, self_type: TypeId, engines: Engines<'_>, + experimental_private_modules: bool, ) -> CompileResult { let mut warnings = vec![]; let mut errors = vec![]; let matching_item_decl_refs = check!( - self.find_items_for_type(type_id, &Vec::::new(), item_name, self_type, engines), + self.find_items_for_type( + type_id, + &Vec::::new(), + item_name, + self_type, + engines, + experimental_private_modules + ), return err(warnings, errors), warnings, errors @@ -370,8 +399,14 @@ impl Namespace { } /// Short-hand for performing a [Module::star_import] with `mod_path` as the destination. - pub(crate) fn star_import(&mut self, src: &Path, engines: Engines<'_>) -> CompileResult<()> { - self.root.star_import(src, &self.mod_path, engines) + pub(crate) fn star_import( + &mut self, + src: &Path, + engines: Engines<'_>, + experimental_private_modules: bool, + ) -> CompileResult<()> { + self.root + .star_import(src, &self.mod_path, engines, experimental_private_modules) } /// Short-hand for performing a [Module::variant_star_import] with `mod_path` as the destination. @@ -380,9 +415,15 @@ impl Namespace { src: &Path, engines: Engines<'_>, enum_name: &Ident, + experimental_private_modules: bool, ) -> CompileResult<()> { - self.root - .variant_star_import(src, &self.mod_path, engines, enum_name) + self.root.variant_star_import( + src, + &self.mod_path, + engines, + enum_name, + experimental_private_modules, + ) } /// Short-hand for performing a [Module::self_import] with `mod_path` as the destination. @@ -391,8 +432,15 @@ impl Namespace { engines: Engines<'_>, src: &Path, alias: Option, + experimental_private_modules: bool, ) -> CompileResult<()> { - self.root.self_import(engines, src, &self.mod_path, alias) + self.root.self_import( + engines, + src, + &self.mod_path, + alias, + experimental_private_modules, + ) } /// Short-hand for performing a [Module::item_import] with `mod_path` as the destination. @@ -402,9 +450,16 @@ impl Namespace { src: &Path, item: &Ident, alias: Option, + experimental_private_modules: bool, ) -> CompileResult<()> { - self.root - .item_import(engines, src, item, &self.mod_path, alias) + self.root.item_import( + engines, + src, + item, + &self.mod_path, + alias, + experimental_private_modules, + ) } /// Short-hand for performing a [Module::variant_import] with `mod_path` as the destination. @@ -415,9 +470,17 @@ impl Namespace { enum_name: &Ident, variant_name: &Ident, alias: Option, + experimental_private_modules: bool, ) -> CompileResult<()> { - self.root - .variant_import(engines, src, enum_name, variant_name, &self.mod_path, alias) + self.root.variant_import( + engines, + src, + enum_name, + variant_name, + &self.mod_path, + alias, + experimental_private_modules, + ) } /// "Enter" the submodule at the given path by returning a new [SubmoduleNamespace]. @@ -429,6 +492,7 @@ impl Namespace { pub(crate) fn enter_submodule( &mut self, mod_name: Ident, + visibility: Visibility, module_span: Span, ) -> SubmoduleNamespace { let init = self.init.clone(); @@ -442,6 +506,7 @@ impl Namespace { let parent_mod_path = std::mem::replace(&mut self.mod_path, submod_path); self.name = Some(mod_name); self.span = Some(module_span); + self.visibility = visibility; self.is_external = false; SubmoduleNamespace { namespace: self, diff --git a/sway-core/src/semantic_analysis/namespace/root.rs b/sway-core/src/semantic_analysis/namespace/root.rs index ca21d3b037b..9129f7a9487 100644 --- a/sway-core/src/semantic_analysis/namespace/root.rs +++ b/sway-core/src/semantic_analysis/namespace/root.rs @@ -1,5 +1,6 @@ use sway_error::error::CompileError; use sway_types::Spanned; +use sway_utils::iter_prefixes; use crate::{ error::*, @@ -42,43 +43,63 @@ impl Root { /// 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 + /// This will concatenate the `mod_path` with the `call_path`'s prefixes and /// then calling `resolve_symbol` with the resulting path and call_path's suffix. /// - /// When `call_path` contains prefixes and the resolved declaration visibility is not public - /// an error is thrown. + /// The `mod_path` is significant here as we assume the resolution is done within the + /// context of the module pointed to by `mod_path` and will only check the call path prefixes + /// and the symbol's own visibility pub(crate) fn resolve_call_path_with_visibility_check( &self, engines: Engines<'_>, mod_path: &Path, call_path: &CallPath, + experimental_private_modules: bool, ) -> CompileResult<&ty::TyDecl> { - let warnings = vec![]; + let mut warnings = vec![]; let mut errors = vec![]; - let result = self.resolve_call_path(mod_path, call_path); + let decl = check!( + self.resolve_call_path(mod_path, call_path), + return err(warnings, errors), + warnings, + errors, + ); // In case there are no prefixes we don't need to check visibility if call_path.prefixes.is_empty() { - return result; + return ok(decl, warnings, errors); } - if let CompileResult { - value: Some(decl), .. - } = result - { - if !decl.visibility(engines.de()).is_public() { - errors.push(CompileError::ImportPrivateSymbol { - name: call_path.suffix.clone(), - span: call_path.suffix.span(), - }); - // Returns ok with error, this allows functions which call this to - // also access the returned TyDecl and throw more suitable errors. - return ok(decl, warnings, errors); + if experimental_private_modules { + // check the visibility of the call path elements + // we don't check the first prefix because direct children are always accessible + for prefix in iter_prefixes(&call_path.prefixes).skip(1) { + let module = check!( + self.check_submodule(prefix), + return err(warnings, errors), + warnings, + errors + ); + if module.visibility.is_private() { + let prefix_last = prefix[prefix.len() - 1].clone(); + errors.push(CompileError::ImportPrivateModule { + span: prefix_last.span(), + name: prefix_last, + }); + } } } - result + // check the visibility of the symbol itself + if !decl.visibility(engines.de()).is_public() { + errors.push(CompileError::ImportPrivateSymbol { + name: call_path.suffix.clone(), + span: call_path.suffix.span(), + }); + } + + ok(decl, warnings, errors) } /// Given a path to a module and the identifier of a symbol within that module, resolve its @@ -99,6 +120,7 @@ impl Root { match module.use_synonyms.get(symbol) { Some((_, _, decl @ ty::TyDecl::EnumVariantDecl { .. })) => ok(decl, vec![], vec![]), Some((src_path, _, _)) if mod_path != src_path => { + // TODO: check that the symbol import is public? self.resolve_symbol(src_path, true_symbol) } _ => CompileResult::from(module.check_symbol(true_symbol)), diff --git a/sway-core/src/semantic_analysis/program.rs b/sway-core/src/semantic_analysis/program.rs index 0594079e858..b4c192ef28d 100644 --- a/sway-core/src/semantic_analysis/program.rs +++ b/sway-core/src/semantic_analysis/program.rs @@ -20,10 +20,12 @@ impl ty::TyProgram { parsed: &ParseProgram, initial_namespace: namespace::Module, package_name: &str, + experimental_private_modules: bool, ) -> CompileResult { let mut namespace = Namespace::init_root(initial_namespace); - let ctx = - TypeCheckContext::from_root(&mut namespace, engines).with_kind(parsed.kind.clone()); + let ctx = TypeCheckContext::from_root(&mut namespace, engines) + .with_kind(parsed.kind.clone()) + .with_experimental_private_modules(experimental_private_modules); let ParseProgram { root, kind } = parsed; let mod_res = ty::TyModule::type_check(ctx, root); mod_res.flat_map(|root| { diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index fd57fffdfc9..d3b37caa373 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -1,7 +1,7 @@ use crate::{ decl_engine::DeclEngine, engine_threading::*, - language::{parsed::TreeType, Purity}, + language::{parsed::TreeType, Purity, Visibility}, namespace::Path, semantic_analysis::{ast_node::Mode, Namespace}, type_system::{ @@ -62,6 +62,9 @@ pub struct TypeCheckContext<'a> { /// disallowing functions from being defined inside of another function /// body). disallow_functions: bool, + + /// Enable experimental module privacy rules + experimental_private_modules: bool, } impl<'a> TypeCheckContext<'a> { @@ -91,6 +94,7 @@ impl<'a> TypeCheckContext<'a> { purity: Purity::default(), kind: TreeType::Contract, disallow_functions: false, + experimental_private_modules: false, } } @@ -114,6 +118,7 @@ impl<'a> TypeCheckContext<'a> { type_engine: self.type_engine, decl_engine: self.decl_engine, disallow_functions: self.disallow_functions, + experimental_private_modules: self.experimental_private_modules, } } @@ -130,6 +135,7 @@ impl<'a> TypeCheckContext<'a> { type_engine: self.type_engine, decl_engine: self.decl_engine, disallow_functions: self.disallow_functions, + experimental_private_modules: self.experimental_private_modules, } } @@ -140,6 +146,7 @@ impl<'a> TypeCheckContext<'a> { pub fn enter_submodule( self, mod_name: Ident, + visibility: Visibility, module_span: Span, with_submod_ctx: impl FnOnce(TypeCheckContext) -> T, ) -> T { @@ -147,7 +154,7 @@ impl<'a> TypeCheckContext<'a> { // namespace. However, we will likely want to pass through the type engine and declaration // engine here once they're added. let Self { namespace, .. } = self; - let mut submod_ns = namespace.enter_submodule(mod_name, module_span); + let mut submod_ns = namespace.enter_submodule(mod_name, visibility, module_span); let submod_ctx = TypeCheckContext::from_module_namespace( &mut submod_ns, Engines::new(self.type_engine, self.decl_engine), @@ -183,6 +190,17 @@ impl<'a> TypeCheckContext<'a> { Self { kind, ..self } } + /// Map this `TypeCheckContext` instance to a new one with the given module kind. + pub(crate) fn with_experimental_private_modules( + self, + experimental_private_modules: bool, + ) -> Self { + Self { + experimental_private_modules, + ..self + } + } + /// Map this `TypeCheckContext` instance to a new one with the given purity. pub(crate) fn with_self_type(self, self_type: TypeId) -> Self { Self { self_type, ..self } @@ -237,6 +255,10 @@ impl<'a> TypeCheckContext<'a> { self.disallow_functions } + pub(crate) fn experimental_private_modules_enabled(&self) -> bool { + self.experimental_private_modules + } + // Provide some convenience functions around the inner context. /// Short-hand for calling the `monomorphize` function in the type engine @@ -259,6 +281,7 @@ impl<'a> TypeCheckContext<'a> { call_site_span, self.namespace, &mod_path, + self.experimental_private_modules, ) } @@ -278,6 +301,7 @@ impl<'a> TypeCheckContext<'a> { span, enforce_type_args, type_info_prefix, + self.experimental_private_modules, ) } @@ -288,8 +312,13 @@ impl<'a> TypeCheckContext<'a> { span: &Span, type_info_prefix: Option<&Path>, ) -> CompileResult { - self.namespace - .resolve_type_without_self(self.engines(), type_id, span, type_info_prefix) + self.namespace.resolve_type_without_self( + self.engines(), + type_id, + span, + type_info_prefix, + self.experimental_private_modules, + ) } /// Short-hand around `type_system::unify_with_self`, where the `TypeCheckContext` provides the diff --git a/sway-core/src/type_system/ast_elements/binding.rs b/sway-core/src/type_system/ast_elements/binding.rs index 4c031677ba0..aaa9b23416e 100644 --- a/sway-core/src/type_system/ast_elements/binding.rs +++ b/sway-core/src/type_system/ast_elements/binding.rs @@ -238,7 +238,11 @@ impl TypeCheckTypeBinding for TypeBinding { // Grab the declaration. let unknown_decl = check!( ctx.namespace - .resolve_call_path_with_visibility_check(engines, &self.inner) + .resolve_call_path_with_visibility_check( + engines, + &self.inner, + ctx.experimental_private_modules_enabled() + ) .cloned(), return err(warnings, errors), warnings, @@ -311,7 +315,11 @@ impl TypeCheckTypeBinding for TypeBinding { // Grab the declaration. let unknown_decl = check!( ctx.namespace - .resolve_call_path_with_visibility_check(engines, &self.inner) + .resolve_call_path_with_visibility_check( + engines, + &self.inner, + ctx.experimental_private_modules_enabled() + ) .cloned(), return err(warnings, errors), warnings, @@ -365,7 +373,11 @@ impl TypeCheckTypeBinding for TypeBinding { // Grab the declaration. let unknown_decl = check!( ctx.namespace - .resolve_call_path_with_visibility_check(engines, &self.inner) + .resolve_call_path_with_visibility_check( + engines, + &self.inner, + ctx.experimental_private_modules_enabled() + ) .cloned(), return err(warnings, errors), warnings, @@ -433,7 +445,11 @@ impl TypeCheckTypeBinding for TypeBinding { // Grab the declaration. let unknown_decl = check!( ctx.namespace - .resolve_call_path_with_visibility_check(engines, &self.inner) + .resolve_call_path_with_visibility_check( + engines, + &self.inner, + ctx.experimental_private_modules_enabled() + ) .cloned(), return err(warnings, errors), warnings, diff --git a/sway-core/src/type_system/engine.rs b/sway-core/src/type_system/engine.rs index d542107f4f9..620645c84a9 100644 --- a/sway-core/src/type_system/engine.rs +++ b/sway-core/src/type_system/engine.rs @@ -101,6 +101,7 @@ impl TypeEngine { call_site_span: &Span, namespace: &mut Namespace, mod_path: &Path, + experimental_private_modules: bool, ) -> CompileResult<()> where T: MonomorphizeHelper + SubstTypes, @@ -161,7 +162,8 @@ impl TypeEngine { enforce_type_arguments, None, namespace, - mod_path + mod_path, + experimental_private_modules, ), self.insert(decl_engine, TypeInfo::ErrorRecovery), warnings, @@ -282,6 +284,7 @@ impl TypeEngine { type_info_prefix: Option<&Path>, namespace: &mut Namespace, mod_path: &Path, + experimental_private_modules: bool, ) -> CompileResult { let mut warnings = vec![]; let mut errors = vec![]; @@ -294,7 +297,12 @@ impl TypeEngine { } => { match namespace .root() - .resolve_call_path_with_visibility_check(engines, module_path, &call_path) + .resolve_call_path_with_visibility_check( + engines, + module_path, + &call_path, + experimental_private_modules, + ) .ok(&mut warnings, &mut errors) .cloned() { @@ -314,7 +322,8 @@ impl TypeEngine { enforce_type_arguments, span, namespace, - mod_path + mod_path, + experimental_private_modules, ), return err(warnings, errors), warnings, @@ -351,7 +360,8 @@ impl TypeEngine { enforce_type_arguments, span, namespace, - mod_path + mod_path, + experimental_private_modules, ), return err(warnings, errors), warnings, @@ -407,7 +417,8 @@ impl TypeEngine { enforce_type_arguments, None, namespace, - mod_path + mod_path, + experimental_private_modules ), self.insert(decl_engine, TypeInfo::ErrorRecovery), warnings, @@ -425,7 +436,8 @@ impl TypeEngine { enforce_type_arguments, None, namespace, - mod_path + mod_path, + experimental_private_modules ), self.insert(decl_engine, TypeInfo::ErrorRecovery), warnings, @@ -452,6 +464,7 @@ impl TypeEngine { type_info_prefix: Option<&Path>, namespace: &mut Namespace, mod_path: &Path, + experimental_private_modules: bool, ) -> CompileResult { type_id.replace_self_type(Engines::new(self, decl_engine), self_type); self.resolve( @@ -462,6 +475,7 @@ impl TypeEngine { type_info_prefix, namespace, mod_path, + experimental_private_modules, ) } diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index b8b6b6bad7b..34db4556615 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -243,6 +243,8 @@ pub enum CompileError { SymbolNotFound { name: Ident, span: Span }, #[error("Symbol \"{name}\" is private.")] ImportPrivateSymbol { name: Ident, span: Span }, + #[error("Module \"{name}\" is private.")] + ImportPrivateModule { name: Ident, span: Span }, #[error( "Because this if expression's value is used, an \"else\" branch is required and it must \ return type \"{r#type}\"" @@ -690,6 +692,7 @@ impl Spanned for CompileError { FieldNotFound { span, .. } => span.clone(), SymbolNotFound { span, .. } => span.clone(), ImportPrivateSymbol { span, .. } => span.clone(), + ImportPrivateModule { span, .. } => span.clone(), NoElseBranch { span, .. } => span.clone(), NotAType { span, .. } => span.clone(), MissingEnumInstantiator { span, .. } => span.clone(), diff --git a/sway-error/src/warning.rs b/sway-error/src/warning.rs index c68215f8b81..c7d5fd5dd8a 100644 --- a/sway-error/src/warning.rs +++ b/sway-error/src/warning.rs @@ -108,6 +108,7 @@ pub enum Warning { effect_in_suggestion: String, block_name: Ident, }, + ModulePrivacyDisabled, } impl fmt::Display for Warning { @@ -249,6 +250,9 @@ impl fmt::Display for Warning { EffectAfterInteraction {effect, effect_in_suggestion, block_name} => write!(f, "{effect} after external contract interaction in function or method \"{block_name}\". \ Consider {effect_in_suggestion} before calling another contract"), + ModulePrivacyDisabled => write!(f, "Module privacy rules will soon change to make modules private by default. + You can enable the new behavior with the --experimental-private-modules flag, which will become the default behavior in a later release. + More details are available in the related RFC: https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0008-private-modules.md"), } } } diff --git a/sway-lib-core/src/lib.sw b/sway-lib-core/src/lib.sw index dcbb46180f1..8c5773aa9c0 100644 --- a/sway-lib-core/src/lib.sw +++ b/sway-lib-core/src/lib.sw @@ -1,9 +1,9 @@ library; -mod primitives; -mod raw_ptr; -mod raw_slice; -mod ops; -mod never; -mod r#storage; -mod prelude; +pub mod primitives; +pub mod raw_ptr; +pub mod raw_slice; +pub mod ops; +pub mod never; +pub mod r#storage; +pub mod prelude; diff --git a/sway-lib-std/src/experimental.sw b/sway-lib-std/src/experimental.sw index 386d90dde06..975ce9534fa 100644 --- a/sway-lib-std/src/experimental.sw +++ b/sway-lib-std/src/experimental.sw @@ -1,4 +1,4 @@ //! Experimental types and functions. library; -mod r#storage; +pub mod r#storage; diff --git a/sway-lib-std/src/lib.sw b/sway-lib-std/src/lib.sw index 12591778d0a..f17a28cfefe 100644 --- a/sway-lib-std/src/lib.sw +++ b/sway-lib-std/src/lib.sw @@ -1,42 +1,42 @@ library; -mod error_signals; -mod logging; -mod revert; -mod result; -mod option; -mod convert; -mod intrinsics; -mod assert; -mod alloc; -mod contract_id; -mod constants; -mod external; -mod registers; -mod call_frames; -mod context; -mod hash; -mod b512; -mod address; -mod identity; -mod vec; -mod bytes; -mod r#storage; -mod b256; -mod tx; -mod inputs; -mod outputs; -mod auth; -mod math; -mod block; -mod token; -mod ecr; -mod vm; -mod flags; -mod u128; -mod u256; -mod message; -mod prelude; -mod low_level_call; +pub mod error_signals; +pub mod logging; +pub mod revert; +pub mod result; +pub mod option; +pub mod convert; +pub mod intrinsics; +pub mod assert; +pub mod alloc; +pub mod contract_id; +pub mod constants; +pub mod external; +pub mod registers; +pub mod call_frames; +pub mod context; +pub mod hash; +pub mod b512; +pub mod address; +pub mod identity; +pub mod vec; +pub mod bytes; +pub mod r#storage; +pub mod b256; +pub mod tx; +pub mod inputs; +pub mod outputs; +pub mod auth; +pub mod math; +pub mod block; +pub mod token; +pub mod ecr; +pub mod vm; +pub mod flags; +pub mod u128; +pub mod u256; +pub mod message; +pub mod prelude; +pub mod low_level_call; use core::*; diff --git a/sway-lib-std/src/storage.sw b/sway-lib-std/src/storage.sw index 6f153e51291..a7badc2d847 100644 --- a/sway-lib-std/src/storage.sw +++ b/sway-lib-std/src/storage.sw @@ -1,9 +1,9 @@ //! Contract storage utilities. library; -mod storage_api; -mod storage_key; -mod storable_slice; -mod storage_map; -mod storage_vec; -mod storage_bytes; +pub mod storage_api; +pub mod storage_key; +pub mod storable_slice; +pub mod storage_map; +pub mod storage_vec; +pub mod storage_bytes; diff --git a/sway-lib-std/src/vm.sw b/sway-lib-std/src/vm.sw index 6b26657fa72..575be8e0b29 100644 --- a/sway-lib-std/src/vm.sw +++ b/sway-lib-std/src/vm.sw @@ -1,4 +1,4 @@ //! VM-specific utilities. library; -mod evm; +pub mod evm; diff --git a/sway-lib-std/src/vm/evm.sw b/sway-lib-std/src/vm/evm.sw index f1836b8cdc0..f53bf2e502f 100644 --- a/sway-lib-std/src/vm/evm.sw +++ b/sway-lib-std/src/vm/evm.sw @@ -1,5 +1,5 @@ //! EVM-specific utilities. library; -mod evm_address; -mod ecr; +pub mod evm_address; +pub mod ecr; diff --git a/sway-lsp/src/core/session.rs b/sway-lsp/src/core/session.rs index 9a01ab6627e..6b7c7dbd65b 100644 --- a/sway-lsp/src/core/session.rs +++ b/sway-lsp/src/core/session.rs @@ -171,6 +171,7 @@ impl Session { true, tests_enabled, Engines::new(&new_type_engine, &new_decl_engine), + true, ) .map_err(LanguageServerError::FailedToCompile)?; diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 03fbe47069c..b2f4cd14bff 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -17,6 +17,7 @@ use sway_core::{ TraitConstraint, TypeId, TypeInfo, }; use sway_types::{Ident, Span, Spanned}; +use sway_utils::iter_prefixes; pub struct TypedTree<'a> { ctx: &'a ParseContext<'a>, @@ -1350,7 +1351,3 @@ fn collect_enum(ctx: &ParseContext, decl_id: &DeclId, declaratio variant.parse(ctx); }); } - -fn iter_prefixes(slice: &[T]) -> impl Iterator + DoubleEndedIterator { - (1..=slice.len()).map(move |len| &slice[..len]) -} diff --git a/sway-parse/src/item/mod.rs b/sway-parse/src/item/mod.rs index 75c715dc5a2..b80c45b20c6 100644 --- a/sway-parse/src/item/mod.rs +++ b/sway-parse/src/item/mod.rs @@ -31,7 +31,8 @@ impl Parse for ItemKind { let mut visibility = parser.take(); - let kind = if let Some(item) = parser.guarded_parse::()? { + let kind = if let Some(mut item) = parser.guarded_parse::()? { + item.visibility = visibility.take(); ItemKind::Submodule(item) } else if let Some(mut item) = parser.guarded_parse::()? { item.visibility = visibility.take(); diff --git a/sway-parse/src/submodule.rs b/sway-parse/src/submodule.rs index f5dedef4afb..44b99353988 100644 --- a/sway-parse/src/submodule.rs +++ b/sway-parse/src/submodule.rs @@ -4,6 +4,7 @@ use sway_ast::submodule::Submodule; impl Parse for Submodule { fn parse(parser: &mut Parser) -> ParseResult { + let visibility = parser.take(); let mod_token = parser.parse()?; let name = parser.parse()?; let semicolon_token = parser.parse()?; @@ -11,6 +12,7 @@ impl Parse for Submodule { mod_token, name, semicolon_token, + visibility, }) } } diff --git a/sway-utils/src/helpers.rs b/sway-utils/src/helpers.rs index 0b6f3cda7a8..2b3ec244fbb 100644 --- a/sway-utils/src/helpers.rs +++ b/sway-utils/src/helpers.rs @@ -24,7 +24,24 @@ pub fn get_sway_files(path: PathBuf) -> Vec { files } + pub fn is_sway_file(file: &Path) -> bool { let res = file.extension(); Some(OsStr::new(constants::SWAY_EXTENSION)) == res } + +/// create an iterator over all prefixes in a slice, smallest first +/// +/// ``` +/// # use sway_utils::iter_prefixes; +/// let val = [1, 2, 3]; +/// let mut it = iter_prefixes(&val); +/// assert_eq!(it.next(), Some([1].as_slice())); +/// assert_eq!(it.next(), Some([1, 2].as_slice())); +/// assert_eq!(it.next(), Some([1, 2, 3].as_slice())); +/// assert_eq!(it.next(), None); +/// +/// ``` +pub fn iter_prefixes(slice: &[T]) -> impl Iterator + DoubleEndedIterator { + (1..=slice.len()).map(move |len| &slice[..len]) +} diff --git a/test/src/e2e_vm_tests/harness.rs b/test/src/e2e_vm_tests/harness.rs index 7f5ef2f45bf..6a57a8088b1 100644 --- a/test/src/e2e_vm_tests/harness.rs +++ b/test/src/e2e_vm_tests/harness.rs @@ -218,6 +218,7 @@ pub(crate) async fn compile_to_bytes(file_name: &str, run_config: &RunConfig) -> println!("Compiling {} ...", file_name.bold()); let manifest_dir = env!("CARGO_MANIFEST_DIR"); let build_opts = forc_pkg::BuildOpts { + experimental_private_modules: true, build_target: run_config.build_target, pkg: forc_pkg::PkgOpts { path: Some(format!( diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/dep_annotated/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/dep_annotated/test.toml index 2bdd5e63dcc..6b5b8d3dea9 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/dep_annotated/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/dep_annotated/test.toml @@ -1,8 +1,5 @@ category = "fail" -# check: pub mod baz; -# nextln: $()Unnecessary visibility qualifier, `pub` is implied here. - # check: /// should return a parser error # nextln: $()Cannot attach a documentation comment to a dependency. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/enum_variant_unit/src/lib_a.sw b/test/src/e2e_vm_tests/test_programs/should_fail/enum_variant_unit/src/lib_a.sw index 2d21e4540be..70006ec55a6 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/enum_variant_unit/src/lib_a.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/enum_variant_unit/src/lib_a.sw @@ -1,5 +1,5 @@ library; -mod inner_lib; +pub mod inner_lib; use inner_lib::*; diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/generics_not_supported/src/lib_a.sw b/test/src/e2e_vm_tests/test_programs/should_fail/generics_not_supported/src/lib_a.sw index 2d21e4540be..70006ec55a6 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/generics_not_supported/src/lib_a.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/generics_not_supported/src/lib_a.sw @@ -1,5 +1,5 @@ library; -mod inner_lib; +pub mod inner_lib; use inner_lib::*; diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.lock new file mode 100644 index 00000000000..04ac51eb4e5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = 'core' +source = 'path+from-root-10546B8FB2F34D61' + +[[package]] +name = 'module_privacy' +source = 'member' +dependencies = ['std'] + +[[package]] +name = 'std' +source = 'path+from-root-10546B8FB2F34D61' +dependencies = ['core'] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.toml new file mode 100644 index 00000000000..2f772fd0b8e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/Forc.toml @@ -0,0 +1,8 @@ +[project] +name = "module_privacy" +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" + +[dependencies] +std = { path = "../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/alpha.sw b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/alpha.sw new file mode 100644 index 00000000000..32b0a26f4f8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/alpha.sw @@ -0,0 +1,4 @@ +library; + +pub fn foo() {} +fn bar() {} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta.sw b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta.sw new file mode 100644 index 00000000000..9adc48f2815 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta.sw @@ -0,0 +1,10 @@ +library; + +mod gamma; + +// pub use gamma::foo as gamma_foo; +// Error: gamma::bar is private +// pub use gamma::bar as gamma_bar; + +pub fn foo() {} +fn bar() {} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta/gamma.sw b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta/gamma.sw new file mode 100644 index 00000000000..3afa68cbcc3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/beta/gamma.sw @@ -0,0 +1,4 @@ +library; + +pub fn foo() {} +fn bar() {} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/main.sw new file mode 100644 index 00000000000..72fd3d6ab48 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/src/main.sw @@ -0,0 +1,47 @@ +script; + +// this module will be accessible outside the library +pub mod alpha; +// this module will not be accessible outside the library +mod beta; + +use ::alpha::foo as foo1; + +// Error: ::alpha::bar is private +use ::alpha::bar as bar1; + +use ::beta::foo as foo2; + +// Error: ::beta::bar is private +use ::beta::bar as bar2; + +// Error: ::beta::gamma is private +use ::beta::gamma::foo as foo3; + +// Error: ::beta::gamma is private +use ::beta::gamma::bar as bar3; + +// Error: ::beta::gamma is private +use ::beta::gamma::*; + + +fn main() { + ::alpha::foo(); + + // Error: ::alpha::bar is private + // ::alpha::bar(); + + ::beta::foo(); + + // Error: ::beta::bar is private + ::beta::bar(); + + // Error: ::beta::gamma is private + ::beta::gamma::foo(); + + // Error: ::beta::gamma is private + ::beta::gamma::bar(); + + // ::beta::gamma_foo(); + +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/test.toml new file mode 100644 index 00000000000..558ed08eae8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/module_privacy/test.toml @@ -0,0 +1,31 @@ +category = "fail" + +# check: use ::alpha::bar as bar1; +# nextln: $()Symbol "bar" is private. + +# check: use ::beta::bar as bar2; +# nextln: $()Symbol "bar" is private. + +# check: use ::beta::gamma::foo as foo3; +# nextln: $()Module "gamma" is private. + +# check: use ::beta::gamma::bar as bar3; +# nextln: $()Module "gamma" is private. + +# check: use ::beta::gamma::bar as bar3; +# nextln: $()Symbol "bar" is private. + +# check: use ::beta::gamma::*; +# nextln: $()Module "gamma" is private. + +# check: ::beta::bar(); +# nextln: $()Symbol "bar" is private. + +# check: ::beta::gamma::foo(); +# nextln: $()Module "gamma" is private. + +# check: ::beta::gamma::bar(); +# nextln: $()Module "gamma" is private. + +# check: ::beta::gamma::bar(); +# nextln: $()Symbol "bar" is private. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/generic_impl_self_where/src/traits.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/generic_impl_self_where/src/traits.sw index 8eacd3fd84f..40bd27d2d87 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/generic_impl_self_where/src/traits.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/generic_impl_self_where/src/traits.sw @@ -1,6 +1,6 @@ library; -mod nested_traits; +pub mod nested_traits; use nested_traits::*; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/multi_item_import/src/bar.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/multi_item_import/src/bar.sw index e11c06d57e8..0b77eb208ef 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/multi_item_import/src/bar.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/multi_item_import/src/bar.sw @@ -1,5 +1,5 @@ library; -mod double_bar; +pub mod double_bar; pub struct Bar1 { a: u32, diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo.sw index 871fba91f2b..9c0926b439e 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo.sw @@ -1,3 +1,3 @@ library; -mod bar; +pub mod bar; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo/bar.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo/bar.sw index df6056f61ce..f54568cbae4 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo/bar.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/primitive_type_argument/src/foo/bar.sw @@ -1,3 +1,3 @@ library; -mod baz; +pub mod baz; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name/src/pkga.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name/src/pkga.sw index f0884075034..7e9a23ca32b 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name/src/pkga.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name/src/pkga.sw @@ -1,6 +1,6 @@ library; -mod pkgb; +pub mod pkgb; pub const TEST_CONST: u64 = 20; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/main.sw index e57ec9e889a..7a4c81d9826 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/main.sw @@ -1,6 +1,6 @@ library; -mod pkga; +pub mod pkga; pub const TEST_CONST: u64 = 60; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/pkga.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/pkga.sw index 0ab4b2fd4eb..7148aefcbb7 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/pkga.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/same_const_name_lib/src/pkga.sw @@ -1,6 +1,6 @@ library; -mod pkgb; +pub mod pkgb; pub const TEST_CONST: u64 = 50; diff --git a/test/src/ir_generation/mod.rs b/test/src/ir_generation/mod.rs index 3fac395242e..ecb4b0b8fe5 100644 --- a/test/src/ir_generation/mod.rs +++ b/test/src/ir_generation/mod.rs @@ -347,6 +347,7 @@ fn compile_core(build_target: BuildTarget, engines: Engines<'_>) -> namespace::M terse_mode: true, disable_tests: false, locked: false, + experimental_private_modules: true, }; let res = forc::test::forc_check::check(check_cmd, engines)