feat(tvix/serde): implement enum deserialisation
Implements externally tagged enum deserialisation. Other serialisation methods are handled by serde internally using the existing methods. See the tests for examples. Change-Id: Ic4a9da3b5a32ddbb5918b1512e70c3ac5ce64f04 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7721 Tested-by: BuildkiteCI Autosubmit: tazjin <tazjin@tvl.su> Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
parent
0e88eb83ef
commit
34be6466d4
4 changed files with 189 additions and 7 deletions
|
@ -47,7 +47,7 @@ pub use crate::io::{DummyIO, EvalIO, FileType};
|
||||||
use crate::observer::{CompilerObserver, RuntimeObserver};
|
use crate::observer::{CompilerObserver, RuntimeObserver};
|
||||||
pub use crate::pretty_ast::pretty_print_expr;
|
pub use crate::pretty_ast::pretty_print_expr;
|
||||||
pub use crate::source::SourceCode;
|
pub use crate::source::SourceCode;
|
||||||
pub use crate::value::Value;
|
pub use crate::value::{NixAttrs, NixList, NixString, Value};
|
||||||
pub use crate::vm::run_lambda;
|
pub use crate::vm::run_lambda;
|
||||||
pub use crate::warnings::{EvalWarning, WarningKind};
|
pub use crate::warnings::{EvalWarning, WarningKind};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Deserialisation from Nix to Rust values.
|
//! Deserialisation from Nix to Rust values.
|
||||||
|
|
||||||
use serde::de;
|
|
||||||
use serde::de::value::{MapDeserializer, SeqDeserializer};
|
use serde::de::value::{MapDeserializer, SeqDeserializer};
|
||||||
|
use serde::de::{self, EnumAccess, VariantAccess};
|
||||||
use tvix_eval::Value;
|
use tvix_eval::Value;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
@ -221,14 +221,14 @@ impl<'de> de::Deserializer<'de> for NixDeserializer {
|
||||||
Err(unexpected("string", &self.value))
|
Err(unexpected("string", &self.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_byte_buf<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
|
@ -307,7 +307,7 @@ impl<'de> de::Deserializer<'de> for NixDeserializer {
|
||||||
fn deserialize_tuple_struct<V>(
|
fn deserialize_tuple_struct<V>(
|
||||||
self,
|
self,
|
||||||
_name: &'static str,
|
_name: &'static str,
|
||||||
len: usize,
|
_len: usize,
|
||||||
visitor: V,
|
visitor: V,
|
||||||
) -> Result<V::Value, Self::Error>
|
) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
|
@ -348,16 +348,27 @@ impl<'de> de::Deserializer<'de> for NixDeserializer {
|
||||||
self.deserialize_map(visitor)
|
self.deserialize_map(visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method is responsible for deserializing the externally
|
||||||
|
// tagged enum variant serialisation.
|
||||||
fn deserialize_enum<V>(
|
fn deserialize_enum<V>(
|
||||||
self,
|
self,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
variants: &'static [&'static str],
|
_variants: &'static [&'static str],
|
||||||
visitor: V,
|
visitor: V,
|
||||||
) -> Result<V::Value, Self::Error>
|
) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
todo!()
|
match self.value {
|
||||||
|
// a string represents a unit variant
|
||||||
|
Value::String(s) => visitor.visit_enum(de::value::StrDeserializer::new(s.as_str())),
|
||||||
|
|
||||||
|
// an attribute set however represents an externally
|
||||||
|
// tagged enum with content
|
||||||
|
Value::Attrs(attrs) => visitor.visit_enum(Enum(*attrs)),
|
||||||
|
|
||||||
|
_ => Err(unexpected(name, &self.value)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
@ -374,3 +385,61 @@ impl<'de> de::Deserializer<'de> for NixDeserializer {
|
||||||
visitor.visit_unit()
|
visitor.visit_unit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Enum(tvix_eval::NixAttrs);
|
||||||
|
|
||||||
|
impl<'de> EnumAccess<'de> for Enum {
|
||||||
|
type Error = Error;
|
||||||
|
type Variant = NixDeserializer;
|
||||||
|
|
||||||
|
// TODO: pass the known variants down here and check against them
|
||||||
|
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
|
||||||
|
where
|
||||||
|
V: de::DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
if self.0.len() != 1 {
|
||||||
|
return Err(Error::AmbiguousEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (key, value) = self.0.into_iter().next().expect("length asserted above");
|
||||||
|
let val = seed.deserialize(de::value::StrDeserializer::<Error>::new(key.as_str()))?;
|
||||||
|
|
||||||
|
Ok((val, NixDeserializer::new(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> VariantAccess<'de> for NixDeserializer {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn unit_variant(self) -> Result<(), Self::Error> {
|
||||||
|
// If this case is hit, a user specified the name of a unit
|
||||||
|
// enum variant but gave it content. Unit enum deserialisation
|
||||||
|
// is handled in `deserialize_enum` above.
|
||||||
|
Err(Error::UnitEnumContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
|
||||||
|
where
|
||||||
|
T: de::DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
seed.deserialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where
|
||||||
|
V: de::Visitor<'de>,
|
||||||
|
{
|
||||||
|
de::Deserializer::deserialize_seq(self, visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_variant<V>(
|
||||||
|
self,
|
||||||
|
_fields: &'static [&'static str],
|
||||||
|
visitor: V,
|
||||||
|
) -> Result<V::Value, Self::Error>
|
||||||
|
where
|
||||||
|
V: de::Visitor<'de>,
|
||||||
|
{
|
||||||
|
de::Deserializer::deserialize_map(self, visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -95,3 +95,106 @@ fn deserialize_tuple() {
|
||||||
let result: (String, usize) = from_str(r#" [ "foo" 42 ] "#).expect("should deserialize");
|
let result: (String, usize) = from_str(r#" [ "foo" 42 ] "#).expect("should deserialize");
|
||||||
assert_eq!(result, ("foo".into(), 42));
|
assert_eq!(result, ("foo".into(), 42));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_unit_enum() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
enum Foo {
|
||||||
|
Bar,
|
||||||
|
Baz,
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: Foo = from_str("\"Baz\"").expect("should deserialize");
|
||||||
|
assert_eq!(result, Foo::Baz);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_tuple_enum() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
enum Foo {
|
||||||
|
Bar,
|
||||||
|
Baz(String, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: Foo = from_str(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
Baz = [ "Slartibartfast" 42 ];
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(result, Foo::Baz("Slartibartfast".into(), 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_struct_enum() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
enum Foo {
|
||||||
|
Bar,
|
||||||
|
Baz { name: String, age: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: Foo = from_str(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
Baz.name = "Slartibartfast";
|
||||||
|
Baz.age = 42;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Foo::Baz {
|
||||||
|
name: "Slartibartfast".into(),
|
||||||
|
age: 42
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_enum_all() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
enum TestEnum {
|
||||||
|
UnitVariant,
|
||||||
|
TupleVariant(String, String),
|
||||||
|
StructVariant { name: String, age: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: Vec<TestEnum> = from_str(
|
||||||
|
r#"
|
||||||
|
let
|
||||||
|
mkTuple = country: drink: { tuple_variant = [ country drink ]; };
|
||||||
|
in
|
||||||
|
[
|
||||||
|
(mkTuple "UK" "cask ale")
|
||||||
|
|
||||||
|
"unit_variant"
|
||||||
|
|
||||||
|
{
|
||||||
|
struct_variant.name = "Slartibartfast";
|
||||||
|
struct_variant.age = 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
(mkTuple "Russia" "квас")
|
||||||
|
]
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("should deserialize");
|
||||||
|
|
||||||
|
let expected = vec![
|
||||||
|
TestEnum::TupleVariant("UK".into(), "cask ale".into()),
|
||||||
|
TestEnum::UnitVariant,
|
||||||
|
TestEnum::StructVariant {
|
||||||
|
name: "Slartibartfast".into(),
|
||||||
|
age: 42,
|
||||||
|
},
|
||||||
|
TestEnum::TupleVariant("Russia".into(), "квас".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
|
@ -31,6 +31,12 @@ pub enum Error {
|
||||||
errors: Vec<tvix_eval::Error>,
|
errors: Vec<tvix_eval::Error>,
|
||||||
source: tvix_eval::SourceCode,
|
source: tvix_eval::SourceCode,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Could not determine an externally tagged enum representation.
|
||||||
|
AmbiguousEnum,
|
||||||
|
|
||||||
|
/// Attempted to provide content to a unit enum.
|
||||||
|
UnitEnumContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
|
@ -69,6 +75,10 @@ impl Display for Error {
|
||||||
Error::IntegerConversion { got, need } => {
|
Error::IntegerConversion { got, need } => {
|
||||||
write!(f, "i64({}) does not fit in a {}", got, need)
|
write!(f, "i64({}) does not fit in a {}", got, need)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error::AmbiguousEnum => write!(f, "could not determine enum variant: ambiguous keys"),
|
||||||
|
|
||||||
|
Error::UnitEnumContent => write!(f, "provided content for unit enum variant"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue