From 647a740c1152729c55ccd97cc2db46c420288fab Mon Sep 17 00:00:00 2001 From: Piepmatz Date: Sun, 6 Jul 2025 08:34:27 +0200 Subject: [PATCH] Add `all` to enable all active experimental options (#16121) - closes #16118 # Description This PR adds the option to pass `all` to the experimental options parser. This will enable all active (not deprecated) experimental options to ease with dogfooding. # User-Facing Changes A new valid value for `--experimental-options` and `NU_EXPERIMENTAL_OPTIONS`. # Tests + Formatting # After Submitting --- crates/nu-experimental/src/lib.rs | 18 +++++++++++++ crates/nu-experimental/src/parse.rs | 41 +++++++++++++++++++++++++---- src/command.rs | 2 +- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/crates/nu-experimental/src/lib.rs b/crates/nu-experimental/src/lib.rs index 58d63a4a4c..6ba0a88ffb 100644 --- a/crates/nu-experimental/src/lib.rs +++ b/crates/nu-experimental/src/lib.rs @@ -182,6 +182,24 @@ impl PartialEq for ExperimentalOption { impl Eq for ExperimentalOption {} +/// Sets the state of all experimental option that aren't deprecated. +/// +/// # Safety +/// This method is unsafe to emphasize that experimental options are not designed to change +/// dynamically at runtime. +/// Changing their state at arbitrary points can lead to inconsistent behavior. +/// You should set experimental options only during initialization, before the application fully +/// starts. +pub unsafe fn set_all(value: bool) { + for option in ALL { + match option.status() { + // SAFETY: The safety bounds for `ExperimentalOption.set` are the same as this function. + Status::OptIn | Status::OptOut => unsafe { option.set(value) }, + Status::DeprecatedDefault | Status::DeprecatedDiscard => {} + } + } +} + pub(crate) trait DynExperimentalOptionMarker { fn identifier(&self) -> &'static str; fn description(&self) -> &'static str; diff --git a/crates/nu-experimental/src/parse.rs b/crates/nu-experimental/src/parse.rs index 79bd146c49..eab2571546 100644 --- a/crates/nu-experimental/src/parse.rs +++ b/crates/nu-experimental/src/parse.rs @@ -19,6 +19,10 @@ pub enum ParseWarning { #[error("Invalid assignment for `{identifier}`, expected `true` or `false`, got `{1}`", identifier = .0.identifier())] InvalidAssignment(&'static ExperimentalOption, String), + /// The assignment for "all" wasn't valid. Only `true` or `false` is accepted. + #[error("Invalid assignment for `all`, expected `true` or `false`, got `{0}`")] + InvalidAssignmentAll(String), + /// This experimental option is deprecated as this is now the default behavior. #[error("The experimental option `{identifier}` is deprecated as this is now the default behavior.", identifier = .0.identifier())] DeprecatedDefault(&'static ExperimentalOption), @@ -33,6 +37,11 @@ pub enum ParseWarning { /// This is the recommended way to activate options, as it handles [`ParseWarning`]s properly /// and is easy to hook into. /// +/// When the key `"all"` is encountered, [`set_all`](super::set_all) is used to set all +/// experimental options that aren't deprecated. +/// This allows opting (or opting out of) all experimental options that are currently available for +/// testing. +/// /// The `iter` argument should yield: /// - the identifier of the option /// - an optional assignment value (`true`/`false`) @@ -44,6 +53,19 @@ pub fn parse_iter<'i, Ctx: Clone>( ) -> Vec<(ParseWarning, Ctx)> { let mut warnings = Vec::new(); for (key, val, ctx) in iter { + if key == "all" { + let val = match parse_val(val.as_deref()) { + Ok(val) => val, + Err(s) => { + warnings.push((ParseWarning::InvalidAssignmentAll(s.to_owned()), ctx)); + continue; + } + }; + // SAFETY: This is part of the expected parse function to be called at initialization. + unsafe { super::set_all(val) }; + continue; + } + let Some(option) = ALL.iter().find(|option| option.identifier() == key.trim()) else { warnings.push((ParseWarning::Unknown(key.to_string()), ctx)); continue; @@ -59,11 +81,9 @@ pub fn parse_iter<'i, Ctx: Clone>( _ => {} } - let val = match val.as_deref().map(str::trim) { - None => true, - Some("true") => true, - Some("false") => false, - Some(s) => { + let val = match parse_val(val.as_deref()) { + Ok(val) => val, + Err(s) => { warnings.push((ParseWarning::InvalidAssignment(option, s.to_owned()), ctx)); continue; } @@ -75,6 +95,15 @@ pub fn parse_iter<'i, Ctx: Clone>( warnings } +fn parse_val(val: Option<&str>) -> Result { + match val.map(str::trim) { + None => Ok(true), + Some("true") => Ok(true), + Some("false") => Ok(false), + Some(s) => Err(s), + } +} + /// Parse experimental options from the [`ENV`] environment variable. /// /// Uses [`parse_iter`] internally. Each warning includes a `Range` pointing to the @@ -110,6 +139,7 @@ impl ParseWarning { match self { Self::Unknown(_) => "nu::experimental_option::unknown", Self::InvalidAssignment(_, _) => "nu::experimental_option::invalid_assignment", + Self::InvalidAssignmentAll(_) => "nu::experimental_option::invalid_assignment_all", Self::DeprecatedDefault(_) => "nu::experimental_option::deprecated_default", Self::DeprecatedDiscard(_) => "nu::experimental_option::deprecated_discard", } @@ -126,6 +156,7 @@ impl ParseWarning { ALL.iter().map(|option| option.identifier()).join(", ") )), Self::InvalidAssignment(_, _) => None, + Self::InvalidAssignmentAll(_) => None, Self::DeprecatedDiscard(_) => None, Self::DeprecatedDefault(_) => { Some(String::from("You can safely remove this option now.")) diff --git a/src/command.rs b/src/command.rs index c394687fee..85c7f8006c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -471,7 +471,7 @@ impl Command for Nu { .named( "experimental-options", SyntaxShape::List(Box::new(SyntaxShape::String)), - "enable or disable experimental options", + r#"enable or disable experimental options, use `"all"` to set all active options"#, None, ) .optional(