diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index 647b955f30e..81f4d5dd7a5 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -202,11 +202,17 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub debug_compile_stdin: bool, - /// Unstable features to enable for this current build + /// Unstable features to enable for this current build. + /// + /// If non-empty, it disables unstable features required in crate manifests. #[arg(value_parser = clap::value_parser!(UnstableFeature))] - #[clap(long, short = 'Z', value_delimiter = ',')] + #[clap(long, short = 'Z', value_delimiter = ',', conflicts_with = "no_unstable_features")] pub unstable_features: Vec, + /// Disable any unstable features required in crate manifests. + #[arg(long, conflicts_with = "unstable_features")] + pub no_unstable_features: bool, + /// Used internally to avoid comptime println from producing output #[arg(long, hide = true)] pub disable_comptime_printing: bool, @@ -268,6 +274,7 @@ impl CompileOptions { debug_comptime_in_file: self.debug_comptime_in_file.as_deref(), pedantic_solving: self.pedantic_solving, enabled_unstable_features: &self.unstable_features, + disable_required_unstable_features: self.no_unstable_features, } } } diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index f223f870743..68ea6a796de 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -90,6 +90,7 @@ impl<'context> Elaborator<'context> { self.usage_tracker, self.crate_graph, self.interpreter_output, + self.required_unstable_features, self.crate_id, self.interpreter_call_stack.clone(), self.options, diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index d40458d779d..a7654243ba1 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -127,6 +127,8 @@ pub struct Elaborator<'context> { pub(crate) crate_graph: &'context CrateGraph, pub(crate) interpreter_output: &'context Option>>, + required_unstable_features: &'context BTreeMap>, + unsafe_block_status: UnsafeBlockStatus, current_loop: Option, @@ -244,6 +246,7 @@ impl<'context> Elaborator<'context> { usage_tracker: &'context mut UsageTracker, crate_graph: &'context CrateGraph, interpreter_output: &'context Option>>, + required_unstable_features: &'context BTreeMap>, crate_id: CrateId, interpreter_call_stack: im::Vector, options: ElaboratorOptions<'context>, @@ -257,6 +260,7 @@ impl<'context> Elaborator<'context> { usage_tracker, crate_graph, interpreter_output, + required_unstable_features, unsafe_block_status: UnsafeBlockStatus::NotInUnsafeBlock, current_loop: None, generics: Vec::new(), @@ -290,6 +294,7 @@ impl<'context> Elaborator<'context> { &mut context.usage_tracker, &context.crate_graph, &context.interpreter_output, + &context.required_unstable_features, crate_id, im::Vector::new(), options, @@ -2382,10 +2387,27 @@ impl<'context> Elaborator<'context> { /// Register a use of the given unstable feature. Errors if the feature has not /// been explicitly enabled in this package. pub fn use_unstable_feature(&mut self, feature: UnstableFeature, location: Location) { - if !self.options.enabled_unstable_features.contains(&feature) { - let reason = ParserErrorReason::ExperimentalFeature(feature); - self.push_err(ParserError::with_reason(reason, location)); + // Is the feature globally enabled via CLI options? + if self.options.enabled_unstable_features.contains(&feature) { + return; + } + + // Can crates require unstable features in their manifest? + let enable_required_unstable_features = self.options.enabled_unstable_features.is_empty() + && !self.options.disable_required_unstable_features; + + // Is it required by the current crate? + if enable_required_unstable_features + && self + .required_unstable_features + .get(&self.crate_id) + .is_some_and(|fs| fs.contains(&feature)) + { + return; } + + let reason = ParserErrorReason::ExperimentalFeature(feature); + self.push_err(ParserError::with_reason(reason, location)); } /// Run the given function using the resolver and return true if any errors (not warnings) diff --git a/compiler/noirc_frontend/src/elaborator/options.rs b/compiler/noirc_frontend/src/elaborator/options.rs index 0d72d2955ba..3d33919d32d 100644 --- a/compiler/noirc_frontend/src/elaborator/options.rs +++ b/compiler/noirc_frontend/src/elaborator/options.rs @@ -41,6 +41,9 @@ pub struct GenericOptions<'a, T> { /// Unstable compiler features that were explicitly enabled. Any unstable features /// that are not in this list result in an error when used. pub enabled_unstable_features: &'a [UnstableFeature], + + /// Deny crates from requiring unstable features. + pub disable_required_unstable_features: bool, } /// Options from nargo_cli that need to be passed down to the elaborator @@ -57,6 +60,7 @@ impl GenericOptions<'_, T> { debug_comptime_in_file: None, pedantic_solving: true, enabled_unstable_features: &[UnstableFeature::Enums], + disable_required_unstable_features: true, } } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index db124a2946f..429d95734f4 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -497,6 +497,7 @@ impl DefCollector { debug_comptime_in_file, pedantic_solving: options.pedantic_solving, enabled_unstable_features: options.enabled_unstable_features, + disable_required_unstable_features: options.disable_required_unstable_features, }; let mut more_errors = diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index 56540ebad59..88c6509f05a 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -7,6 +7,7 @@ pub mod type_check; use crate::ast::UnresolvedGenerics; use crate::debug::DebugInstrumenter; +use crate::elaborator::UnstableFeature; use crate::graph::{CrateGraph, CrateId}; use crate::hir_def::function::FuncMeta; use crate::node_interner::{FuncId, NodeInterner, TypeId}; @@ -56,6 +57,9 @@ pub struct Context<'file_manager, 'parsed_files> { /// Writer for comptime prints. pub interpreter_output: Option>>, + + /// Any unstable features required by the current package or its dependencies. + pub required_unstable_features: BTreeMap>, } #[derive(Debug)] @@ -78,6 +82,7 @@ impl Context<'_, '_> { parsed_files: Cow::Owned(parsed_files), package_build_path: PathBuf::default(), interpreter_output: Some(Rc::new(RefCell::new(std::io::stdout()))), + required_unstable_features: BTreeMap::new(), } } @@ -96,6 +101,7 @@ impl Context<'_, '_> { parsed_files: Cow::Borrowed(parsed_files), package_build_path: PathBuf::default(), interpreter_output: Some(Rc::new(RefCell::new(std::io::stdout()))), + required_unstable_features: BTreeMap::new(), } } diff --git a/docs/docs/getting_started/project_breakdown.md b/docs/docs/getting_started/project_breakdown.md index e442e377040..a26af0f37e7 100644 --- a/docs/docs/getting_started/project_breakdown.md +++ b/docs/docs/getting_started/project_breakdown.md @@ -62,6 +62,7 @@ The package section defines a number of fields including: - `type` (**required**) - can be "bin", "lib", or "contract" to specify whether its a binary, library or Aztec contract - `authors` (optional) - authors of the project - `compiler_version` - specifies the version of the compiler to use. This is enforced by the compiler and follow's [Rust's versioning](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field), so a `compiler_version = 0.18.0` will enforce Nargo version 0.18.0, `compiler_version = ^0.18.0` will enforce anything above 0.18.0 but below 0.19.0, etc. For more information, see how [Rust handles these operators](https://docs.rs/semver/latest/semver/enum.Op.html) +- `compiler_unstable_features` (optional) - A list of unstable features required by this package to compile. - `description` (optional) - `entry` (optional) - a relative filepath to use as the entry point into your package (overrides the default of `src/lib.nr` or `src/main.nr`) - `backend` (optional) diff --git a/docs/versioned_docs/version-v1.0.0-beta.5/reference/nargo_commands.md b/docs/versioned_docs/version-v1.0.0-beta.5/reference/nargo_commands.md index 3e8d29ee9f4..bde4d5825a6 100644 --- a/docs/versioned_docs/version-v1.0.0-beta.5/reference/nargo_commands.md +++ b/docs/versioned_docs/version-v1.0.0-beta.5/reference/nargo_commands.md @@ -680,6 +680,13 @@ Run the fuzzing harnesses for this program Possible values: `true`, `false` * `-Z`, `--unstable-features ` — Unstable features to enable for this current build + +* `--no-unstable-features` — Disable unstable features required by crates in their manifests + + Default value: `false` + + Possible values: `true`, `false` + * `--oracle-resolver ` — JSON RPC url to solve oracle calls * `--timeout ` — Maximum time in seconds to spend fuzzing (default: no timeout) @@ -690,6 +697,7 @@ Run the fuzzing harnesses for this program + ## `nargo info` Provides detailed information on each of a program's function (represented by a single circuit) @@ -855,4 +863,3 @@ Generates a shell completion script for your favorite shell This document was generated automatically by clap-markdown. - diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 0d161b55c44..380d170d146 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -269,6 +269,9 @@ fn byte_span_to_range<'a, F: files::Files<'a> + ?Sized>( } } +/// Create a workspace based on the source file location: +/// * if there is a `Nargo.toml` file, use it to read the workspace +/// * otherwise treat the parent directory as a dummy workspace pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result { if let Some(toml_path) = find_file_manifest(file_path) { match resolve_workspace_from_toml( @@ -304,6 +307,7 @@ pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result { let crate_id = prepare_dependency(context, &package.entry_path); + add_unstable_features(context, crate_id, package); add_dep(context, parent_crate, crate_id, dep_name.clone()); prepare_dependencies(context, crate_id, &package.dependencies); } @@ -287,10 +288,15 @@ pub fn prepare_package<'file_manager, 'parsed_files>( package: &Package, ) -> (Context<'file_manager, 'parsed_files>, CrateId) { let mut context = Context::from_ref_file_manager(file_manager, parsed_files); - let crate_id = prepare_crate(&mut context, &package.entry_path); - + add_unstable_features(&mut context, crate_id, package); prepare_dependencies(&mut context, crate_id, &package.dependencies); - (context, crate_id) } + +/// Add any unstable features required by the `Package` to the `Context`. +fn add_unstable_features(context: &mut Context, crate_id: CrateId, package: &Package) { + context + .required_unstable_features + .insert(crate_id, package.compiler_required_unstable_features.clone()); +} diff --git a/tooling/nargo/src/package.rs b/tooling/nargo/src/package.rs index cd1d5e2a84f..c716c81e3e4 100644 --- a/tooling/nargo/src/package.rs +++ b/tooling/nargo/src/package.rs @@ -2,6 +2,7 @@ use std::{collections::BTreeMap, fmt::Display, path::PathBuf}; use acvm::acir::circuit::ExpressionWidth; pub use noirc_driver::CrateName; +use noirc_frontend::elaborator::UnstableFeature; use crate::constants::PROVER_INPUT_FILE; @@ -47,6 +48,7 @@ pub struct Package { pub version: Option, // A semver string which specifies the compiler version required to compile this package pub compiler_required_version: Option, + pub compiler_required_unstable_features: Vec, pub root_dir: PathBuf, pub package_type: PackageType, pub entry_path: PathBuf, diff --git a/tooling/nargo_cli/tests/stdlib-tests.rs b/tooling/nargo_cli/tests/stdlib-tests.rs index f2d9d133b3c..8c25e0b25fc 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -58,6 +58,7 @@ fn run_stdlib_tests(force_brillig: bool, inliner_aggressiveness: i64) { let dummy_package = Package { version: None, compiler_required_version: None, + compiler_required_unstable_features: Vec::new(), root_dir: PathBuf::from("."), package_type: PackageType::Binary, entry_path: PathBuf::from("main.nr"), diff --git a/tooling/nargo_toml/src/lib.rs b/tooling/nargo_toml/src/lib.rs index d3a00fc241f..06e070290c5 100644 --- a/tooling/nargo_toml/src/lib.rs +++ b/tooling/nargo_toml/src/lib.rs @@ -4,6 +4,7 @@ use std::{ collections::BTreeMap, path::{Component, Path, PathBuf}, + str::FromStr, }; use errors::SemverError; @@ -13,7 +14,7 @@ use nargo::{ workspace::Workspace, }; use noirc_driver::parse_expression_width; -use noirc_frontend::graph::CrateName; +use noirc_frontend::{elaborator::UnstableFeature, graph::CrateName}; use serde::Deserialize; mod errors; @@ -241,9 +242,17 @@ impl PackageConfig { }) .map_or(Ok(None), |res| res.map(Some))?; + // Collect any unstable features the package needs to compile. + // Ignore the ones that we don't recognize: maybe they are no longer unstable, but a dependency hasn't been updated. + let compiler_required_unstable_features = + self.package.compiler_unstable_features.as_ref().map_or(Vec::new(), |feats| { + feats.iter().flat_map(|feat| UnstableFeature::from_str(feat).ok()).collect() + }); + Ok(Package { version: self.package.version.clone(), compiler_required_version: self.package.compiler_version.clone(), + compiler_required_unstable_features, root_dir: root_dir.to_path_buf(), entry_path, package_type, @@ -317,6 +326,9 @@ pub struct PackageMetadata { // We also state that ACIR and the compiler will upgrade in lockstep. // so you will not need to supply an ACIR and compiler version pub compiler_version: Option, + /// List of unstable features we want the compiler to enable to compile this package. + /// This is most useful with the LSP, so it can figure out what is allowed without CLI args. + pub compiler_unstable_features: Option>, pub license: Option, pub expression_width: Option, } diff --git a/tooling/nargo_toml/src/semver.rs b/tooling/nargo_toml/src/semver.rs index 83f31c71a5e..1dcfc131834 100644 --- a/tooling/nargo_toml/src/semver.rs +++ b/tooling/nargo_toml/src/semver.rs @@ -100,6 +100,7 @@ mod tests { let mut package = Package { compiler_required_version: Some("0.1.0".to_string()), + compiler_required_unstable_features: Vec::new(), root_dir: PathBuf::new(), package_type: PackageType::Library, entry_path: PathBuf::new(), @@ -136,6 +137,7 @@ mod tests { let mut package = Package { compiler_required_version: Some("0.1.0".to_string()), + compiler_required_unstable_features: Vec::new(), root_dir: PathBuf::new(), package_type: PackageType::Library, entry_path: PathBuf::new(), @@ -147,23 +149,13 @@ mod tests { let valid_dependency = Package { compiler_required_version: Some("0.1.0".to_string()), - root_dir: PathBuf::new(), - package_type: PackageType::Library, - entry_path: PathBuf::new(), name: CrateName::from_str("good_dependency").unwrap(), - dependencies: BTreeMap::new(), - version: Some("1.0".to_string()), - expression_width: None, + ..package.clone() }; let invalid_dependency = Package { compiler_required_version: Some("0.2.0".to_string()), - root_dir: PathBuf::new(), - package_type: PackageType::Library, - entry_path: PathBuf::new(), name: CrateName::from_str("bad_dependency").unwrap(), - dependencies: BTreeMap::new(), - version: Some("1.0".to_string()), - expression_width: None, + ..package.clone() }; package.dependencies.insert( @@ -202,6 +194,7 @@ mod tests { let package = Package { compiler_required_version: Some(">=0.1.0".to_string()), + compiler_required_unstable_features: Vec::new(), root_dir: PathBuf::new(), package_type: PackageType::Library, entry_path: PathBuf::new(), @@ -224,6 +217,7 @@ mod tests { let package = Package { compiler_required_version: Some(">=0.1.0".to_string()), + compiler_required_unstable_features: Vec::new(), root_dir: PathBuf::new(), package_type: PackageType::Library, entry_path: PathBuf::new(), @@ -244,6 +238,7 @@ mod tests { let package = Package { compiler_required_version: Some("0.1.0".to_string()), + compiler_required_unstable_features: Vec::new(), root_dir: PathBuf::new(), package_type: PackageType::Library, entry_path: PathBuf::new(),