diff options
author | Brian Olsen <brian@maven-group.org> | 2024-07-22T14·51+0200 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-08-25T15·05+0000 |
commit | ced05a2bb6b66d30208520d0791f4fa298c429e2 (patch) | |
tree | d295eb9766db79df8f3053bc5022e24059012f16 /tvix/nix-compat-derive/src/internal/attrs.rs | |
parent | 9af69204787d47cfe551f524d01b1a726971f06e (diff) |
feat(tvix/nix-compat-derive): Add deriver for NixDeserialize r/8586
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>
Diffstat (limited to 'tvix/nix-compat-derive/src/internal/attrs.rs')
-rw-r--r-- | tvix/nix-compat-derive/src/internal/attrs.rs | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/tvix/nix-compat-derive/src/internal/attrs.rs b/tvix/nix-compat-derive/src/internal/attrs.rs new file mode 100644 index 000000000000..dbc959d1e917 --- /dev/null +++ b/tvix/nix-compat-derive/src/internal/attrs.rs @@ -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, + } + ); + } +} |