use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::{parse_macro_input, Attribute, Data, DeriveInput, LitStr, Token};
pub(crate) fn derive_algebraic_laws_impl(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let ident = &input.ident;
let laws = match extract_laws_attribute(&input.attrs) {
Ok(v) => v,
Err(e) => return e.to_compile_error().into(),
};
let law_exprs = laws.iter().map(|lit| {
let src = lit.value();
let trimmed = src.trim();
let path: syn::Expr = match syn::parse_str(&format!("::vyre::ops::AlgebraicLaw::{trimmed}"))
{
Ok(e) => e,
Err(err) => {
return syn::Error::new_spanned(
lit,
format!("failed to parse AlgebraicLaw variant `{trimmed}`: {err}"),
)
.to_compile_error();
}
};
quote! { #path }
});
match &input.data {
Data::Struct(_) | Data::Enum(_) => {}
Data::Union(_) => {
return syn::Error::new_spanned(
ident,
"#[derive(AlgebraicLaws)] does not support unions. Fix: derive it on the op struct or enum that declares algebraic laws.",
)
.to_compile_error()
.into();
}
}
let law_exprs_vec: Vec<_> = law_exprs.collect();
quote! {
impl #ident {
pub const LAWS: &'static [::vyre::ops::AlgebraicLaw] = &[
#(#law_exprs_vec),*
];
}
impl ::vyre::ops::AlgebraicLawProvider for #ident {
fn laws() -> &'static [::vyre::ops::AlgebraicLaw] {
Self::LAWS
}
}
}
.into()
}
pub(crate) fn extract_laws_attribute(attrs: &[Attribute]) -> syn::Result<Vec<LitStr>> {
for attr in attrs {
if !attr.path().is_ident("vyre") {
continue;
}
let mut laws: Option<Vec<LitStr>> = None;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("laws") {
let value = meta.value()?;
let lookahead = value.lookahead1();
if lookahead.peek(syn::token::Bracket) {
let content;
syn::bracketed!(content in value);
let mut collected = Vec::new();
while !content.is_empty() {
if content.peek(LitStr) {
let lit: LitStr = content.parse()?;
collected.push(lit);
} else {
let expr: syn::Expr = content.parse()?;
let rendered = quote! { #expr }.to_string();
collected.push(LitStr::new(&rendered, expr.span()));
}
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
}
laws = Some(collected);
Ok(())
} else {
Err(meta.error("expected `laws = [..]`"))
}
} else {
Err(meta.error("unknown vyre() argument; expected `laws = [..]`"))
}
})?;
if let Some(l) = laws {
return Ok(l);
}
}
Ok(Vec::new())
}