refactor Value::follow_cell_path to reduce clones and return Cow (#15640)

# Description
While working on something else, I noticed that
`Value::follow_cell_path` receives `self`.

While it would be ideal for the signature to be `(&'a self, cell_path)
-> &'a Value`, that's not possible because:
1. Selecting a row from a list and field from a record can be done with
a reference but selecting a column from a table requires creating a new
list.
2. `Value::Custom` returns new `Value`s when indexed.

So the signature becomes `(&'a self, cell_path) -> Cow<'a, Value>`.

Another complication that arises is, once a new `Value` is created, and
it is further indexed, the `current` variable
1. can't be `&'a Value`, as the lifetime requirement means it can't
refer to local variables
2. _shouldn't_ be `Cow<'a, Value>`, as once it becomes an owned value,
it can't be borrowed ever again, as `current` is derived from its
previous value in further iterations. So once it's owned, it can't be
indexed by reference, leading to more clones

We need `current` to have _two_ possible lifetimes
1. `'out`: references derived from `&self`
2. `'local`: references derived from an owned value stored in a local
variable

```rust
enum MultiLife<'out, 'local, T>
where
    'out: 'local,
    T: ?Sized,
{
    Out(&'out T),
    Local(&'local T),
}
```
With `current: MultiLife<'out, '_, Value>`, we can traverse values with
minimal clones, and we can transform it to `Cow<'out, Value>` easily
(`MultiLife::Out -> Cow::Borrowed, MultiLife::Local -> Cow::Owned`) to
return it

# User-Facing Changes

# Tests + Formatting

# After Submitting

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
Bahex
2025-05-01 17:43:57 +03:00
committed by GitHub
parent deca337a56
commit 55de232a1c
19 changed files with 373 additions and 336 deletions

View File

@ -269,6 +269,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
input = eval_subexpression::<D>(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::<D>(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 {

View File

@ -694,9 +694,8 @@ fn eval_instruction<D: DebugContext>(
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)