maybe-fatal-derive 0.1.0-beta.4

Code generation for the maybe-fatal crate
Documentation
use proc_macro2::Span;
use syn::{Attribute, meta::ParseNestedMeta};

#[derive(Clone, Default)]
pub struct Repr {
    pub layout: Option<syn::Ident>,
    pub packed: Option<syn::LitInt>,
    pub align: Option<syn::LitInt>,
}

impl Repr {
    pub fn layout_span_or_call_site(&self) -> Span {
        match &self.layout {
            Some(layout) => layout.span(),
            None => Span::call_site(),
        }
    }

    pub fn is_rust(&self) -> bool {
        self.layout.as_ref().is_none_or(|layout| layout == "Rust")
    }

    pub fn is_transparent(&self) -> bool {
        self.layout
            .as_ref()
            .is_some_and(|layout| layout == "transparent")
    }

    pub fn parse_from_attrs<'a>(
        attrs: impl IntoIterator<Item = &'a Attribute>,
    ) -> syn::Result<Self> {
        let mut this = Self::default();
        for attr in attrs {
            this.try_extend_with(attr)?;
        }

        Ok(this)
    }

    pub fn try_extend_with(&mut self, attr: &Attribute) -> syn::Result<()> {
        if attr.path().is_ident("repr") {
            attr.parse_nested_meta(|meta| self.parse_nested_subattribute(meta))
        } else {
            Ok(())
        }
    }

    fn parse_nested_subattribute(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> {
        if meta.path.is_ident("packed") {
            self.parse_packed(meta)
        } else if meta.path.is_ident("align") {
            self.parse_align(meta)
        } else {
            self.parse_layout(meta)
        }
    }

    fn parse_layout(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> {
        if self.layout.is_some() {
            return Err(meta.error("multiple layouts"));
        }

        self.layout = Some(meta.path.require_ident()?.clone());
        if meta.input.is_empty() {
            Ok(())
        } else {
            Err(meta.error("expected layout only"))
        }
    }

    fn parse_packed(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> {
        if self.packed.is_some() {
            return Err(meta.error("repeated `packed` meta attribute"));
        }

        if meta.input.is_empty() {
            self.packed = Some(syn::parse_quote! { 1 });
            return Ok(());
        }

        let content;
        syn::parenthesized!(content in meta.input);
        self.packed = Some(content.parse()?);

        if content.is_empty() {
            Ok(())
        } else {
            Err(meta.error("expected end of `packed` meta attribute"))
        }
    }

    fn parse_align(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> {
        if self.align.is_some() {
            return Err(meta.error("repeated `align` meta attribute"));
        }

        let content;
        syn::parenthesized!(content in meta.input);
        self.align = Some(content.parse()?);

        if content.is_empty() {
            Ok(())
        } else {
            Err(meta.error("expected end of `align` meta attribute"))
        }
    }
}