Partial workaround and deprecation warning for breaking change usage of #15654 (#15806)

# Description
Adds a temporary workaround to prevent #15654 from being a breaking
change when using a closure stored in a variable, and issues a warning.
Also has a special case related to
https://github.com/carapace-sh/carapace-bin/pull/2796 which suggests
re-running `carapace init`


![image](https://github.com/user-attachments/assets/783f3dbf-2a85-4aa5-ac66-efc584ac77fd)


![image](https://github.com/user-attachments/assets/c8fb5ae1-66a8-474c-8244-a22600f4da43)

# After Submitting
Remove variable name detection after grace period
This commit is contained in:
132ikl 2025-06-04 04:19:25 -04:00 committed by GitHub
parent c563e0cfb0
commit 42fc9f52a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 15 deletions

View File

@ -1,5 +1,11 @@
use std::{borrow::Cow, ops::Deref};
use nu_engine::{ClosureEval, command_prelude::*}; use nu_engine::{ClosureEval, command_prelude::*};
use nu_protocol::{ListStream, Signals}; use nu_protocol::{
ListStream, Signals,
ast::{Expr, Expression},
report_shell_warning,
};
#[derive(Clone)] #[derive(Clone)]
pub struct Default; pub struct Default;
@ -32,6 +38,11 @@ impl Command for Default {
.category(Category::Filters) .category(Category::Filters)
} }
// FIXME remove once deprecation warning is no longer needed
fn requires_ast_for_arguments(&self) -> bool {
true
}
fn description(&self) -> &str { fn description(&self) -> &str {
"Sets a default value if a row's column is missing or null." "Sets a default value if a row's column is missing or null."
} }
@ -46,14 +57,19 @@ impl Command for Default {
let default_value: Value = call.req(engine_state, stack, 0)?; let default_value: Value = call.req(engine_state, stack, 0)?;
let columns: Vec<String> = call.rest(engine_state, stack, 1)?; let columns: Vec<String> = call.rest(engine_state, stack, 1)?;
let empty = call.has_flag(engine_state, stack, "empty")?; let empty = call.has_flag(engine_state, stack, "empty")?;
// FIXME for deprecation of closure passed via variable
let default_value_expr = call.positional_nth(stack, 0);
let default_value =
DefaultValue::new(engine_state, stack, default_value, default_value_expr);
default( default(
engine_state,
stack,
call, call,
input, input,
default_value, default_value,
empty, empty,
columns, columns,
engine_state.signals(),
) )
} }
@ -134,16 +150,14 @@ impl Command for Default {
} }
fn default( fn default(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
default_value: Value, mut default_value: DefaultValue,
default_when_empty: bool, default_when_empty: bool,
columns: Vec<String>, columns: Vec<String>,
signals: &Signals,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let input_span = input.span().unwrap_or(call.head); let input_span = input.span().unwrap_or(call.head);
let mut default_value = DefaultValue::new(engine_state, stack, default_value);
let metadata = input.metadata(); let metadata = input.metadata();
// If user supplies columns, check if input is a record or list of records // If user supplies columns, check if input is a record or list of records
@ -184,7 +198,7 @@ fn default(
item item
} }
}) })
.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) .into_pipeline_data_with_metadata(head, signals.clone(), metadata))
// If columns are given, but input does not use columns, return an error // If columns are given, but input does not use columns, return an error
} else { } else {
Err(ShellError::PipelineMismatch { Err(ShellError::PipelineMismatch {
@ -227,8 +241,20 @@ enum DefaultValue {
} }
impl DefaultValue { impl DefaultValue {
fn new(engine_state: &EngineState, stack: &Stack, value: Value) -> Self { fn new(
engine_state: &EngineState,
stack: &Stack,
value: Value,
expr: Option<&Expression>,
) -> Self {
let span = value.span(); let span = value.span();
// FIXME temporary workaround to warn people of breaking change from #15654
let value = match closure_variable_warning(engine_state, value, expr) {
Ok(val) => val,
Err(default_value) => return default_value,
};
match value { match value {
Value::Closure { val, .. } => { Value::Closure { val, .. } => {
let closure_eval = ClosureEval::new(engine_state, stack, *val); let closure_eval = ClosureEval::new(engine_state, stack, *val);
@ -277,6 +303,54 @@ fn fill_record(
Ok(Value::record(record, span)) Ok(Value::record(record, span))
} }
fn closure_variable_warning(
engine_state: &EngineState,
value: Value,
value_expr: Option<&Expression>,
) -> Result<Value, DefaultValue> {
// only warn if we are passed a closure inside a variable
let from_variable = matches!(
value_expr,
Some(Expression {
expr: Expr::FullCellPath(_),
..
})
);
let span = value.span();
match (&value, from_variable) {
// this is a closure from inside a variable
(Value::Closure { .. }, true) => {
let span_contents = String::from_utf8_lossy(engine_state.get_span_contents(span));
let carapace_suggestion = "re-run carapace init with version v1.3.3 or later\nor, change this to `{ $carapace_completer }`";
let suggestion = match span_contents {
Cow::Borrowed("$carapace_completer") => carapace_suggestion.to_string(),
Cow::Owned(s) if s.deref() == "$carapace_completer" => {
carapace_suggestion.to_string()
}
_ => format!("change this to {{ {} }}", span_contents).to_string(),
};
report_shell_warning(
engine_state,
&ShellError::DeprecationWarning {
deprecation_type: "Behavior",
suggestion,
span,
help: Some(
r"Since 0.105.0, closure literals passed to default are lazily evaluated, rather than returned as a value.
In a future release, closures passed by variable will also be lazily evaluated.",
),
},
);
// bypass the normal DefaultValue::new logic
Err(DefaultValue::Calculated(value))
}
_ => Ok(value),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::Upsert; use crate::Upsert;

View File

@ -1219,12 +1219,12 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
span: Span, span: Span,
}, },
#[error("{deprecated} is deprecated and will be removed in a future release")] #[error("{deprecation_type} deprecated.")]
#[diagnostic()] #[diagnostic(code(nu::shell::deprecated))]
Deprecated { DeprecationWarning {
deprecated: &'static str, deprecation_type: &'static str,
suggestion: &'static str, suggestion: String,
#[label("{deprecated} is deprecated. {suggestion}")] #[label("{suggestion}")]
span: Span, span: Span,
#[help] #[help]
help: Option<&'static str>, help: Option<&'static str>,