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)