about summary refs log tree commit diff
path: root/tvix/nix-compat-derive
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/nix-compat-derive')
-rw-r--r--tvix/nix-compat-derive/Cargo.toml1
-rw-r--r--tvix/nix-compat-derive/src/de.rs5
-rw-r--r--tvix/nix-compat-derive/src/internal/attrs.rs148
-rw-r--r--tvix/nix-compat-derive/src/internal/mod.rs4
-rw-r--r--tvix/nix-compat-derive/src/internal/symbol.rs3
-rw-r--r--tvix/nix-compat-derive/src/lib.rs175
-rw-r--r--tvix/nix-compat-derive/src/ser.rs227
7 files changed, 549 insertions, 14 deletions
diff --git a/tvix/nix-compat-derive/Cargo.toml b/tvix/nix-compat-derive/Cargo.toml
index da6d6744e650..bc656b42842c 100644
--- a/tvix/nix-compat-derive/Cargo.toml
+++ b/tvix/nix-compat-derive/Cargo.toml
@@ -14,6 +14,7 @@ syn = { version = "2.0.76", features = ["full", "extra-traits"] }
 [dev-dependencies]
 hex-literal = { workspace = true }
 pretty_assertions = { workspace = true }
+proptest = { workspace = true, features = ["std", "alloc", "tempfile"] }
 rstest = { workspace = true }
 tokio-test = { workspace = true }
 tokio = { workspace = true, features = ["io-util", "macros"] }
diff --git a/tvix/nix-compat-derive/src/de.rs b/tvix/nix-compat-derive/src/de.rs
index 2214254e2b32..e678b50b0533 100644
--- a/tvix/nix-compat-derive/src/de.rs
+++ b/tvix/nix-compat-derive/src/de.rs
@@ -34,6 +34,11 @@ pub fn expand_nix_deserialize_remote(
 ) -> syn::Result<TokenStream> {
     let cx = Context::new();
     let remote = Remote::from_ast(&cx, crate_path, input);
+    if let Some(attrs) = remote.as_ref().map(|r| &r.attrs) {
+        if attrs.from_str.is_none() && attrs.type_from.is_none() && attrs.type_try_from.is_none() {
+            cx.error_spanned(input, "Missing from_str, from or try_from attribute");
+        }
+    }
     cx.check()?;
     let remote = remote.unwrap();
 
diff --git a/tvix/nix-compat-derive/src/internal/attrs.rs b/tvix/nix-compat-derive/src/internal/attrs.rs
index 9ed84aaf8745..d0fa3b008e22 100644
--- a/tvix/nix-compat-derive/src/internal/attrs.rs
+++ b/tvix/nix-compat-derive/src/internal/attrs.rs
@@ -3,7 +3,9 @@ use syn::meta::ParseNestedMeta;
 use syn::parse::Parse;
 use syn::{parse_quote, Attribute, Expr, ExprLit, ExprPath, Lit, Token};
 
-use super::symbol::{Symbol, CRATE, DEFAULT, FROM, FROM_STR, NIX, TRY_FROM, VERSION};
+use super::symbol::{
+    Symbol, CRATE, DEFAULT, DISPLAY, FROM, FROM_STR, INTO, NIX, TRY_FROM, TRY_INTO, VERSION,
+};
 use super::Context;
 
 #[derive(Debug, PartialEq, Eq)]
@@ -104,6 +106,9 @@ pub struct Container {
     pub from_str: Option<syn::Path>,
     pub type_from: Option<syn::Type>,
     pub type_try_from: Option<syn::Type>,
+    pub type_into: Option<syn::Type>,
+    pub type_try_into: Option<syn::Type>,
+    pub display: Default,
     pub crate_path: Option<syn::Path>,
 }
 
@@ -113,6 +118,9 @@ impl Container {
         let mut type_try_from = None;
         let mut crate_path = None;
         let mut from_str = None;
+        let mut type_into = None;
+        let mut type_try_into = None;
+        let mut display = Default::None;
 
         for attr in attrs {
             if attr.path() != NIX {
@@ -125,6 +133,18 @@ impl Container {
                     type_try_from = parse_lit(ctx, &meta, TRY_FROM)?;
                 } else if meta.path == FROM_STR {
                     from_str = Some(meta.path);
+                } else if meta.path == INTO {
+                    type_into = parse_lit(ctx, &meta, INTO)?;
+                } else if meta.path == TRY_INTO {
+                    type_try_into = parse_lit(ctx, &meta, TRY_INTO)?;
+                } else if meta.path == DISPLAY {
+                    if meta.input.peek(Token![=]) {
+                        if let Some(path) = parse_lit(ctx, &meta, DISPLAY)? {
+                            display = Default::Path(path);
+                        }
+                    } else {
+                        display = Default::Default(meta.path);
+                    }
                 } else if meta.path == CRATE {
                     crate_path = parse_lit(ctx, &meta, CRATE)?;
                 } else {
@@ -144,6 +164,9 @@ impl Container {
             from_str,
             type_from,
             type_try_from,
+            type_into,
+            type_try_into,
+            display,
             crate_path,
         }
     }
@@ -342,6 +365,46 @@ mod test {
     }
 
     #[test]
+    fn parse_container_from_str() {
+        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(from_str)])];
+        let ctx = Context::new();
+        let container = Container::from_ast(&ctx, &attrs);
+        ctx.check().unwrap();
+        assert_eq!(
+            container,
+            Container {
+                from_str: Some(parse_quote!(from_str)),
+                type_from: None,
+                type_try_from: None,
+                type_into: None,
+                type_try_into: None,
+                display: Default::None,
+                crate_path: None,
+            }
+        );
+    }
+
+    #[test]
+    fn parse_container_from() {
+        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(from="u64")])];
+        let ctx = Context::new();
+        let container = Container::from_ast(&ctx, &attrs);
+        ctx.check().unwrap();
+        assert_eq!(
+            container,
+            Container {
+                from_str: None,
+                type_from: Some(parse_quote!(u64)),
+                type_try_from: None,
+                type_into: None,
+                type_try_into: None,
+                display: Default::None,
+                crate_path: None,
+            }
+        );
+    }
+
+    #[test]
     fn parse_container_try_from() {
         let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(try_from="u64")])];
         let ctx = Context::new();
