diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 3a439bb790..34376b8ddb 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind}; use nu_engine::{column::get_columns, eval_variable}; use nu_protocol::{ @@ -101,7 +103,9 @@ pub(crate) fn eval_cell_path( } else { eval_constant(working_set, head) }?; - head_value.follow_cell_path(path_members, false) + head_value + .follow_cell_path(path_members, false) + .map(Cow::into_owned) } fn get_suggestions_by_value( diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 18206acba6..e648c66298 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -253,12 +253,11 @@ fn format_record( optional: false, }) .collect(); - match data_as_value.clone().follow_cell_path(&path_members, false) { - Ok(value_at_column) => { - output.push_str(value_at_column.to_expanded_string(", ", config).as_str()) - } - Err(se) => return Err(se), - } + + let expanded_string = data_as_value + .follow_cell_path(&path_members, false)? + .to_expanded_string(", ", config); + output.push_str(expanded_string.as_str()) } } } diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index 92a540fa46..b99289f6da 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -15,17 +15,8 @@ pub fn empty( if !columns.is_empty() { for val in input { for column in &columns { - let val = val.clone(); - match val.follow_cell_path(&column.members, false) { - Ok(Value::Nothing { .. }) => {} - Ok(_) => { - if negate { - return Ok(Value::bool(true, head).into_pipeline_data()); - } else { - return Ok(Value::bool(false, head).into_pipeline_data()); - } - } - Err(err) => return Err(err), + if !val.follow_cell_path(&column.members, false)?.is_nothing() { + return Ok(Value::bool(negate, head).into_pipeline_data()); } } } diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 54f99386af..d681849448 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use nu_engine::command_prelude::*; use nu_protocol::{ast::PathMember, Signals}; @@ -188,9 +190,11 @@ fn action( let input = input.into_value(span)?; for path in paths { - let val = input.clone().follow_cell_path(&path.members, !sensitive); - - output.push(val?); + output.push( + input + .follow_cell_path(&path.members, !sensitive)? + .into_owned(), + ); } Ok(output.into_iter().into_pipeline_data(span, signals)) @@ -223,10 +227,10 @@ pub fn follow_cell_path_into_stream( .map(move |value| { let span = value.span(); - match value.follow_cell_path(&cell_path, insensitive) { - Ok(v) => v, - Err(error) => Value::error(error, span), - } + value + .follow_cell_path(&cell_path, insensitive) + .map(Cow::into_owned) + .unwrap_or_else(|error| Value::error(error, span)) }) .into_pipeline_data(head, signals); diff --git a/crates/nu-command/src/filters/group_by.rs b/crates/nu-command/src/filters/group_by.rs index 0f56152834..a8da1cedf3 100644 --- a/crates/nu-command/src/filters/group_by.rs +++ b/crates/nu-command/src/filters/group_by.rs @@ -322,11 +322,9 @@ fn group_cell_path( let mut groups = IndexMap::<_, Vec<_>>::new(); for value in values.into_iter() { - let key = value - .clone() - .follow_cell_path(&column_name.members, false)?; + let key = value.follow_cell_path(&column_name.members, false)?; - if matches!(key, Value::Nothing { .. }) { + if key.is_nothing() { continue; // likely the result of a failed optional access, ignore this value } diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs index 97a087ec6e..e2801bf6ee 100644 --- a/crates/nu-command/src/filters/insert.rs +++ b/crates/nu-command/src/filters/insert.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce}; use nu_protocol::ast::PathMember; @@ -299,8 +301,8 @@ fn insert_value_by_closure( ) -> Result<(), ShellError> { let value_at_path = if first_path_member_int { value - .clone() .follow_cell_path(cell_path, false) + .map(Cow::into_owned) .unwrap_or(Value::nothing(span)) } else { value.clone() @@ -319,8 +321,8 @@ fn insert_single_value_by_closure( ) -> Result<(), ShellError> { let value_at_path = if first_path_member_int { value - .clone() .follow_cell_path(cell_path, false) + .map(Cow::into_owned) .unwrap_or(Value::nothing(span)) } else { value.clone() diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 3e8e5cb614..b324a7e771 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -229,45 +229,37 @@ fn select( match v { Value::List { vals: input_vals, .. - } => { - Ok(input_vals - .into_iter() - .map(move |input_val| { - if !columns.is_empty() { - let mut record = Record::new(); - for path in &columns { - //FIXME: improve implementation to not clone - match input_val.clone().follow_cell_path(&path.members, false) { - Ok(fetcher) => { - record.push(path.to_column_name(), fetcher); - } - Err(e) => return Value::error(e, call_span), + } => Ok(input_vals + .into_iter() + .map(move |input_val| { + if !columns.is_empty() { + let mut record = Record::new(); + for path in &columns { + match input_val.follow_cell_path(&path.members, false) { + Ok(fetcher) => { + record.push(path.to_column_name(), fetcher.into_owned()); } + Err(e) => return Value::error(e, call_span), } - - Value::record(record, span) - } else { - input_val.clone() } - }) - .into_pipeline_data_with_metadata( - call_span, - engine_state.signals().clone(), - metadata, - )) - } + + Value::record(record, span) + } else { + input_val.clone() + } + }) + .into_pipeline_data_with_metadata( + call_span, + engine_state.signals().clone(), + metadata, + )), _ => { if !columns.is_empty() { let mut record = Record::new(); for cell_path in columns { - // FIXME: remove clone - match v.clone().follow_cell_path(&cell_path.members, false) { - Ok(result) => { - record.push(cell_path.to_column_name(), result); - } - Err(e) => return Err(e), - } + let result = v.follow_cell_path(&cell_path.members, false)?; + record.push(cell_path.to_column_name(), result.into_owned()); } Ok(Value::record(record, call_span) @@ -278,31 +270,24 @@ fn select( } } } - PipelineData::ListStream(stream, metadata, ..) => { - Ok(stream - .map(move |x| { - if !columns.is_empty() { - let mut record = Record::new(); - for path in &columns { - //FIXME: improve implementation to not clone - match x.clone().follow_cell_path(&path.members, false) { - Ok(value) => { - record.push(path.to_column_name(), value); - } - Err(e) => return Value::error(e, call_span), + PipelineData::ListStream(stream, metadata, ..) => Ok(stream + .map(move |x| { + if !columns.is_empty() { + let mut record = Record::new(); + for path in &columns { + match x.follow_cell_path(&path.members, false) { + Ok(value) => { + record.push(path.to_column_name(), value.into_owned()); } + Err(e) => return Value::error(e, call_span), } - Value::record(record, call_span) - } else { - x } - }) - .into_pipeline_data_with_metadata( - call_span, - engine_state.signals().clone(), - metadata, - )) - } + Value::record(record, call_span) + } else { + x + } + }) + .into_pipeline_data_with_metadata(call_span, engine_state.signals().clone(), metadata)), _ => Ok(PipelineData::empty()), } } diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index 9d1d152d1e..3fe63ce676 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -243,17 +243,17 @@ fn update_value_by_closure( cell_path: &[PathMember], first_path_member_int: bool, ) -> Result<(), ShellError> { - let value_at_path = value.clone().follow_cell_path(cell_path, false)?; + let value_at_path = value.follow_cell_path(cell_path, false)?; let arg = if first_path_member_int { - &value_at_path + value_at_path.as_ref() } else { &*value }; let new_value = closure .add_arg(arg.clone()) - .run_with_input(value_at_path.into_pipeline_data())? + .run_with_input(value_at_path.into_owned().into_pipeline_data())? .into_value(span)?; value.update_data_at_cell_path(cell_path, new_value) @@ -266,17 +266,17 @@ fn update_single_value_by_closure( cell_path: &[PathMember], first_path_member_int: bool, ) -> Result<(), ShellError> { - let value_at_path = value.clone().follow_cell_path(cell_path, false)?; + let value_at_path = value.follow_cell_path(cell_path, false)?; let arg = if first_path_member_int { - &value_at_path + value_at_path.as_ref() } else { &*value }; let new_value = closure .add_arg(arg.clone()) - .run_with_input(value_at_path.into_pipeline_data())? + .run_with_input(value_at_path.into_owned().into_pipeline_data())? .into_value(span)?; value.update_data_at_cell_path(cell_path, new_value) diff --git a/crates/nu-command/src/filters/upsert.rs b/crates/nu-command/src/filters/upsert.rs index 5bcd7b9a8a..750263f402 100644 --- a/crates/nu-command/src/filters/upsert.rs +++ b/crates/nu-command/src/filters/upsert.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce}; use nu_protocol::ast::PathMember; @@ -319,15 +321,19 @@ fn upsert_value_by_closure( cell_path: &[PathMember], first_path_member_int: bool, ) -> Result<(), ShellError> { - let value_at_path = value.clone().follow_cell_path(cell_path, false); + let value_at_path = value.follow_cell_path(cell_path, false); let arg = if first_path_member_int { - value_at_path.clone().unwrap_or(Value::nothing(span)) + value_at_path + .as_deref() + .cloned() + .unwrap_or(Value::nothing(span)) } else { value.clone() }; let input = value_at_path + .map(Cow::into_owned) .map(IntoPipelineData::into_pipeline_data) .unwrap_or(PipelineData::Empty); @@ -346,15 +352,19 @@ fn upsert_single_value_by_closure( cell_path: &[PathMember], first_path_member_int: bool, ) -> Result<(), ShellError> { - let value_at_path = value.clone().follow_cell_path(cell_path, false); + let value_at_path = value.follow_cell_path(cell_path, false); let arg = if first_path_member_int { - value_at_path.clone().unwrap_or(Value::nothing(span)) + value_at_path + .as_deref() + .cloned() + .unwrap_or(Value::nothing(span)) } else { value.clone() }; let input = value_at_path + .map(Cow::into_owned) .map(IntoPipelineData::into_pipeline_data) .unwrap_or(PipelineData::Empty); diff --git a/crates/nu-command/src/platform/input/list.rs b/crates/nu-command/src/platform/input/list.rs index 91a31aebd8..df605f4a27 100644 --- a/crates/nu-command/src/platform/input/list.rs +++ b/crates/nu-command/src/platform/input/list.rs @@ -89,8 +89,7 @@ impl Command for InputList { .into_iter() .map(move |val| { let display_value = if let Some(ref cellpath) = display_path { - val.clone() - .follow_cell_path(&cellpath.members, false)? + val.follow_cell_path(&cellpath.members, false)? .to_expanded_string(", ", &config) } else { val.to_expanded_string(", ", &config) diff --git a/crates/nu-command/src/sort_utils.rs b/crates/nu-command/src/sort_utils.rs index 951a93ead9..0511e7cddf 100644 --- a/crates/nu-command/src/sort_utils.rs +++ b/crates/nu-command/src/sort_utils.rs @@ -239,8 +239,8 @@ pub fn compare_cell_path( insensitive: bool, natural: bool, ) -> Result { - let left = left.clone().follow_cell_path(&cell_path.members, false)?; - let right = right.clone().follow_cell_path(&cell_path.members, false)?; + let left = left.follow_cell_path(&cell_path.members, false)?; + let right = right.follow_cell_path(&cell_path.members, false)?; compare_values(&left, &right, insensitive, natural) } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 41d39241f8..c39dd0cf30 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -269,6 +269,7 @@ pub fn eval_expression_with_input( input = eval_subexpression::(engine_state, stack, block, input)? .into_value(*span)? .follow_cell_path(&full_cell_path.tail, false)? + .into_owned() .into_pipeline_data() } else { input = eval_subexpression::(engine_state, stack, block, input)?; @@ -604,7 +605,7 @@ impl Eval for EvalRuntime { let is_config = original_key == "config"; - stack.add_env_var(original_key, value); + stack.add_env_var(original_key, value.into_owned()); // Trigger the update to config, if we modified that. if is_config { diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 6dfa913931..ffd0938079 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -694,9 +694,8 @@ fn eval_instruction( let value = ctx.clone_reg_value(*src, *span)?; let path = ctx.take_reg(*path); if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { - // TODO: make follow_cell_path() not have to take ownership, probably using Cow let value = value.follow_cell_path(&path.members, true)?; - ctx.put_reg(*dst, value.into_pipeline_data()); + ctx.put_reg(*dst, value.into_owned().into_pipeline_data()); Ok(Continue) } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { Err(*error) diff --git a/crates/nu-lsp/src/goto.rs b/crates/nu-lsp/src/goto.rs index 1a232c5986..f8623ee0d7 100644 --- a/crates/nu-lsp/src/goto.rs +++ b/crates/nu-lsp/src/goto.rs @@ -63,7 +63,7 @@ impl LanguageServer { let var = working_set.get_variable(*var_id); Some( var.const_val - .clone() + .as_ref() .and_then(|val| val.follow_cell_path(cell_path, false).ok()) .map(|val| val.span()) .unwrap_or(var.declaration_span), diff --git a/crates/nu-lsp/src/hover.rs b/crates/nu-lsp/src/hover.rs index df6de49fc2..c75499f0c2 100644 --- a/crates/nu-lsp/src/hover.rs +++ b/crates/nu-lsp/src/hover.rs @@ -160,16 +160,15 @@ impl LanguageServer { let var = working_set.get_variable(var_id); markdown_hover( var.const_val - .clone() + .as_ref() .and_then(|val| val.follow_cell_path(&cell_path, false).ok()) .map(|val| { - let ty = val.get_type().clone(); - let value_string = val - .coerce_into_string() - .ok() - .map(|s| format!("\n---\n{}", s)) - .unwrap_or_default(); - format!("```\n{}\n```{}", ty, value_string) + let ty = val.get_type(); + if let Ok(s) = val.coerce_str() { + format!("```\n{}\n```\n---\n{}", ty, s) + } else { + format!("```\n{}\n```", ty) + } }) .unwrap_or("`unknown`".into()), ) diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 15fc5105d3..65c9b5f82b 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -7,7 +7,7 @@ use crate::{ debugger::DebugContext, BlockId, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, }; -use std::{collections::HashMap, sync::Arc}; +use std::{borrow::Cow, collections::HashMap, sync::Arc}; /// To share implementations for regular eval and const eval pub trait Eval { @@ -43,11 +43,8 @@ pub trait Eval { // Cell paths are usually case-sensitive, but we give $env // special treatment. - if cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID) { - value.follow_cell_path(&cell_path.tail, true) - } else { - value.follow_cell_path(&cell_path.tail, false) - } + let insensitive = cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID); + value.follow_cell_path(&cell_path.tail, insensitive).map(Cow::into_owned) } Expr::DateTime(dt) => Ok(Value::date(*dt, expr_span)), Expr::List(list) => { diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index ce688c18e3..a0ea603dc1 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -6,7 +6,7 @@ use crate::{ ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError, Signals, Span, Type, Value, }; -use std::io::Write; +use std::{borrow::Cow, io::Write}; const LINE_ENDING_PATTERN: &[char] = &['\r', '\n']; @@ -416,8 +416,11 @@ impl PipelineData { match self { // FIXME: there are probably better ways of doing this PipelineData::ListStream(stream, ..) => Value::list(stream.into_iter().collect(), head) - .follow_cell_path(cell_path, insensitive), - PipelineData::Value(v, ..) => v.follow_cell_path(cell_path, insensitive), + .follow_cell_path(cell_path, insensitive) + .map(Cow::into_owned), + PipelineData::Value(v, ..) => v + .follow_cell_path(cell_path, insensitive) + .map(Cow::into_owned), PipelineData::Empty => Err(ShellError::IncompatiblePathAccess { type_name: "empty pipeline".to_string(), span: head, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index ad7f8fe546..7ff9218eea 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -38,7 +38,7 @@ use std::{ borrow::Cow, cmp::Ordering, fmt::{Debug, Display, Write}, - ops::Bound, + ops::{Bound, ControlFlow, Deref}, path::PathBuf, }; @@ -1080,224 +1080,83 @@ impl Value { } /// Follow a given cell path into the value: for example accessing select elements in a stream or list - pub fn follow_cell_path( - self, + pub fn follow_cell_path<'out>( + &'out self, cell_path: &[PathMember], insensitive: bool, - ) -> Result { - let mut current = self; + ) -> Result, ShellError> { + enum MultiLife<'out, 'local, T> + where + 'out: 'local, + T: ?Sized, + { + Out(&'out T), + Local(&'local T), + } - for member in cell_path { - match member { - PathMember::Int { - val: count, - span: origin_span, - optional, - } => { - // Treat a numeric path member as `select ` - match current { - Value::List { mut vals, .. } => { - if *count < vals.len() { - // `vals` is owned and will be dropped right after this, - // so we can `swap_remove` the value at index `count` - // without worrying about preserving order. - current = vals.swap_remove(*count); - } else if *optional { - return Ok(Value::nothing(*origin_span)); // short-circuit - } else if vals.is_empty() { - return Err(ShellError::AccessEmptyContent { span: *origin_span }); - } else { - return Err(ShellError::AccessBeyondEnd { - max_idx: vals.len() - 1, - span: *origin_span, - }); - } - } - Value::Binary { val, .. } => { - if let Some(item) = val.get(*count) { - current = Value::int(*item as i64, *origin_span); - } else if *optional { - return Ok(Value::nothing(*origin_span)); // short-circuit - } else if val.is_empty() { - return Err(ShellError::AccessEmptyContent { span: *origin_span }); - } else { - return Err(ShellError::AccessBeyondEnd { - max_idx: val.len() - 1, - span: *origin_span, - }); - } - } - Value::Range { ref val, .. } => { - if let Some(item) = val - .into_range_iter(current.span(), Signals::empty()) - .nth(*count) - { - current = item; - } else if *optional { - return Ok(Value::nothing(*origin_span)); // short-circuit - } else { - return Err(ShellError::AccessBeyondEndOfStream { - span: *origin_span, - }); - } - } - Value::Custom { ref val, .. } => { - current = - match val.follow_path_int(current.span(), *count, *origin_span) { - Ok(val) => val, - Err(err) => { - if *optional { - return Ok(Value::nothing(*origin_span)); - // short-circuit - } else { - return Err(err); - } - } - }; - } - Value::Nothing { .. } if *optional => { - return Ok(Value::nothing(*origin_span)); // short-circuit - } - // Records (and tables) are the only built-in which support column names, - // so only use this message for them. - Value::Record { .. } => { - return Err(ShellError::TypeMismatch { - err_message:"Can't access record values with a row index. Try specifying a column name instead".into(), - span: *origin_span, - }); - } - Value::Error { error, .. } => return Err(*error), - x => { - return Err(ShellError::IncompatiblePathAccess { - type_name: format!("{}", x.get_type()), - span: *origin_span, - }); - } - } - } - PathMember::String { - val: column_name, - span: origin_span, - optional, - } => { - let span = current.span(); + impl<'out, 'local, T> Deref for MultiLife<'out, 'local, T> + where + 'out: 'local, + T: ?Sized, + { + type Target = T; - match current { - Value::Record { mut val, .. } => { - // Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected. - if let Some(found) = val.to_mut().iter_mut().rev().find(|x| { - if insensitive { - x.0.eq_ignore_case(column_name) - } else { - x.0 == column_name - } - }) { - current = std::mem::take(found.1); - } else if *optional { - return Ok(Value::nothing(*origin_span)); // short-circuit - } else if let Some(suggestion) = - did_you_mean(val.columns(), column_name) - { - return Err(ShellError::DidYouMean { - suggestion, - span: *origin_span, - }); - } else { - return Err(ShellError::CantFindColumn { - col_name: column_name.clone(), - span: Some(*origin_span), - src_span: span, - }); - } - } - // String access of Lists always means Table access. - // Create a List which contains each matching value for contained - // records in the source list. - Value::List { vals, .. } => { - let list = vals - .into_iter() - .map(|val| { - let val_span = val.span(); - match val { - Value::Record { mut val, .. } => { - if let Some(found) = - val.to_mut().iter_mut().rev().find(|x| { - if insensitive { - x.0.eq_ignore_case(column_name) - } else { - x.0 == column_name - } - }) - { - Ok(std::mem::take(found.1)) - } else if *optional { - Ok(Value::nothing(*origin_span)) - } else if let Some(suggestion) = - did_you_mean(val.columns(), column_name) - { - Err(ShellError::DidYouMean { - suggestion, - span: *origin_span, - }) - } else { - Err(ShellError::CantFindColumn { - col_name: column_name.clone(), - span: Some(*origin_span), - src_span: val_span, - }) - } - } - Value::Nothing { .. } if *optional => { - Ok(Value::nothing(*origin_span)) - } - _ => Err(ShellError::CantFindColumn { - col_name: column_name.clone(), - span: Some(*origin_span), - src_span: val_span, - }), - } - }) - .collect::>()?; - - current = Value::list(list, span); - } - Value::Custom { ref val, .. } => { - current = match val.follow_path_string( - current.span(), - column_name.clone(), - *origin_span, - ) { - Ok(val) => val, - Err(err) => { - if *optional { - return Ok(Value::nothing(*origin_span)); - // short-circuit - } else { - return Err(err); - } - } - } - } - Value::Nothing { .. } if *optional => { - return Ok(Value::nothing(*origin_span)); // short-circuit - } - Value::Error { error, .. } => return Err(*error), - x => { - return Err(ShellError::IncompatiblePathAccess { - type_name: format!("{}", x.get_type()), - span: *origin_span, - }); - } - } + fn deref(&self) -> &Self::Target { + match *self { + MultiLife::Out(x) => x, + MultiLife::Local(x) => x, } } } + + // A dummy value is required, otherwise rust doesn't allow references, which we need for + // the `std::ptr::eq` comparison + let mut store: Value = Value::test_nothing(); + let mut current: MultiLife<'out, '_, Value> = MultiLife::Out(self); + + for member in cell_path { + current = match current { + MultiLife::Out(current) => match get_value_member(current, member, insensitive)? { + ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))), + ControlFlow::Continue(x) => match x { + Cow::Borrowed(x) => MultiLife::Out(x), + Cow::Owned(x) => { + store = x; + MultiLife::Local(&store) + } + }, + }, + MultiLife::Local(current) => { + match get_value_member(current, member, insensitive)? { + ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))), + ControlFlow::Continue(x) => match x { + Cow::Borrowed(x) => MultiLife::Local(x), + Cow::Owned(x) => { + store = x; + MultiLife::Local(&store) + } + }, + } + } + }; + } + // If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now. // Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used. - if let Value::Error { error, .. } = current { - Err(*error) + if let Value::Error { error, .. } = &*current { + Err(error.as_ref().clone()) } else { - Ok(current) + Ok(match current { + MultiLife::Out(x) => Cow::Borrowed(x), + MultiLife::Local(x) => { + let x = if std::ptr::eq(x, &store) { + store + } else { + x.clone() + }; + Cow::Owned(x) + } + }) } } @@ -1307,9 +1166,7 @@ impl Value { cell_path: &[PathMember], callback: Box Value>, ) -> Result<(), ShellError> { - let orig = self.clone(); - - let new_val = callback(&orig.follow_cell_path(cell_path, false)?); + let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref()); match new_val { Value::Error { error, .. } => Err(*error), @@ -1409,9 +1266,7 @@ impl Value { cell_path: &[PathMember], callback: Box Value + 'a>, ) -> Result<(), ShellError> { - let orig = self.clone(); - - let new_val = callback(&orig.follow_cell_path(cell_path, false)?); + let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref()); match new_val { Value::Error { error, .. } => Err(*error), @@ -2147,6 +2002,198 @@ impl Value { } } +fn get_value_member<'a>( + current: &'a Value, + member: &PathMember, + insensitive: bool, +) -> Result>, ShellError> { + match member { + PathMember::Int { + val: count, + span: origin_span, + optional, + } => { + // Treat a numeric path member as `select ` + match current { + Value::List { vals, .. } => { + if *count < vals.len() { + Ok(ControlFlow::Continue(Cow::Borrowed(&vals[*count]))) + } else if *optional { + Ok(ControlFlow::Break(*origin_span)) + // short-circuit + } else if vals.is_empty() { + Err(ShellError::AccessEmptyContent { span: *origin_span }) + } else { + Err(ShellError::AccessBeyondEnd { + max_idx: vals.len() - 1, + span: *origin_span, + }) + } + } + Value::Binary { val, .. } => { + if let Some(item) = val.get(*count) { + Ok(ControlFlow::Continue(Cow::Owned(Value::int( + *item as i64, + *origin_span, + )))) + } else if *optional { + Ok(ControlFlow::Break(*origin_span)) + // short-circuit + } else if val.is_empty() { + Err(ShellError::AccessEmptyContent { span: *origin_span }) + } else { + Err(ShellError::AccessBeyondEnd { + max_idx: val.len() - 1, + span: *origin_span, + }) + } + } + Value::Range { ref val, .. } => { + if let Some(item) = val + .into_range_iter(current.span(), Signals::empty()) + .nth(*count) + { + Ok(ControlFlow::Continue(Cow::Owned(item))) + } else if *optional { + Ok(ControlFlow::Break(*origin_span)) + // short-circuit + } else { + Err(ShellError::AccessBeyondEndOfStream { + span: *origin_span, + }) + } + } + Value::Custom { ref val, .. } => { + match val.follow_path_int(current.span(), *count, *origin_span) + { + Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))), + Err(err) => { + if *optional { + Ok(ControlFlow::Break(*origin_span)) + // short-circuit + } else { + Err(err) + } + } + } + } + Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)), + // Records (and tables) are the only built-in which support column names, + // so only use this message for them. + Value::Record { .. } => Err(ShellError::TypeMismatch { + err_message:"Can't access record values with a row index. Try specifying a column name instead".into(), + span: *origin_span, + }), + Value::Error { error, .. } => Err(*error.clone()), + x => Err(ShellError::IncompatiblePathAccess { type_name: format!("{}", x.get_type()), span: *origin_span }), + } + } + PathMember::String { + val: column_name, + span: origin_span, + optional, + } => { + let span = current.span(); + match current { + Value::Record { val, .. } => { + if let Some(found) = val.iter().rev().find(|x| { + if insensitive { + x.0.eq_ignore_case(column_name) + } else { + x.0 == column_name + } + }) { + Ok(ControlFlow::Continue(Cow::Borrowed(found.1))) + } else if *optional { + Ok(ControlFlow::Break(*origin_span)) + // short-circuit + } else if let Some(suggestion) = did_you_mean(val.columns(), column_name) { + Err(ShellError::DidYouMean { + suggestion, + span: *origin_span, + }) + } else { + Err(ShellError::CantFindColumn { + col_name: column_name.clone(), + span: Some(*origin_span), + src_span: span, + }) + } + } + // String access of Lists always means Table access. + // Create a List which contains each matching value for contained + // records in the source list. + Value::List { vals, .. } => { + let list = vals + .iter() + .map(|val| { + let val_span = val.span(); + match val { + Value::Record { val, .. } => { + if let Some(found) = val.iter().rev().find(|x| { + if insensitive { + x.0.eq_ignore_case(column_name) + } else { + x.0 == column_name + } + }) { + Ok(found.1.clone()) + } else if *optional { + Ok(Value::nothing(*origin_span)) + } else if let Some(suggestion) = + did_you_mean(val.columns(), column_name) + { + Err(ShellError::DidYouMean { + suggestion, + span: *origin_span, + }) + } else { + Err(ShellError::CantFindColumn { + col_name: column_name.clone(), + span: Some(*origin_span), + src_span: val_span, + }) + } + } + Value::Nothing { .. } if *optional => { + Ok(Value::nothing(*origin_span)) + } + _ => Err(ShellError::CantFindColumn { + col_name: column_name.clone(), + span: Some(*origin_span), + src_span: val_span, + }), + } + }) + .collect::>()?; + + Ok(ControlFlow::Continue(Cow::Owned(Value::list(list, span)))) + } + Value::Custom { ref val, .. } => { + match val.follow_path_string(current.span(), column_name.clone(), *origin_span) + { + Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))), + Err(err) => { + if *optional { + Ok(ControlFlow::Break(*origin_span)) + // short-circuit + } else { + Err(err) + } + } + } + } + Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)), + Value::Error { error, .. } => Err(error.as_ref().clone()), + x => Err(ShellError::IncompatiblePathAccess { + type_name: format!("{}", x.get_type()), + span: *origin_span, + }), + } + } + } +} + impl Default for Value { fn default() -> Self { Value::Nothing { diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs index acc74635ed..c479cd4457 100644 --- a/crates/nu_plugin_inc/src/inc.rs +++ b/crates/nu_plugin_inc/src/inc.rs @@ -91,8 +91,7 @@ impl Inc { pub fn inc(&self, head: Span, value: &Value) -> Result { if let Some(cell_path) = &self.cell_path { - let working_value = value.clone(); - let cell_value = working_value.follow_cell_path(&cell_path.members, false)?; + let cell_value = value.follow_cell_path(&cell_path.members, false)?; let cell_value = self.inc_value(head, &cell_value)?;