about summary refs log tree commit diff
path: root/tvix/nix-compat-derive/src/internal/mod.rs
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 nix-compat");
                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);
        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()
}