@@ -353,6 +416,89 @@ mod test {
                 from_str: None,
                 type_from: None,
                 type_try_from: Some(parse_quote!(u64)),
+                type_into: None,
+                type_try_into: None,
+                display: Default::None,
+                crate_path: None,
+            }
+        );
+    }
+
+    #[test]
+    fn parse_container_into() {
+        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(into="u64")])];
+        let ctx = Context::new();
+        let container = Container::from_ast(&ctx, &attrs);
+        ctx.check().unwrap();
+        assert_eq!(
+            container,
+            Container {
+                from_str: None,
+                type_from: None,
+                type_try_from: None,
+                type_into: Some(parse_quote!(u64)),
+                type_try_into: None,
+                display: Default::None,
+                crate_path: None,
+            }
+        );
+    }
+
+    #[test]
+    fn parse_container_try_into() {
+        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(try_into="u64")])];
+        let ctx = Context::new();
+        let container = Container::from_ast(&ctx, &attrs);
+        ctx.check().unwrap();
+        assert_eq!(
+            container,
+            Container {
+                from_str: None,
+                type_from: None,
+                type_try_from: None,
+                type_into: None,
+                type_try_into: Some(parse_quote!(u64)),
+                display: Default::None,
+                crate_path: None,
+            }
+        );
+    }
+
+    #[test]
+    fn parse_container_display() {
+        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(display)])];
+        let ctx = Context::new();
+        let container = Container::from_ast(&ctx, &attrs);
+        ctx.check().unwrap();
+        assert_eq!(
+            container,
+            Container {
+                from_str: None,
+                type_from: None,
+                type_try_from: None,
+                type_into: None,
+                type_try_into: None,
+                display: Default::Default(parse_quote!(display)),
+                crate_path: None,
+            }
+        );
+    }
+
+    #[test]
+    fn parse_container_display_path() {
+        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(display="Path::display")])];
+        let ctx = Context::new();
+        let container = Container::from_ast(&ctx, &attrs);
+        ctx.check().unwrap();
+        assert_eq!(
+            container,
+            Container {
+                from_str: None,
+                type_from: None,
+                type_try_from: None,
+                type_into: None,
+                type_try_into: None,
+                display: Default::Path(parse_quote!(Path::display)),
                 crate_path: None,
             }
         );
