feat(tvix/nix-compat-derive): Add deriver for NixDeserialize

This adds a nix-compat-derive derive crate that implements a deriver
for NixDeserialize implementations. This is to reduce the amount of
code needed to implement deserialization for all the types used by
the Nix daemon protocol.

Change-Id: I484724b550e8a1d5e9adad9555d9dc1374ae95c2
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12022
Autosubmit: Brian Olsen <me@griff.name>
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
Brian Olsen 2024-07-22 16:51:42 +02:00 committed by clbot
parent 9af6920478
commit ced05a2bb6
48 changed files with 2376 additions and 3 deletions

View file

@ -0,0 +1,272 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
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_deserialize(nnixrs: Path, input: &mut DeriveInput) -> syn::Result<TokenStream> {
let cx = Context::new();
let cont = Container::from_ast(&cx, nnixrs, input);
cx.check()?;
let cont = cont.unwrap();
let ty = cont.ident_type();
let body = nix_deserialize_body(&cont);
let crate_path = cont.crate_path();
Ok(nix_deserialize_impl(
crate_path,
&ty,
&cont.original.generics,
body,
))
}
pub fn expand_nix_deserialize_remote(
crate_path: Path,
input: &RemoteInput,
) -> syn::Result<TokenStream> {
let cx = Context::new();
let remote = Remote::from_ast(&cx, crate_path, input);
cx.check()?;
let remote = remote.unwrap();
let crate_path = remote.crate_path();
let body = nix_deserialize_body_from(crate_path, &remote.attrs).expect("From tokenstream");
let generics = Generics::default();
Ok(nix_deserialize_impl(crate_path, remote.ty, &generics, body))
}
fn nix_deserialize_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::de::NixDeserialize for #ty #ty_generics
#where_clause
{
#[allow(clippy::manual_async_fn)]
fn try_deserialize<R>(reader: &mut R) -> impl ::std::future::Future<Output=Result<Option<Self>, R::Error>> + Send + '_
where R: ?Sized + #crate_path::nix_daemon::de::NixRead + Send,
{
#body
}
}
}
}
fn nix_deserialize_body_from(
crate_path: &syn::Path,
attrs: &attrs::Container,
) -> Option<TokenStream> {
if let Some(span) = attrs.from_str.as_ref() {
Some(nix_deserialize_from_str(crate_path, span.span()))
} else if let Some(type_from) = attrs.type_from.as_ref() {
Some(nix_deserialize_from(type_from))
} else {
attrs
.type_try_from
.as_ref()
.map(|type_try_from| nix_deserialize_try_from(crate_path, type_try_from))
}
}
fn nix_deserialize_body(cont: &Container) -> TokenStream {
if let Some(tokens) = nix_deserialize_body_from(cont.crate_path(), &cont.attrs) {
tokens
} else {
match &cont.data {
Data::Struct(style, fields) => nix_deserialize_struct(*style, fields),
Data::Enum(variants) => nix_deserialize_enum(variants),
}
}
}
fn nix_deserialize_struct(style: Style, fields: &[Field<'_>]) -> TokenStream {
let read_fields = fields.iter().map(|f| {
let field = f.var_ident();
let ty = f.ty;
let read_value = quote_spanned! {
ty.span()=> if first__ {
first__ = false;
if let Some(v) = reader.try_read_value::<#ty>().await? {
v
} else {
return Ok(None);
}
} else {
reader.read_value::<#ty>().await?
}
};
if let Some(version) = f.attrs.version.as_ref() {
let default = match &f.attrs.default {
Default::Default => quote_spanned!(ty.span()=>::std::default::Default::default),
Default::Path(path) => path.to_token_stream(),
_ => panic!("No default for versioned field"),
};
quote! {
let #field : #ty = if (#version).contains(&reader.version().minor()) {
#read_value
} else {
#default()
};
}
} else {
quote! {
let #field : #ty = #read_value;
}
}
});
let field_names = fields.iter().map(|f| f.var_ident());
let construct = match style {
Style::Struct => {
quote! {
Self { #(#field_names),* }
}
}
Style::Tuple => {
quote! {
Self(#(#field_names),*)
}
}
Style::Unit => quote!(Self),
};
quote! {
#[allow(unused_assignments)]
async move {
let mut first__ = true;
#(#read_fields)*
Ok(Some(#construct))
}
}
}
fn nix_deserialize_variant(variant: &Variant<'_>) -> TokenStream {
let ident = variant.ident;
let read_fields = variant.fields.iter().map(|f| {
let field = f.var_ident();
let ty = f.ty;
let read_value = quote_spanned! {
ty.span()=> if first__ {
first__ = false;
if let Some(v) = reader.try_read_value::<#ty>().await? {
v
} else {
return Ok(None);
}
} else {
reader.read_value::<#ty>().await?
}
};
if let Some(version) = f.attrs.version.as_ref() {
let default = match &f.attrs.default {
Default::Default => quote_spanned!(ty.span()=>::std::default::Default::default),
Default::Path(path) => path.to_token_stream(),
_ => panic!("No default for versioned field"),
};
quote! {
let #field : #ty = if (#version).contains(&reader.version().minor()) {
#read_value
} else {
#default()
};
}
} else {
quote! {
let #field : #ty = #read_value;
}
}
});
let field_names = variant.fields.iter().map(|f| f.var_ident());
let construct = match variant.style {
Style::Struct => {
quote! {
Self::#ident { #(#field_names),* }
}
}
Style::Tuple => {
quote! {
Self::#ident(#(#field_names),*)
}
}
Style::Unit => quote!(Self::#ident),
};
let version = &variant.attrs.version;
quote! {
#version => {
#(#read_fields)*
Ok(Some(#construct))
}
}
}
fn nix_deserialize_enum(variants: &[Variant<'_>]) -> TokenStream {
let match_variant = variants
.iter()
.map(|variant| nix_deserialize_variant(variant));
quote! {
#[allow(unused_assignments)]
async move {
let mut first__ = true;
match reader.version().minor() {
#(#match_variant)*
}
}
}
}
fn nix_deserialize_from(ty: &Type) -> TokenStream {
quote_spanned! {
ty.span() =>
async move {
if let Some(value) = reader.try_read_value::<#ty>().await? {
Ok(Some(<Self as ::std::convert::From<#ty>>::from(value)))
} else {
Ok(None)
}
}
}
}
fn nix_deserialize_try_from(crate_path: &Path, ty: &Type) -> TokenStream {
quote_spanned! {
ty.span() =>
async move {
use #crate_path::nix_daemon::de::Error;
if let Some(item) = reader.try_read_value::<#ty>().await? {
<Self as ::std::convert::TryFrom<#ty>>::try_from(item)
.map_err(Error::invalid_data)
.map(Some)
} else {
Ok(None)
}
}
}
}
fn nix_deserialize_from_str(crate_path: &Path, span: Span) -> TokenStream {
quote_spanned! {
span =>
async move {
use #crate_path::nix_daemon::de::Error;
if let Some(buf) = reader.try_read_bytes().await? {
let s = ::std::str::from_utf8(&buf)
.map_err(Error::invalid_data)?;
<Self as ::std::str::FromStr>::from_str(s)
.map_err(Error::invalid_data)
.map(Some)
} else {
Ok(None)
}
}
}
}

View file

@ -0,0 +1,358 @@
use quote::ToTokens;
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::Context;
#[derive(Debug, PartialEq, Eq)]
pub enum Default {
None,
#[allow(clippy::enum_variant_names)]
Default,
Path(ExprPath),
}
impl Default {
pub fn is_none(&self) -> bool {
matches!(self, Default::None)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Field {
pub default: Default,
pub version: Option<syn::ExprRange>,
}
impl Field {
pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Field {
let mut version = None;
let mut default = Default::None;
for attr in attrs {
if attr.path() != NIX {
continue;
}
if let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path == VERSION {
version = parse_lit(ctx, &meta, VERSION)?;
} else if meta.path == DEFAULT {
if meta.input.peek(Token![=]) {
if let Some(path) = parse_lit(ctx, &meta, DEFAULT)? {
default = Default::Path(path);
}
} else {
default = Default::Default;
}
} else {
let path = meta.path.to_token_stream().to_string();
return Err(meta.error(format_args!("unknown nix field attribute '{}'", path)));
}
Ok(())
}) {
eprintln!("{:?}", err.span().source_text());
ctx.syn_error(err);
}
}
if version.is_some() && default.is_none() {
default = Default::Default;
}
Field { default, version }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Variant {
pub version: syn::ExprRange,
}
impl Variant {
pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Variant {
let mut version = parse_quote!(..);
for attr in attrs {
if attr.path() != NIX {
continue;
}
if let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path == VERSION {
if let Some(v) = parse_lit(ctx, &meta, VERSION)? {
version = v;
}
} else {
let path = meta.path.to_token_stream().to_string();
return Err(
meta.error(format_args!("unknown nix variant attribute '{}'", path))
);
}
Ok(())
}) {
eprintln!("{:?}", err.span().source_text());
ctx.syn_error(err);
}
}
Variant { version }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Container {
pub from_str: Option<syn::Path>,
pub type_from: Option<syn::Type>,
pub type_try_from: Option<syn::Type>,
pub crate_path: Option<syn::Path>,
}
impl Container {
pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Container {
let mut type_from = None;
let mut type_try_from = None;
let mut crate_path = None;
let mut from_str = None;
for attr in attrs {
if attr.path() != NIX {
continue;
}
if let Err(err) = attr.parse_nested_meta(|meta| {
if meta.path == FROM {
type_from = parse_lit(ctx, &meta, FROM)?;
} else if meta.path == TRY_FROM {
type_try_from = parse_lit(ctx, &meta, TRY_FROM)?;
} else if meta.path == FROM_STR {
from_str = Some(meta.path);
} else if meta.path == CRATE {
crate_path = parse_lit(ctx, &meta, CRATE)?;
} else {
let path = meta.path.to_token_stream().to_string();
return Err(
meta.error(format_args!("unknown nix variant attribute '{}'", path))
);
}
Ok(())
}) {
eprintln!("{:?}", err.span().source_text());
ctx.syn_error(err);
}
}
Container {
from_str,
type_from,
type_try_from,
crate_path,
}
}
}
pub fn get_lit_str(
ctx: &Context,
meta: &ParseNestedMeta,
attr: Symbol,
) -> syn::Result<Option<syn::LitStr>> {
let expr: Expr = meta.value()?.parse()?;
let mut value = &expr;
while let Expr::Group(e) = value {
value = &e.expr;
}
if let Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}) = value
{
Ok(Some(s.clone()))
} else {
ctx.error_spanned(
expr,
format_args!("expected nix attribute {} to be string", attr),
);
Ok(None)
}
}
pub fn parse_lit<T: Parse>(
ctx: &Context,
meta: &ParseNestedMeta,
attr: Symbol,
) -> syn::Result<Option<T>> {
match get_lit_str(ctx, meta, attr)? {
Some(lit) => Ok(Some(lit.parse()?)),
None => Ok(None),
}
}
#[cfg(test)]
mod test {
use syn::{parse_quote, Attribute};
use crate::internal::Context;
use super::*;
#[test]
fn parse_field_version() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(version="..34")])];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::Default,
version: Some(parse_quote!(..34)),
}
);
}
#[test]
fn parse_field_default() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(default)])];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::Default,
version: None,
}
);
}
#[test]
fn parse_field_default_path() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(default="Default::default")])];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::Path(parse_quote!(Default::default)),
version: None,
}
);
}
#[test]
fn parse_field_both() {
let attrs: Vec<Attribute> =
vec![parse_quote!(#[nix(version="..", default="Default::default")])];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::Path(parse_quote!(Default::default)),
version: Some(parse_quote!(..)),
}
);
}
#[test]
fn parse_field_both_rev() {
let attrs: Vec<Attribute> =
vec![parse_quote!(#[nix(default="Default::default", version="..")])];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::Path(parse_quote!(Default::default)),
version: Some(parse_quote!(..)),
}
);
}
#[test]
fn parse_field_no_attr() {
let attrs: Vec<Attribute> = vec![];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::None,
version: None,
}
);
}
#[test]
fn parse_field_no_subattrs() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix()])];
let ctx = Context::new();
let field = Field::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
field,
Field {
default: Default::None,
version: None,
}
);
}
#[test]
fn parse_variant_version() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(version="..34")])];
let ctx = Context::new();
let variant = Variant::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
variant,
Variant {
version: parse_quote!(..34),
}
);
}
#[test]
fn parse_variant_no_attr() {
let attrs: Vec<Attribute> = vec![];
let ctx = Context::new();
let variant = Variant::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
variant,
Variant {
version: parse_quote!(..),
}
);
}
#[test]
fn parse_variant_no_subattrs() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix()])];
let ctx = Context::new();
let variant = Variant::from_ast(&ctx, &attrs);
ctx.check().unwrap();
assert_eq!(
variant,
Variant {
version: parse_quote!(..),
}
);
}
#[test]
fn parse_container_try_from() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(try_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: None,
type_try_from: Some(parse_quote!(u64)),
crate_path: None,
}
);
}
}

