From e3f59910b87ddbee24ec5e1de059dd87f7b56fe3 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sun, 1 Sep 2024 10:02:12 -0700 Subject: [PATCH] Implement `IntoValue` for more types (#13744) # Description Implements `IntoValue` for `&str` and `DateTime` as well as other nushell types like `Record` and `Closure`. Also allows `HashMap`s with keys besides `String` to implement `IntoValue`. --- crates/nu-cli/tests/completions/mod.rs | 10 +-- crates/nu-derive-value/src/into.rs | 2 +- crates/nu-protocol/src/value/into_value.rs | 71 ++++++++++++++++++---- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 70ea1ea99f..3dc4443bc3 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -377,7 +377,7 @@ fn file_completions_with_mixed_separators() { file(dir.join("lib-dir1").join("baz.nu")), file(dir.join("lib-dir1").join("xyzzy.nu")), ]; - let expecetd_slash_paths: Vec = expected_paths + let expected_slash_paths: Vec = expected_paths .iter() .map(|s| s.replace(MAIN_SEPARATOR, "/")) .collect(); @@ -385,22 +385,22 @@ fn file_completions_with_mixed_separators() { let target_dir = format!("ls {dir_str}/lib-dir1/"); let suggestions = completer.complete(&target_dir, target_dir.len()); - match_suggestions(&expecetd_slash_paths, &suggestions); + match_suggestions(&expected_slash_paths, &suggestions); let target_dir = format!("cp {dir_str}\\lib-dir1/"); let suggestions = completer.complete(&target_dir, target_dir.len()); - match_suggestions(&expecetd_slash_paths, &suggestions); + match_suggestions(&expected_slash_paths, &suggestions); let target_dir = format!("ls {dir_str}/lib-dir1\\/"); let suggestions = completer.complete(&target_dir, target_dir.len()); - match_suggestions(&expecetd_slash_paths, &suggestions); + match_suggestions(&expected_slash_paths, &suggestions); let target_dir = format!("ls {dir_str}\\lib-dir1\\/"); let suggestions = completer.complete(&target_dir, target_dir.len()); - match_suggestions(&expecetd_slash_paths, &suggestions); + match_suggestions(&expected_slash_paths, &suggestions); let target_dir = format!("ls {dir_str}\\lib-dir1\\"); let suggestions = completer.complete(&target_dir, target_dir.len()); diff --git a/crates/nu-derive-value/src/into.rs b/crates/nu-derive-value/src/into.rs index 3dfb92d28e..370558005a 100644 --- a/crates/nu-derive-value/src/into.rs +++ b/crates/nu-derive-value/src/into.rs @@ -191,7 +191,7 @@ fn enum_into_value( .as_str() .to_case(container_attrs.rename_all.unwrap_or(Case::Snake)); match &variant.fields { - // In the future we can implement more complexe enums here. + // In the future we can implement more complex enums here. Fields::Named(fields) => Err(DeriveError::UnsupportedEnums { fields_span: fields.span(), }), diff --git a/crates/nu-protocol/src/value/into_value.rs b/crates/nu-protocol/src/value/into_value.rs index d95de9d96a..c7b776071a 100644 --- a/crates/nu-protocol/src/value/into_value.rs +++ b/crates/nu-protocol/src/value/into_value.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; - -use crate::{Record, ShellError, Span, Value}; +use crate::{ast::CellPath, engine::Closure, Range, Record, ShellError, Span, Value}; +use chrono::{DateTime, FixedOffset}; +use std::{borrow::Borrow, collections::HashMap}; /// A trait for converting a value into a [`Value`]. /// @@ -152,6 +152,12 @@ impl IntoValue for String { } } +impl IntoValue for &str { + fn into_value(self, span: Span) -> Value { + Value::string(self, span) + } +} + impl IntoValue for Vec where T: IntoValue, @@ -173,24 +179,59 @@ where } } -impl IntoValue for HashMap +impl IntoValue for HashMap where + K: Borrow + Into, V: IntoValue, { fn into_value(self, span: Span) -> Value { - let mut record = Record::new(); - for (k, v) in self.into_iter() { - // Using `push` is fine as a hashmaps have unique keys. - // To ensure this uniqueness, we only allow hashmaps with strings as - // keys and not keys which implement `Into` or `ToString`. - record.push(k, v.into_value(span)); - } - Value::record(record, span) + // The `Borrow` constraint is to ensure uniqueness, as implementations of `Borrow` + // must uphold by certain properties (e.g., `(x == y) == (x.borrow() == y.borrow())`. + // + // The `Into` constraint is necessary for us to convert the key into a `String`. + // Most types that implement `Borrow` also implement `Into`. + // Implementations of `Into` must also be lossless and value-preserving conversions. + // So, when combined with the `Borrow` constraint, this means that the converted + // `String` keys should be unique. + self.into_iter() + .map(|(k, v)| (k.into(), v.into_value(span))) + .collect::() + .into_value(span) } } // Nu Types +impl IntoValue for Range { + fn into_value(self, span: Span) -> Value { + Value::range(self, span) + } +} + +impl IntoValue for Record { + fn into_value(self, span: Span) -> Value { + Value::record(self, span) + } +} + +impl IntoValue for Closure { + fn into_value(self, span: Span) -> Value { + Value::closure(self, span) + } +} + +impl IntoValue for ShellError { + fn into_value(self, span: Span) -> Value { + Value::error(self, span) + } +} + +impl IntoValue for CellPath { + fn into_value(self, span: Span) -> Value { + Value::cell_path(self, span) + } +} + impl IntoValue for Value { fn into_value(self, span: Span) -> Value { self.with_span(span) @@ -199,6 +240,12 @@ impl IntoValue for Value { // Foreign Types +impl IntoValue for DateTime { + fn into_value(self, span: Span) -> Value { + Value::date(self, span) + } +} + impl IntoValue for bytes::Bytes { fn into_value(self, span: Span) -> Value { Value::binary(self.to_vec(), span)