Support for all custom value operations on plugin custom values (#12088)

# Description

Adds support for the following operations on plugin custom values, in
addition to `to_base_value` which was already present:

- `follow_path_int()`
- `follow_path_string()`
- `partial_cmp()`
- `operation()`
- `Drop` (notification, if opted into with
`CustomValue::notify_plugin_on_drop`)

There are additionally customizable methods within the `Plugin` and
`StreamingPlugin` traits for implementing these functions in a way that
requires access to the plugin state, as a registered handle model such
as might be used in a dataframes plugin would.

`Value::append` was also changed to handle custom values correctly.

# User-Facing Changes

- Signature of `CustomValue::follow_path_string` and
`CustomValue::follow_path_int` changed to give access to the span of the
custom value itself, useful for some errors.
- Plugins using custom values have to be recompiled because the engine
will try to do custom value operations that aren't supported
- Plugins can do more things 🎉 

# Tests + Formatting
Tests were added for all of the new custom values functionality.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Document protocol reference `CustomValueOp` variants:
  - [ ] `FollowPathInt`
  - [ ] `FollowPathString`
  - [ ] `PartialCmp`
  - [ ] `Operation`
  - [ ] `Dropped`
- [ ] Document `notify_on_drop` optional field in `PluginCustomValue`
This commit is contained in:
Devyn Cairns
2024-03-12 02:37:08 -07:00
committed by GitHub
parent 8a250d2e08
commit 73f3c0b60b
19 changed files with 1065 additions and 156 deletions

View File

@ -26,18 +26,30 @@ pub trait CustomValue: fmt::Debug + Send + Sync {
fn as_any(&self) -> &dyn std::any::Any;
/// Follow cell path by numeric index (e.g. rows)
fn follow_path_int(&self, _count: usize, span: Span) -> Result<Value, ShellError> {
fn follow_path_int(
&self,
self_span: Span,
index: usize,
path_span: Span,
) -> Result<Value, ShellError> {
let _ = (self_span, index);
Err(ShellError::IncompatiblePathAccess {
type_name: self.value_string(),
span,
span: path_span,
})
}
/// Follow cell path by string key (e.g. columns)
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
fn follow_path_string(
&self,
self_span: Span,
column_name: String,
path_span: Span,
) -> Result<Value, ShellError> {
let _ = (self_span, column_name);
Err(ShellError::IncompatiblePathAccess {
type_name: self.value_string(),
span,
span: path_span,
})
}
@ -54,11 +66,23 @@ pub trait CustomValue: fmt::Debug + Send + Sync {
/// Default impl raises [`ShellError::UnsupportedOperator`].
fn operation(
&self,
_lhs_span: Span,
lhs_span: Span,
operator: Operator,
op: Span,
_right: &Value,
right: &Value,
) -> Result<Value, ShellError> {
let _ = (lhs_span, right);
Err(ShellError::UnsupportedOperator { operator, span: op })
}
/// For custom values in plugins: return `true` here if you would like to be notified when all
/// copies of this custom value are dropped in the engine.
///
/// The notification will take place via
/// [`.custom_value_dropped()`](crate::StreamingPlugin::custom_value_dropped) on the plugin.
///
/// The default is `false`.
fn notify_plugin_on_drop(&self) -> bool {
false
}
}

View File

@ -1106,18 +1106,19 @@ impl Value {
});
}
}
Value::CustomValue { val, .. } => {
current = match val.follow_path_int(*count, *origin_span) {
Ok(val) => val,
Err(err) => {
if *optional {
return Ok(Value::nothing(*origin_span));
// short-circuit
} else {
return Err(err);
Value::CustomValue { 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
@ -1249,8 +1250,22 @@ impl Value {
current = Value::list(list, span);
}
Value::CustomValue { val, .. } => {
current = val.follow_path_string(column_name.clone(), *origin_span)?;
Value::CustomValue { 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
@ -2652,6 +2667,9 @@ impl Value {
val.extend(rhs);
Ok(Value::binary(val, span))
}
(Value::CustomValue { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Append), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),