View file

@ -0,0 +1,50 @@
use std::cell::RefCell;
use std::fmt;
use std::thread::panicking;
use quote::ToTokens;
pub struct Context {
errors: RefCell<Option<Vec<syn::Error>>>,
}
impl Context {
pub fn new() -> Context {
Context {
errors: RefCell::new(Some(Vec::new())),
}
}
pub fn syn_error(&self, error: syn::Error) {
self.errors
.borrow_mut()
.as_mut()
.take()
.unwrap()
.push(error);
}
pub fn error_spanned<T: ToTokens, D: fmt::Display>(&self, tokens: T, message: D) {
self.syn_error(syn::Error::new_spanned(tokens, message));
}
pub fn check(&self) -> syn::Result<()> {
let mut iter = self.errors.borrow_mut().take().unwrap().into_iter();
let mut err = match iter.next() {
None => return Ok(()),
Some(err) => err,
};
for next_err in iter {
err.combine(next_err);
}
Err(err)
}
}
impl Drop for Context {
fn drop(&mut self) {
if self.errors.borrow().is_some() && !panicking() {
panic!("Context dropped without checking errors");
}
}
}

View file

@ -0,0 +1,110 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemoteInput {
pub attrs: Vec<syn::Attribute>,
pub ident: syn::Type,
}
impl syn::parse::Parse for RemoteInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let attrs = input.call(syn::Attribute::parse_outer)?;
let ident = input.parse::<syn::Type>()?;
Ok(RemoteInput { attrs, ident })
}
}
impl quote::ToTokens for RemoteInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
fn is_outer(attr: &&syn::Attribute) -> bool {
match attr.style {
syn::AttrStyle::Outer => true,
syn::AttrStyle::Inner(_) => false,
}
}
for attr in self.attrs.iter().filter(is_outer) {
attr.to_tokens(tokens);
}
self.ident.to_tokens(tokens);
}
}
#[cfg(test)]
mod test {
use syn::parse_quote;
//use syn::parse::Parse;
use super::*;
#[test]
fn test_input() {
let p: RemoteInput = parse_quote!(u64);
assert_eq!(
p,
RemoteInput {
attrs: vec![],
ident: parse_quote!(u64),
}
);
}
#[test]
fn test_input_attr() {
let p: RemoteInput = parse_quote!(
#[nix]
u64
);
assert_eq!(
p,
RemoteInput {
attrs: vec![parse_quote!(#[nix])],
ident: parse_quote!(u64),
}
);
}
#[test]
fn test_input_attr_multiple() {
let p: RemoteInput = parse_quote!(
#[nix]
#[hello]
u64
);
assert_eq!(
p,
RemoteInput {
attrs: vec![parse_quote!(#[nix]), parse_quote!(#[hello])],
ident: parse_quote!(u64),
}
);
}
#[test]
fn test_input_attr_full() {
let p: RemoteInput = parse_quote!(
#[nix(try_from = "u64")]
usize
);
assert_eq!(
p,
RemoteInput {
attrs: vec![parse_quote!(#[nix(try_from="u64")])],
ident: parse_quote!(usize),
}
);
}
#[test]
fn test_input_attr_other() {
let p: RemoteInput = parse_quote!(
#[muh]
u64
);
assert_eq!(
p,
RemoteInput {
attrs: vec![parse_quote!(#[muh])],
ident: parse_quote!(u64),
}
);
}
}

View file

@ -0,0 +1,183 @@
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::Token;
pub mod attrs;
mod ctx;
pub mod inputs;
mod symbol;
pub use ctx::Context;
pub struct Field<'a> {
pub member: syn::Member,
pub ty: &'a syn::Type,
pub attrs: attrs::Field,
pub original: &'a syn::Field,
}
impl<'a> Field<'a> {
pub fn from_ast(ctx: &Context, idx: usize, field: &'a syn::Field) -> Field<'a> {
let attrs = attrs::Field::from_ast(ctx, &field.attrs);
let member = match &field.ident {
Some(id) => syn::Member::Named(id.clone()),
None => syn::Member::Unnamed(idx.into()),
};
Field {
member,
attrs,
ty: &field.ty,
original: field,
}
}
pub fn var_ident(&self) -> syn::Ident {
match &self.member {
syn::Member::Named(name) => name.clone(),
syn::Member::Unnamed(idx) => {
syn::Ident::new(&format!("field{}", idx.index), self.original.span())
}
}
}
}
pub struct Variant<'a> {
pub ident: &'a syn::Ident,
pub attrs: attrs::Variant,
pub style: Style,
pub fields: Vec<Field<'a>>,
//pub original: &'a syn::Variant,
}
impl<'a> Variant<'a> {
pub fn from_ast(ctx: &Context, variant: &'a syn::Variant) -> Self {
let attrs = attrs::Variant::from_ast(ctx, &variant.attrs);
let (style, fields) = match &variant.fields {
syn::Fields::Named(fields) => (Style::Struct, fields_ast(ctx, &fields.named)),
syn::Fields::Unnamed(fields) => (Style::Tuple, fields_ast(ctx, &fields.unnamed)),
syn::Fields::Unit => (Style::Unit, Vec::new()),
};
Variant {
ident: &variant.ident,
attrs,
style,
fields,
//original: variant,
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum Style {
Struct,
Tuple,
Unit,
}
pub enum Data<'a> {
Enum(Vec<Variant<'a>>),
Struct(Style, Vec<Field<'a>>),
}
pub struct Container<'a> {
pub ident: &'a syn::Ident,
pub attrs: attrs::Container,
pub data: Data<'a>,
pub crate_path: syn::Path,
pub original: &'a syn::DeriveInput,
}
impl<'a> Container<'a> {
pub fn from_ast(
ctx: &Context,
crate_path: syn::Path,
input: &'a mut syn::DeriveInput,
) -> Option<Container<'a>> {
let attrs = attrs::Container::from_ast(ctx, &input.attrs);
let data = match &input.data {
syn::Data::Struct(s) => match &s.fields {
syn::Fields::Named(fields) => {
Data::Struct(Style::Struct, fields_ast(ctx, &fields.named))
}
syn::Fields::Unnamed(fields) => {
Data::Struct(Style::Tuple, fields_ast(ctx, &fields.unnamed))
}
syn::Fields::Unit => Data::Struct(Style::Unit, Vec::new()),
},
syn::Data::Enum(e) => {
let variants = e
.variants
.iter()
.map(|variant| Variant::from_ast(ctx, variant))
.collect();
Data::Enum(variants)
}
syn::Data::Union(u) => {
ctx.error_spanned(u.union_token, "Union not supported by nixrs");
return None;
}
};
Some(Container {
ident: &input.ident,
attrs,
data,
crate_path,
original: input,
})
}
pub fn crate_path(&self) -> &syn::Path {
if let Some(crate_path) = self.attrs.crate_path.as_ref() {
crate_path
} else {
&self.crate_path
}
}
pub fn ident_type(&self) -> syn::Type {
let path: syn::Path = self.ident.clone().into();
let tp = syn::TypePath { qself: None, path };
tp.into()
}
}
pub struct Remote<'a> {
pub attrs: attrs::Container,
pub ty: &'a syn::Type,
pub crate_path: syn::Path,
}
impl<'a> Remote<'a> {
pub fn from_ast(
ctx: &Context,
crate_path: syn::Path,
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,
crate_path,
})
}
pub fn crate_path(&self) -> &syn::Path {
if let Some(crate_path) = self.attrs.crate_path.as_ref() {
crate_path
} else {
&self.crate_path
}
}
}
fn fields_ast<'a>(ctx: &Context, fields: &'a Punctuated<syn::Field, Token![,]>) -> Vec<Field<'a>> {
fields
.iter()
.enumerate()
.map(|(idx, field)| Field::from_ast(ctx, idx, field))
.collect()
}

View file

@ -0,0 +1,32 @@
use std::fmt;
use syn::Path;
#[derive(Copy, Clone)]
pub struct Symbol(&'static str);
pub const NIX: Symbol = Symbol("nix");
pub const VERSION: Symbol = Symbol("version");
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 CRATE: Symbol = Symbol("crate");
impl PartialEq<Symbol> for Path {
fn eq(&self, word: &Symbol) -> bool {
self.is_ident(word.0)
}
}
impl<'a> PartialEq<Symbol> for &'a Path {
fn eq(&self, word: &Symbol) -> bool {
self.is_ident(word.0)
}
}
impl fmt::Display for Symbol {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(self.0)
}
}

View file

@ -0,0 +1,348 @@
//! # Using derive
//!
//! 1. [Overview](#overview)
//! 3. [Attributes](#attributes)
//! 1. [Container attributes](#container-attributes)
//! 1. [`#[nix(from_str)]`](#nixfrom_str)
//! 2. [`#[nix(from = "FromType")]`](#nixfrom--fromtype)
//! 3. [`#[nix(try_from = "FromType")]`](#nixtry_from--fromtype)
//! 4. [`#[nix(crate = "...")]`](#nixcrate--)
//! 2. [Variant attributes](#variant-attributes)
//! 1. [`#[nix(version = "range")]`](#nixversion--range)
//! 3. [Field attributes](#field-attributes)
//! 1. [`#[nix(version = "range")]`](#nixversion--range-1)
//! 2. [`#[nix(default)]`](#nixdefault)
//! 3. [`#[nix(default = "path")]`](#nixdefault--path)
//!
//! ## Overview
//!
//! This crate contains derive macros and function-like macros for implementing
//! `NixDeserialize` with less boilerplate.
//!
//! ### Examples
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Unnamed(u64, String);
//! ```
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Fields {
//! number: u64,
//! message: String,
//! };
//! ```
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Ignored;
//! ```
//!
//! ## Attributes
//!
//! To customize the derived trait implementations you can add
//! [attributes](https://doc.rust-lang.org/reference/attributes.html)
//! to containers, fields and variants.
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! #[nix(crate="nix_compat")] // <-- This is a container attribute
//! struct Fields {
//! number: u64,
//! #[nix(version="..20")] // <-- This is a field attribute
//! message: String,
//! };
//!
//! #[derive(NixDeserialize)]
//! #[nix(crate="nix_compat")] // <-- This is also a container attribute
//! enum E {
//! #[nix(version="..10")] // <-- This is a variant attribute
//! A(u64),
//! #[nix(version="10..")] // <-- This is also a variant attribute
//! B(String),
//! }
//! ```
//!
//! ### Container attributes
//!
//! ##### `#[nix(from_str)]`
//!
//! When `from_str` is specified the fields are all ignored and instead a
//! `String` is first deserialized and then `FromStr::from_str` is used
//! to convert this `String` to the container type.
//!
//! This means that the container must implement `FromStr` and the error
//! returned from the `from_str` must implement `Display`.
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! #[nix(from_str)]
//! struct MyString(String);
//! impl std::str::FromStr for MyString {
//! type Err = String;
//! fn from_str(s: &str) -> Result<Self, Self::Err> {
//! if s != "bad string" {
//! Ok(MyString(s.to_string()))
//! } else {
//! Err("Got a bad string".to_string())
//! }
//! }
//! }
//! ```
//!
//! ##### `#[nix(from = "FromType")]`
//!
//! When `from` is specified the fields are all ignored and instead a
//! value of `FromType` is first deserialized and then `From::from` is
//! used to convert from this value to the container type.
//!
//! This means that the container must implement `From<FromType>` and
//! `FromType` must implement `NixDeserialize`.
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! #[nix(from="usize")]
//! struct MyValue(usize);
//! impl From<usize> for MyValue {
//! fn from(val: usize) -> Self {
//! MyValue(val)
//! }
//! }
//! ```
//!
//! ##### `#[nix(try_from = "FromType")]`
//!
//! With `try_from` a value of `FromType` is first deserialized and then
//! `TryFrom::try_from` is used to convert from this value to the container
//! type.
//!
//! This means that the container must implement `TryFrom<FromType>` and
//! `FromType` must implement `NixDeserialize`.
//! The error returned from `try_from` also needs to implement `Display`.
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! #[nix(try_from="usize")]
//! struct WrongAnswer(usize);
//! impl TryFrom<usize> for WrongAnswer {
//! type Error = String;
//! fn try_from(val: usize) -> Result<Self, Self::Error> {
//! if val != 42 {
//! Ok(WrongAnswer(val))
//! } else {
//! Err("Got the answer to life the universe and everything".to_string())
//! }
//! }
//! }
//! ```
//!
//! ##### `#[nix(crate = "...")]`
//!
//! Specify the path to the `nix-compat` crate instance to use when referring
//! to the API in the generated code. This is usually not needed.
//!
//! ### Variant attributes
//!
//! ##### `#[nix(version = "range")]`
//!
//! Specifies the protocol version range where this variant is used.
//! When deriving an enum the `version` attribute is used to select which
//! variant of the enum to deserialize. The range is for minor version and
//! the version ranges of all variants combined must cover all versions
//! without any overlap or the first variant that matches is selected.
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #[derive(NixDeserialize)]
//! enum Testing {
//! #[nix(version="..=18")]
//! OldVersion(u64),
//! #[nix(version="19..")]
//! NewVersion(String),
//! }
//! ```
//!
//! ### Field attributes
//!
//! ##### `#[nix(version = "range")]`
//!
//! Specifies the protocol version range where this field is included.
//! The range is for minor version. For example `version = "..20"`
//! includes the field in protocol versions `1.0` to `1.19` and skips
//! it in version `1.20` and above.
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Field {
//! number: u64,
//! #[nix(version="..20")]
//! messsage: String,
//! }
//! ```
//!
//! ##### `#[nix(default)]`
//!
//! When a field is skipped because the active protocol version falls
//! outside the range specified in [`#[nix(version = "range")]`](#nixversion--range-1)
//! this attribute indicates that `Default::default()` should be used
//! to get a value for the field. This is also the default
//! when you only specify [`#[nix(version = "range")]`](#nixversion--range-1).
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Field {
//! number: u64,
//! #[nix(version="..20", default)]
//! messsage: String,
//! }
//! ```
//!
//! ##### `#[nix(default = "path")]`
//!
//! When a field is skipped because the active protocol version falls
//! outside the range specified in [`#[nix(version = "range")]`](#nixversion--range-1)
//! this attribute indicates that the function in `path` should be called to
//! get a default value for the field. The given function must be callable
//! as `fn() -> T`.
//! For example `default = "my_value"` would call `my_value()` and `default =
//! "AType::empty"` would call `AType::empty()`.
//!
//! ###### Example
//!
//! ```rust
//! # use nix_compat_derive::NixDeserialize;
//! #
//! #[derive(NixDeserialize)]
//! struct Field {
//! number: u64,
//! #[nix(version="..20", default="missing_string")]
//! messsage: String,
//! }
//!
//! fn missing_string() -> String {
//! "missing string".to_string()
//! }
//! ```
use internal::inputs::RemoteInput;
use proc_macro::TokenStream;
use syn::{parse_quote, DeriveInput};
mod de;
mod internal;
#[cfg(not(feature = "external"))]
#[proc_macro_derive(NixDeserialize, attributes(nix))]
pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as DeriveInput);
let nnixrs: syn::Path = parse_quote!(crate);
de::expand_nix_deserialize(nnixrs, &mut input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[cfg(feature = "external")]
#[proc_macro_derive(NixDeserialize, attributes(nix))]
pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as DeriveInput);
let nnixrs: syn::Path = parse_quote!(::nix_compat);
de::expand_nix_deserialize(nnixrs, &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
/// to implement it yourself. So this macro can be used for those situations
/// where you would derive using `#[nix(from_str)]`,
/// `#[nix(from = "FromType")]` or `#[nix(try_from = "FromType")]` if you
/// could.
///
/// #### Example
///
/// ```rust
/// # use nix_compat_derive::nix_deserialize_remote;
/// #
/// struct MyU64(u64);
///
/// impl From<u64> for MyU64 {
/// fn from(value: u64) -> Self {
/// Self(value)
/// }
/// }
///
/// nix_deserialize_remote!(#[nix(from="u64")] MyU64);
/// ```
#[cfg(not(feature = "external"))]
#[proc_macro]
pub fn nix_deserialize_remote(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as RemoteInput);
let crate_path = parse_quote!(crate);
de::expand_nix_deserialize_remote(crate_path, &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
/// to implement it yourself. So this macro can be used for those situations
/// where you would derive using `#[nix(from_str)]`,
/// `#[nix(from = "FromType")]` or `#[nix(try_from = "FromType")]` if you
/// could.
///
/// #### Example
///
/// ```rust
/// # use nix_compat_derive::nix_deserialize_remote;
/// #
/// struct MyU64(u64);
///
/// impl From<u64> for MyU64 {
/// fn from(value: u64) -> Self {
/// Self(value)
/// }
/// }
///
/// nix_deserialize_remote!(#[nix(from="u64")] MyU64);
/// ```
#[cfg(feature = "external")]
#[proc_macro]
pub fn nix_deserialize_remote(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as RemoteInput);
let crate_path = parse_quote!(::nix_compat);
de::expand_nix_deserialize_remote(crate_path, &input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}