Ian Manske 847646e44e
Remove lazy records (#12682)
# Description
Removes lazy records from the language, following from the reasons
outlined in #12622. Namely, this should make semantics more clear and
will eliminate concerns regarding maintainability.

# User-Facing Changes
- Breaking change: `lazy make` is removed.
- Breaking change: `describe --collect-lazyrecords` flag is removed.
- `sys` and `debug info` now return regular records.

# After Submitting
- Update nushell book if necessary.
- Explore new `sys` and `debug info` APIs to prevent them from taking
too long (e.g., subcommands or taking an optional column/cell-path
argument).
2024-05-03 08:36:10 +08:00

222 lines
7.5 KiB
Rust

use std::cmp::Ordering;
use nu_protocol::{ast::Operator, CustomValue, ShellError, Span, Value};
use nu_utils::SharedCow;
use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests;
/// An opaque container for a custom value that is handled fully by a plugin.
///
/// This is the only type of custom value that is allowed to cross the plugin serialization
/// boundary.
///
/// The plugin is responsible for ensuring that local plugin custom values are converted to and from
/// [`PluginCustomValue`] on the boundary.
///
/// The engine is responsible for adding tracking the source of the custom value, ensuring that only
/// [`PluginCustomValue`] is contained within any values sent, and that the source of any values
/// sent matches the plugin it is being sent to.
///
/// Most of the [`CustomValue`] methods on this type will result in a panic. The source must be
/// added (see `nu_plugin_engine::PluginCustomValueWithSource`) in order to implement the
/// functionality via plugin calls.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PluginCustomValue(SharedCow<SharedContent>);
/// Content shared across copies of a plugin custom value.
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SharedContent {
/// The name of the type of the custom value as defined by the plugin (`type_name()`)
name: String,
/// The bincoded representation of the custom value on the plugin side
data: Vec<u8>,
/// True if the custom value should notify the source if all copies of it are dropped.
///
/// This is not serialized if `false`, since most custom values don't need it.
#[serde(default, skip_serializing_if = "is_false")]
notify_on_drop: bool,
}
fn is_false(b: &bool) -> bool {
!b
}
#[typetag::serde]
impl CustomValue for PluginCustomValue {
fn clone_value(&self, span: Span) -> Value {
self.clone().into_value(span)
}
fn type_name(&self) -> String {
self.name().to_owned()
}
fn to_base_value(&self, _span: Span) -> Result<Value, ShellError> {
panic!("to_base_value() not available on plugin custom value without source");
}
fn follow_path_int(
&self,
_self_span: Span,
_index: usize,
_path_span: Span,
) -> Result<Value, ShellError> {
panic!("follow_path_int() not available on plugin custom value without source");
}
fn follow_path_string(
&self,
_self_span: Span,
_column_name: String,
_path_span: Span,
) -> Result<Value, ShellError> {
panic!("follow_path_string() not available on plugin custom value without source");
}
fn partial_cmp(&self, _other: &Value) -> Option<Ordering> {
panic!("partial_cmp() not available on plugin custom value without source");
}
fn operation(
&self,
_lhs_span: Span,
_operator: Operator,
_op_span: Span,
_right: &Value,
) -> Result<Value, ShellError> {
panic!("operation() not available on plugin custom value without source");
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl PluginCustomValue {
/// Create a new [`PluginCustomValue`].
pub fn new(name: String, data: Vec<u8>, notify_on_drop: bool) -> PluginCustomValue {
PluginCustomValue(SharedCow::new(SharedContent {
name,
data,
notify_on_drop,
}))
}
/// Create a [`Value`] containing this custom value.
pub fn into_value(self, span: Span) -> Value {
Value::custom(Box::new(self), span)
}
/// The name of the type of the custom value as defined by the plugin (`type_name()`)
pub fn name(&self) -> &str {
&self.0.name
}
/// The bincoded representation of the custom value on the plugin side
pub fn data(&self) -> &[u8] {
&self.0.data
}
/// True if the custom value should notify the source if all copies of it are dropped.
pub fn notify_on_drop(&self) -> bool {
self.0.notify_on_drop
}
/// Count the number of shared copies of this [`PluginCustomValue`].
pub fn ref_count(&self) -> usize {
SharedCow::ref_count(&self.0)
}
/// Serialize a custom value into a [`PluginCustomValue`]. This should only be done on the
/// plugin side.
pub fn serialize_from_custom_value(
custom_value: &dyn CustomValue,
span: Span,
) -> Result<PluginCustomValue, ShellError> {
let name = custom_value.type_name();
let notify_on_drop = custom_value.notify_plugin_on_drop();
bincode::serialize(custom_value)
.map(|data| PluginCustomValue::new(name, data, notify_on_drop))
.map_err(|err| ShellError::CustomValueFailedToEncode {
msg: err.to_string(),
span,
})
}
/// Deserialize a [`PluginCustomValue`] into a `Box<dyn CustomValue>`. This should only be done
/// on the plugin side.
pub fn deserialize_to_custom_value(
&self,
span: Span,
) -> Result<Box<dyn CustomValue>, ShellError> {
bincode::deserialize::<Box<dyn CustomValue>>(self.data()).map_err(|err| {
ShellError::CustomValueFailedToDecode {
msg: err.to_string(),
span,
}
})
}
/// Convert all plugin-native custom values to [`PluginCustomValue`] within the given `value`,
/// recursively. This should only be done on the plugin side.
pub fn serialize_custom_values_in(value: &mut Value) -> Result<(), ShellError> {
value.recurse_mut(&mut |value| {
let span = value.span();
match value {
Value::Custom { ref val, .. } => {
if val.as_any().downcast_ref::<PluginCustomValue>().is_some() {
// Already a PluginCustomValue
Ok(())
} else {
let serialized = Self::serialize_from_custom_value(&**val, span)?;
*value = Value::custom(Box::new(serialized), span);
Ok(())
}
}
_ => Ok(()),
}
})
}
/// Convert all [`PluginCustomValue`]s to plugin-native custom values within the given `value`,
/// recursively. This should only be done on the plugin side.
pub fn deserialize_custom_values_in(value: &mut Value) -> Result<(), ShellError> {
value.recurse_mut(&mut |value| {
let span = value.span();
match value {
Value::Custom { ref val, .. } => {
if let Some(val) = val.as_any().downcast_ref::<PluginCustomValue>() {
let deserialized = val.deserialize_to_custom_value(span)?;
*value = Value::custom(deserialized, span);
Ok(())
} else {
// Already not a PluginCustomValue
Ok(())
}
}
_ => Ok(()),
}
})
}
/// Render any custom values in the `Value` using `to_base_value()`
pub fn render_to_base_value_in(value: &mut Value) -> Result<(), ShellError> {
value.recurse_mut(&mut |value| {
let span = value.span();
match value {
Value::Custom { ref val, .. } => {
*value = val.to_base_value(span)?;
Ok(())
}
_ => Ok(()),
}
})
}
}