diff --git a/tvix/nix-compat-derive/src/internal/mod.rs b/tvix/nix-compat-derive/src/internal/mod.rs
index 07ef43b6e0bb..aa42d904718d 100644
--- a/tvix/nix-compat-derive/src/internal/mod.rs
+++ b/tvix/nix-compat-derive/src/internal/mod.rs
@@ -154,10 +154,6 @@ impl<'a> Remote<'a> {
         input: &'a inputs::RemoteInput,
     ) -> Option<Remote<'a>> {
         let attrs = attrs::Container::from_ast(ctx, &input.attrs);
-        if attrs.from_str.is_none() && attrs.type_from.is_none() && attrs.type_try_from.is_none() {
-            ctx.error_spanned(input, "Missing from_str, from or try_from attribute");
-            return None;
-        }
         Some(Remote {
             ty: &input.ident,
             attrs,
diff --git a/tvix/nix-compat-derive/src/internal/symbol.rs b/tvix/nix-compat-derive/src/internal/symbol.rs
index ed3fe304eb5d..2bbdc069aa0f 100644
--- a/tvix/nix-compat-derive/src/internal/symbol.rs
+++ b/tvix/nix-compat-derive/src/internal/symbol.rs
@@ -11,6 +11,9 @@ pub const DEFAULT: Symbol = Symbol("default");
 pub const FROM: Symbol = Symbol("from");
 pub const TRY_FROM: Symbol = Symbol("try_from");
 pub const FROM_STR: Symbol = Symbol("from_str");
+pub const INTO: Symbol = Symbol("into");
+pub const TRY_INTO: Symbol = Symbol("try_into");
+pub const DISPLAY: Symbol = Symbol("display");
 pub const CRATE: Symbol = Symbol("crate");
 
 impl PartialEq<Symbol> for Path {
diff --git a/tvix/nix-compat-derive/src/lib.rs b/tvix/nix-compat-derive/src/lib.rs
index 89735cadf315..394473b1cbf8 100644
--- a/tvix/nix-compat-derive/src/lib.rs
+++ b/tvix/nix-compat-derive/src/lib.rs
@@ -6,7 +6,11 @@
 //!         1. [`#[nix(from_str)]`](#nixfrom_str)
 //!         2. [`#[nix(from = "FromType")]`](#nixfrom--fromtype)
 //!         3. [`#[nix(try_from = "FromType")]`](#nixtry_from--fromtype)
-//!         4. [`#[nix(crate = "...")]`](#nixcrate--)
+//!         4. [`#[nix(into = "IntoType")]`](#nixinto--intotype)
+//!         5. [`#[nix(try_into = "IntoType")]`](#nixtry_into--intotype)
+//!         6. [`#[nix(display)]`](#nixdisplay)
+//!         7. [`#[nix(display = "path")]`](#nixdisplay--path)
+//!         8. [`#[nix(crate = "...")]`](#nixcrate--)
 //!     2. [Variant attributes](#variant-attributes)
 //!         1. [`#[nix(version = "range")]`](#nixversion--range)
 //!     3. [Field attributes](#field-attributes)
@@ -17,20 +21,21 @@
 //! ## Overview
 //!
 //! This crate contains derive macros and function-like macros for implementing
-//! `NixDeserialize` with less boilerplate.
+//! `NixDeserialize` and `NixSerialize` with less boilerplate.
 //!
 //! ### Examples
+//!
 //! ```rust
-//! # use nix_compat_derive::NixDeserialize;
+//! # use nix_compat_derive::{NixDeserialize, NixSerialize};
 //! #
-//! #[derive(NixDeserialize)]
+//! #[derive(NixDeserialize, NixSerialize)]
 //! struct Unnamed(u64, String);
 //! ```
 //!
 //! ```rust
-//! # use nix_compat_derive::NixDeserialize;
+//! # use nix_compat_derive::{NixDeserialize, NixSerialize};
 //! #
-//! #[derive(NixDeserialize)]
+//! #[derive(NixDeserialize, NixSerialize)]
 //! struct Fields {
 //!     number: u64,
 //!     message: String,
@@ -38,9 +43,9 @@
 //! ```
 //!
 //! ```rust
-//! # use nix_compat_derive::NixDeserialize;
+//! # use nix_compat_derive::{NixDeserialize, NixSerialize};
 //! #
-//! #[derive(NixDeserialize)]
+//! #[derive(NixDeserialize, NixSerialize)]
 //! struct Ignored;
 //! ```
 //!
@@ -64,7 +69,7 @@
 //! #[derive(NixDeserialize)]
 //! #[nix(crate="nix_compat")] // <-- This is also a container attribute
 //! enum E {
-//!     #[nix(version="..=9")] // <-- This is a variant attribute
+//!     #[nix(version="..10")] // <-- This is a variant attribute
 //!     A(u64),
 //!     #[nix(version="10..")] // <-- This is also a variant attribute
 //!     B(String),
@@ -156,6 +161,114 @@
 //! }
 //! ```
 //!
+//! ##### `#[nix(into = "IntoType")]`
+//!
+//! When `into` is specified the fields are all ignored and instead the
+//! container type is converted to `IntoType` using `Into::into` and
+//! `IntoType` is then serialized. Before converting `Clone::clone` is
+//! called.
+//!
+//! This means that the container must implement `Into<IntoType>` and `Clone`
+//! and `IntoType` must implement `NixSerialize`.
+//!
+//! ###### Example
+//!
+//! ```rust
+//! # use nix_compat_derive::NixSerialize;
+//! #
+//! #[derive(Clone, NixSerialize)]
+//! #[nix(into="usize")]
+//! struct MyValue(usize);
+//! impl From<MyValue> for usize {
+//!     fn from(val: MyValue) -> Self {
+//!         val.0
+//!     }
+//! }
+//! ```
+//!
+//! ##### `#[nix(try_into = "IntoType")]`
+//!
+//! When `try_into` is specified the fields are all ignored and instead the
+//! container type is converted to `IntoType` using `TryInto::try_into` and
+//! `IntoType` is then serialized. Before converting `Clone::clone` is
+//! called.
+//!
+//! This means that the container must implement `TryInto<IntoType>` and
+//! `Clone` and `IntoType` must implement `NixSerialize`.
+//! The error returned from `try_into` also needs to implement `Display`.
+//!
+//! ###### Example
+//!
+//! ```rust
+//! # use nix_compat_derive::NixSerialize;
+//! #
+//! #[derive(Clone, NixSerialize)]
+//! #[nix(try_into="usize")]
+//! struct WrongAnswer(usize);
+//! impl TryFrom<WrongAnswer> for usize {
+//!     type Error = String;
+//!     fn try_from(val: WrongAnswer) -> Result<Self, Self::Error> {
+//!         if val.0 != 42 {
+//!             Ok(val.0)
+//!         } else {
+//!             Err("Got the answer to life the universe and everything".to_string())
+//!         }
+//!     }
+//! }
+//! ```
+//!
+//! ##### `#[nix(display)]`
+//!
+//! When `display` is specified the fields are all ignored and instead the
+//! container must implement `Display` and `NixWrite::write_display` is used to
+//! write the container.
+//!
+//! ###### Example
+//!
+//! ```rust
+//! # use nix_compat_derive::NixSerialize;
+//! # use std::fmt::{Display, Result, Formatter};
+//! #
+//! #[derive(NixSerialize)]
+//! #[nix(display)]
+//! struct WrongAnswer(usize);
+//! impl Display for WrongAnswer {
+//!     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+//!         write!(f, "Wrong Answer = {}", self.0)
+//!     }
+//! }
+//! ```
+//!
+//! ##### `#[nix(display = "path")]`
+//!
+//! When `display` is specified the fields are all ignored and instead the
+//! container the specified path must point to a function that is callable as
+//! `fn(&T) -> impl Display`. The result from this call is then written with
+//! `NixWrite::write_display`.
+//! For example `default = "my_value"` would call `my_value(&self)` and `display =
+//! "AType::empty"` would call `AType::empty(&self)`.
+//!
+//! ###### Example
+//!
+//! ```rust
+//! # use nix_compat_derive::NixSerialize;
+//! # use std::fmt::{Display, Result, Formatter};
+//! #
+//! #[derive(NixSerialize)]
+//! #[nix(display = "format_it")]
+//! struct WrongAnswer(usize);
+//! struct WrongDisplay<'a>(&'a WrongAnswer);
+//! impl<'a> Display for WrongDisplay<'a> {
+//!     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+//!         write!(f, "Wrong Answer = {}", self.0.0)
+//!     }
+//! }
+//!
+//! fn format_it(value: &WrongAnswer) -> impl Display + '_ {
+//!     WrongDisplay(value)
+//! }
+//! ```
+//!
 //! ##### `#[nix(crate = "...")]`
 //!
 //! Specify the path to the `nix-compat` crate instance to use when referring
@@ -175,6 +288,7 @@
 //!
 //! ```rust
 //! # use nix_compat_derive::NixDeserialize;
+//! #
 //! #[derive(NixDeserialize)]
 //! enum Testing {
 //!     #[nix(version="..=18")]
@@ -260,6 +374,7 @@ use syn::{parse_quote, DeriveInput};
 
 mod de;
 mod internal;
+mod ser;
 
 #[proc_macro_derive(NixDeserialize, attributes(nix))]
 pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
@@ -270,6 +385,15 @@ pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
         .into()
 }
 
+#[proc_macro_derive(NixSerialize, attributes(nix))]
+pub fn derive_nix_serialize(item: TokenStream) -> TokenStream {
+    let mut input = syn::parse_macro_input!(item as DeriveInput);
+    let crate_path: syn::Path = parse_quote!(::nix_compat);
+    ser::expand_nix_serialize(crate_path, &mut input)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}
+
 /// Macro to implement `NixDeserialize` on a type.
 /// Sometimes you can't use the deriver to implement `NixDeserialize`
 /// (like when dealing with types in Rust standard library) but don't want
@@ -301,3 +425,36 @@ pub fn nix_deserialize_remote(item: TokenStream) -> TokenStream {
         .unwrap_or_else(syn::Error::into_compile_error)
         .into()
 }
+
+/// Macro to implement `NixSerialize` on a type.
+/// Sometimes you can't use the deriver to implement `NixSerialize`
+/// (like when dealing with types in Rust standard library) but don't want
+/// to implement it yourself. So this macro can be used for those situations
+/// where you would derive using `#[nix(display)]`, `#[nix(display = "path")]`,
+/// `#[nix(store_dir_display)]`, `#[nix(into = "IntoType")]` or
+/// `#[nix(try_into = "IntoType")]` if you could.
+///
+/// #### Example
+///
+/// ```rust
+/// # use nix_compat_derive::nix_serialize_remote;
+/// #
+/// #[derive(Clone)]
+/// struct MyU64(u64);
+///
+/// impl From<MyU64> for u64 {
+///     fn from(value: MyU64) -> Self {
+///         value.0
+///     }
+/// }
+///
+/// nix_serialize_remote!(#[nix(into="u64")] MyU64);
+/// ```
+#[proc_macro]
+pub fn nix_serialize_remote(item: TokenStream) -> TokenStream {
+    let input = syn::parse_macro_input!(item as RemoteInput);
+    let crate_path = parse_quote!(::nix_compat);
+    ser::expand_nix_serialize_remote(crate_path, &input)
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
+}
diff --git a/tvix/nix-compat-derive/src/ser.rs b/tvix/nix-compat-derive/src/ser.rs
new file mode 100644
index 000000000000..47ddfa39366d
--- /dev/null
+++ b/tvix/nix-compat-derive/src/ser.rs
@@ -0,0 +1,227 @@
+use proc_macro2::{Span, TokenStream};
+use quote::{quote, quote_spanned};
+use syn::spanned::Spanned;
+use syn::{DeriveInput, Generics, Path, Type};
+
+use crate::internal::attrs::Default;
+use crate::internal::inputs::RemoteInput;
+use crate::internal::{attrs, Container, Context, Data, Field, Remote, Style, Variant};
+
+pub fn expand_nix_serialize(crate_path: Path, input: &mut DeriveInput) -> syn::Result<TokenStream> {
+    let cx = Context::new();
+    let cont = Container::from_ast(&cx, crate_path, input);
+    cx.check()?;
+    let cont = cont.unwrap();
+
+    let ty = cont.ident_type();
+    let body = nix_serialize_body(&cont);
+    let crate_path = cont.crate_path();
+
+    Ok(nix_serialize_impl(
+        crate_path,
+        &ty,
+        &cont.original.generics,
+        body,
+    ))
+}
+
+pub fn expand_nix_serialize_remote(
+    crate_path: Path,
+    input: &RemoteInput,
+) -> syn::Result<TokenStream> {
+    let cx = Context::new();
+    let remote = Remote::from_ast(&cx, crate_path, input);
+    if let Some(attrs) = remote.as_ref().map(|r| &r.attrs) {
+        if attrs.display.is_none() && attrs.type_into.is_none() && attrs.type_try_into.is_none() {
+            cx.error_spanned(input, "Missing into, try_into or display attribute");
+        }
+    }
+    cx.check()?;
+    let remote = remote.unwrap();
+
+    let crate_path = remote.crate_path();
+    let body = nix_serialize_body_into(crate_path, &remote.attrs).expect("From tokenstream");
+    let generics = Generics::default();
+    Ok(nix_serialize_impl(crate_path, remote.ty, &generics, body))
+}
+
+fn nix_serialize_impl(
+    crate_path: &Path,
+    ty: &Type,
+    generics: &Generics,
+    body: TokenStream,
+) -> TokenStream {
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+    quote! {
+        #[automatically_derived]
+        impl #impl_generics #crate_path::nix_daemon::ser::NixSerialize for #ty #ty_generics
+            #where_clause
+        {
+            async fn serialize<W>(&self, writer: &mut W) -> std::result::Result<(), W::Error>
+                where W: #crate_path::nix_daemon::ser::NixWrite
+            {
+                use #crate_path::nix_daemon::ser::Error as _;
+                #body
+            }
+        }
+    }
+}
+
+fn nix_serialize_body_into(
+    crate_path: &syn::Path,
+    attrs: &attrs::Container,
+) -> Option<TokenStream> {
+    if let Default::Default(span) = &attrs.display {
+        Some(nix_serialize_display(span.span()))
+    } else if let Default::Path(path) = &attrs.display {
+        Some(nix_serialize_display_path(path))
+    } else if let Some(type_into) = attrs.type_into.as_ref() {
+        Some(nix_serialize_into(type_into))
+    } else {
+        attrs
+            .type_try_into
+            .as_ref()
+            .map(|type_try_into| nix_serialize_try_into(crate_path, type_try_into))
+    }
+}
+
+fn nix_serialize_body(cont: &Container) -> TokenStream {
+    if let Some(tokens) = nix_serialize_body_into(cont.crate_path(), &cont.attrs) {
+        tokens
+    } else {
+        match &cont.data {
+            Data::Struct(_style, fields) => nix_serialize_struct(fields),
+            Data::Enum(variants) => nix_serialize_enum(variants),
+        }
+    }
+}
+
+fn nix_serialize_struct(fields: &[Field<'_>]) -> TokenStream {
+    let write_fields = fields.iter().map(|f| {
+        let field = &f.member;
+        let ty = f.ty;
+        let write_value = quote_spanned! {
+            ty.span()=> writer.write_value(&self.#field).await?
+        };
+        if let Some(version) = f.attrs.version.as_ref() {
+            quote! {
+                if (#version).contains(&writer.version().minor()) {
+                    #write_value;
+                }
+            }
+        } else {
+            quote! {
+                #write_value;
+            }
+        }
+    });
+
+    quote! {
+        #(#write_fields)*
+        Ok(())
+    }
+}
+
+fn nix_serialize_variant(variant: &Variant<'_>) -> TokenStream {
+    let ident = variant.ident;
+    let write_fields = variant.fields.iter().map(|f| {
+        let field = f.var_ident();
+        let ty = f.ty;
+        let write_value = quote_spanned! {
+            ty.span()=> writer.write_value(#field).await?
+        };
+        if let Some(version) = f.attrs.version.as_ref() {
+            quote! {
+                if (#version).contains(&writer.version().minor()) {
+                    #write_value;
+                }
+            }
+        } else {
+            quote! {
+                #write_value;
+            }
+        }
+    });
+    let field_names = variant.fields.iter().map(|f| f.var_ident());
+    let destructure = match variant.style {
+        Style::Struct => {
+            quote! {
+                Self::#ident { #(#field_names),* }
+            }
+        }
+        Style::Tuple => {
+            quote! {
+                Self::#ident(#(#field_names),*)
+            }
+        }
+        Style::Unit => quote!(Self::#ident),
+    };
+    let ignore = match variant.style {
+        Style::Struct => {
+            quote! {
+                Self::#ident { .. }
+            }
+        }
+        Style::Tuple => {
+            quote! {
+                Self::#ident(_, ..)
+            }
+        }
+        Style::Unit => quote!(Self::#ident),
+    };
+    let version = &variant.attrs.version;
+    quote! {
+        #destructure if (#version).contains(&writer.version().minor()) => {
+            #(#write_fields)*
+        }
+        #ignore => {
+            return Err(W::Error::invalid_enum(format!("{} is not valid for version {}", stringify!(#ident), writer.version())));
+        }
+    }
+}
+
+fn nix_serialize_enum(variants: &[Variant<'_>]) -> TokenStream {
+    let match_variant = variants
+        .iter()
+        .map(|variant| nix_serialize_variant(variant));
+    quote! {
+        match self {
+            #(#match_variant)*
+        }
+        Ok(())
+    }
+}
+
+fn nix_serialize_into(ty: &Type) -> TokenStream {
+    quote_spanned! {
+        ty.span() =>
+        {
+            let other : #ty = <Self as Clone>::clone(self).into();
+            writer.write_value(&other).await
+        }
+    }
+}
+
+fn nix_serialize_try_into(crate_path: &Path, ty: &Type) -> TokenStream {
+    quote_spanned! {
+        ty.span() =>
+        {
+            use #crate_path::nix_daemon::ser::Error;
+            let other : #ty = <Self as Clone>::clone(self).try_into().map_err(Error::unsupported_data)?;
+            writer.write_value(&other).await
+        }
+    }
+}
+
+fn nix_serialize_display(span: Span) -> TokenStream {
+    quote_spanned! {
+        span => writer.write_display(self).await
+    }
+}
+
+fn nix_serialize_display_path(path: &syn::ExprPath) -> TokenStream {
+    quote_spanned! {
+        path.span() => writer.write_display(#path(self)).await
+    }
+}