snix/tvix/nix-compat-derive/src/internal/attrs.rs
Brian Olsen b88579ade4 feat(tvix/nix-compat): Add nix serialization support
This change implements the serialization part that is needed to
implement the nix daemon protocol. Previously was add deserialization
and derivers for that and this then adds the other part of that equation
so that you can write types that can then be read using deserialization.

Change-Id: I2917de634980a93822a4f5a8ad38897b9ce16d89
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12729
Autosubmit: Brian Olsen <me@griff.name>
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
2024-11-04 20:02:54 +00:00

506 lines
15 KiB
Rust

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, DISPLAY, FROM, FROM_STR, INTO, NIX, TRY_FROM, TRY_INTO, VERSION,
};
use super::Context;
#[derive(Debug, PartialEq, Eq)]
pub enum Default {
None,
#[allow(clippy::enum_variant_names)]
Default(syn::Path),
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 version_path = 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)?;
version_path = Some(meta.path);
} 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(meta.path);
}
} 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(version_path.unwrap());
}
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 type_into: Option<syn::Type>,
pub type_try_into: Option<syn::Type>,
pub display: Default,
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;
let mut type_into = None;
let mut type_try_into = None;
let mut display = Default::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 == 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 {
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,
type_into,
type_try_into,
display,
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(parse_quote!(version)),
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(parse_quote!(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_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();
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)),
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,
}
);
}
}