diff --git a/Cargo.lock b/Cargo.lock index c73ef931..ea215eec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -731,9 +731,10 @@ dependencies = [ name = "conduit_macros" version = "0.4.5" dependencies = [ + "itertools 0.13.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.71", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 66ba7385..17e7e712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -426,7 +426,7 @@ default-features = false version = "0.1" [workspace.dependencies.syn] -version = "1.0" +version = "2.0" features = ["full", "extra-traits"] [workspace.dependencies.quote] diff --git a/src/core/info/cargo.rs b/src/core/info/cargo.rs index 544bbb8f..012a08e0 100644 --- a/src/core/info/cargo.rs +++ b/src/core/info/cargo.rs @@ -16,19 +16,19 @@ use crate::Result; #[cargo_manifest] const WORKSPACE_MANIFEST: &'static str = (); -#[cargo_manifest("macros")] +#[cargo_manifest(crate = "macros")] const MACROS_MANIFEST: &'static str = (); -#[cargo_manifest("core")] +#[cargo_manifest(crate = "core")] const CORE_MANIFEST: &'static str = (); -#[cargo_manifest("database")] +#[cargo_manifest(crate = "database")] const DATABASE_MANIFEST: &'static str = (); -#[cargo_manifest("service")] +#[cargo_manifest(crate = "service")] const SERVICE_MANIFEST: &'static str = (); -#[cargo_manifest("admin")] +#[cargo_manifest(crate = "admin")] const ADMIN_MANIFEST: &'static str = (); -#[cargo_manifest("router")] +#[cargo_manifest(crate = "router")] const ROUTER_MANIFEST: &'static str = (); -#[cargo_manifest("main")] +#[cargo_manifest(crate = "main")] const MAIN_MANIFEST: &'static str = (); /// Processed list of features access all project crates. This is generated from diff --git a/src/macros/Cargo.toml b/src/macros/Cargo.toml index ca98f169..9e866578 100644 --- a/src/macros/Cargo.toml +++ b/src/macros/Cargo.toml @@ -18,6 +18,7 @@ proc-macro = true syn.workspace = true quote.workspace = true proc-macro2.workspace = true +itertools.workspace = true [lints] workspace = true diff --git a/src/macros/admin.rs b/src/macros/admin.rs index e1d294b9..4189d64f 100644 --- a/src/macros/admin.rs +++ b/src/macros/admin.rs @@ -1,17 +1,15 @@ +use itertools::Itertools; use proc_macro::{Span, TokenStream}; use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::{parse_macro_input, AttributeArgs, Fields, Ident, ItemEnum, Variant}; +use quote::{quote, ToTokens}; +use syn::{Error, Fields, Ident, ItemEnum, Meta, Variant}; -use crate::utils::camel_to_snake_string; +use crate::{utils::camel_to_snake_string, Result}; -pub(super) fn command_dispatch(args: TokenStream, input_: TokenStream) -> TokenStream { - let input = input_.clone(); - let item = parse_macro_input!(input as ItemEnum); - let _args = parse_macro_input!(args as AttributeArgs); - let arm = item.variants.iter().map(dispatch_arm); - let name = item.ident; - let q = quote! { +pub(super) fn command_dispatch(item: ItemEnum, _args: &[Meta]) -> Result { + let name = &item.ident; + let arm: Vec = item.variants.iter().map(dispatch_arm).try_collect()?; + let switch = quote! { pub(super) async fn process(command: #name, body: Vec<&str>) -> Result { use #name::*; #[allow(non_snake_case)] @@ -21,14 +19,17 @@ pub(super) fn command_dispatch(args: TokenStream, input_: TokenStream) -> TokenS } }; - [input_, q.into()].into_iter().collect::() + Ok([item.into_token_stream(), switch] + .into_iter() + .collect::() + .into()) } -fn dispatch_arm(v: &Variant) -> TokenStream2 { +fn dispatch_arm(v: &Variant) -> Result { let name = &v.ident; let target = camel_to_snake_string(&format!("{name}")); let handler = Ident::new(&target, Span::call_site().into()); - match &v.fields { + let res = match &v.fields { Fields::Named(fields) => { let field = fields.named.iter().filter_map(|f| f.ident.as_ref()); let arg = field.clone(); @@ -37,7 +38,9 @@ fn dispatch_arm(v: &Variant) -> TokenStream2 { } }, Fields::Unnamed(fields) => { - let field = &fields.unnamed.first().expect("one field"); + let Some(ref field) = fields.unnamed.first() else { + return Err(Error::new(Span::call_site().into(), "One unnamed field required")); + }; quote! { #name ( #field ) => Box::pin(#handler::process(#field, body)).await?, } @@ -47,5 +50,7 @@ fn dispatch_arm(v: &Variant) -> TokenStream2 { #name => Box::pin(#handler(&body)).await?, } }, - } + }; + + Ok(res) } diff --git a/src/macros/cargo.rs b/src/macros/cargo.rs index 17132a6c..cd36658e 100644 --- a/src/macros/cargo.rs +++ b/src/macros/cargo.rs @@ -1,39 +1,34 @@ use std::{fs::read_to_string, path::PathBuf}; -use proc_macro::TokenStream; +use proc_macro::{Span, TokenStream}; use quote::quote; -use syn::{parse_macro_input, AttributeArgs, ItemConst, Lit, NestedMeta}; +use syn::{Error, ItemConst, Meta}; -pub(super) fn manifest(args: TokenStream, item: TokenStream) -> TokenStream { - let item = parse_macro_input!(item as ItemConst); - let args = parse_macro_input!(args as AttributeArgs); - let member = args.into_iter().find_map(|arg| { - let NestedMeta::Lit(arg) = arg else { - return None; - }; - let Lit::Str(arg) = arg else { - return None; - }; - Some(arg.value()) - }); +use crate::{utils, Result}; - let path = manifest_path(member.as_deref()); +pub(super) fn manifest(item: ItemConst, args: &[Meta]) -> Result { + let member = utils::get_named_string(args, "crate"); + let path = manifest_path(member.as_deref())?; let manifest = read_to_string(&path).unwrap_or_default(); - - let name = item.ident; let val = manifest.as_str(); + let name = item.ident; let ret = quote! { const #name: &'static str = #val; }; - ret.into() + Ok(ret.into()) } #[allow(clippy::option_env_unwrap)] -fn manifest_path(member: Option<&str>) -> PathBuf { - let mut path: PathBuf = option_env!("CARGO_MANIFEST_DIR") - .expect("missing CARGO_MANIFEST_DIR in environment") - .into(); +fn manifest_path(member: Option<&str>) -> Result { + let Some(path) = option_env!("CARGO_MANIFEST_DIR") else { + return Err(Error::new( + Span::call_site().into(), + "missing CARGO_MANIFEST_DIR in environment", + )); + }; + + let mut path: PathBuf = path.into(); // conduwuit/src/macros/ -> conduwuit/src/ path.pop(); @@ -47,5 +42,6 @@ fn manifest_path(member: Option<&str>) -> PathBuf { } path.push("Cargo.toml"); - path + + Ok(path) } diff --git a/src/macros/debug.rs b/src/macros/debug.rs index 5251fa17..e83fd44e 100644 --- a/src/macros/debug.rs +++ b/src/macros/debug.rs @@ -1,13 +1,12 @@ use std::cmp; use proc_macro::TokenStream; -use syn::{parse_macro_input, AttributeArgs, Item}; +use quote::ToTokens; +use syn::{Item, Meta}; -pub(super) fn recursion_depth(args: TokenStream, item_: TokenStream) -> TokenStream { - let item = item_.clone(); - let item = parse_macro_input!(item as Item); - let _args = parse_macro_input!(args as AttributeArgs); +use crate::Result; +pub(super) fn recursion_depth(item: Item, _args: &[Meta]) -> Result { let mut best: usize = 0; let mut count: usize = 0; // think you'd find a fancy recursive ast visitor? think again @@ -24,5 +23,5 @@ pub(super) fn recursion_depth(args: TokenStream, item_: TokenStream) -> TokenStr println!("DEPTH: {best}"); println!("LENGTH: {count}"); - item_ + Ok(item.into_token_stream().into()) } diff --git a/src/macros/implement.rs b/src/macros/implement.rs index 1a06e588..b5c8d787 100644 --- a/src/macros/implement.rs +++ b/src/macros/implement.rs @@ -1,23 +1,26 @@ use proc_macro::TokenStream; -use quote::{quote, ToTokens}; -use syn::{parse_macro_input, AttributeArgs, ItemFn, Meta, NestedMeta}; +use quote::quote; +use syn::{ItemFn, Meta, MetaList}; -pub(super) fn implement(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let item = parse_macro_input!(input as ItemFn); +use crate::Result; - let NestedMeta::Meta(Meta::Path(receiver)) = args +pub(super) fn implement(item: ItemFn, args: &[Meta]) -> Result { + let Meta::List(MetaList { + path, + .. + }) = &args .first() .expect("missing path to trait or item to implement") else { panic!("invalid path to item for implement"); }; + let input = item; let out = quote! { - impl #receiver { - #item + impl #path { + #input } }; - out.into_token_stream().into() + Ok(out.into()) } diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 8d7e2e5b..1a5494bb 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -7,23 +7,50 @@ mod rustc; mod utils; use proc_macro::TokenStream; +use syn::{ + parse::{Parse, Parser}, + parse_macro_input, Error, Item, ItemConst, ItemEnum, ItemFn, Meta, +}; + +pub(crate) type Result = std::result::Result; #[proc_macro_attribute] pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStream { - admin::command_dispatch(args, input) + attribute_macro::(args, input, admin::command_dispatch) } #[proc_macro_attribute] -pub fn cargo_manifest(args: TokenStream, input: TokenStream) -> TokenStream { cargo::manifest(args, input) } +pub fn cargo_manifest(args: TokenStream, input: TokenStream) -> TokenStream { + attribute_macro::(args, input, cargo::manifest) +} #[proc_macro_attribute] -pub fn recursion_depth(args: TokenStream, input: TokenStream) -> TokenStream { debug::recursion_depth(args, input) } +pub fn recursion_depth(args: TokenStream, input: TokenStream) -> TokenStream { + attribute_macro::(args, input, debug::recursion_depth) +} #[proc_macro] pub fn rustc_flags_capture(args: TokenStream) -> TokenStream { rustc::flags_capture(args) } #[proc_macro_attribute] -pub fn refutable(args: TokenStream, input: TokenStream) -> TokenStream { refutable::refutable(args, input) } +pub fn refutable(args: TokenStream, input: TokenStream) -> TokenStream { + attribute_macro::(args, input, refutable::refutable) +} #[proc_macro_attribute] -pub fn implement(args: TokenStream, input: TokenStream) -> TokenStream { implement::implement(args, input) } +pub fn implement(args: TokenStream, input: TokenStream) -> TokenStream { + attribute_macro::(args, input, implement::implement) +} + +fn attribute_macro(args: TokenStream, input: TokenStream, func: F) -> TokenStream +where + F: Fn(I, &[Meta]) -> Result, + I: Parse, +{ + let item = parse_macro_input!(input as I); + syn::punctuated::Punctuated::::parse_terminated + .parse(args) + .map(|args| args.iter().cloned().collect::>()) + .and_then(|ref args| func(item, args)) + .unwrap_or_else(|e| e.to_compile_error().into()) +} diff --git a/src/macros/refutable.rs b/src/macros/refutable.rs index 6a6884e0..facb4729 100644 --- a/src/macros/refutable.rs +++ b/src/macros/refutable.rs @@ -1,11 +1,10 @@ use proc_macro::{Span, TokenStream}; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, AttributeArgs, FnArg::Typed, Ident, ItemFn, Pat, PatIdent, PatType, Stmt}; +use syn::{FnArg::Typed, Ident, ItemFn, Meta, Pat, PatIdent, PatType, Stmt}; -pub(super) fn refutable(args: TokenStream, input: TokenStream) -> TokenStream { - let _args = parse_macro_input!(args as AttributeArgs); - let mut item = parse_macro_input!(input as ItemFn); +use crate::Result; +pub(super) fn refutable(mut item: ItemFn, _args: &[Meta]) -> Result { let inputs = item.sig.inputs.clone(); let stmt = &mut item.block.stmts; let sig = &mut item.sig; @@ -25,37 +24,30 @@ pub(super) fn refutable(args: TokenStream, input: TokenStream) -> TokenStream { let variant = &pat.path; let fields = &pat.fields; - // new versions of syn can replace this kronecker kludge with get_mut() - for (j, input) in sig.inputs.iter_mut().enumerate() { - if i != j { - continue; - } + let Some(Typed(PatType { + ref mut pat, + .. + })) = sig.inputs.get_mut(i) + else { + continue; + }; - let Typed(PatType { - ref mut pat, - .. - }) = input - else { - continue; - }; + let name = format!("_args_{i}"); + *pat = Box::new(Pat::Ident(PatIdent { + ident: Ident::new(&name, Span::call_site().into()), + attrs: Vec::new(), + by_ref: None, + mutability: None, + subpat: None, + })); - let name = format!("_args_{i}"); - *pat = Box::new(Pat::Ident(PatIdent { - ident: Ident::new(&name, Span::call_site().into()), - attrs: Vec::new(), - by_ref: None, - mutability: None, - subpat: None, - })); + let field = fields.iter(); + let refute = quote! { + let #variant { #( #field ),*, .. } = #name else { panic!("incorrect variant passed to function argument {i}"); }; + }; - let field = fields.iter(); - let refute = quote! { - let #variant { #( #field ),*, .. } = #name else { panic!("incorrect variant passed to function argument {i}"); }; - }; - - stmt.insert(0, syn::parse2::(refute).expect("syntax error")); - } + stmt.insert(0, syn::parse2::(refute)?); } - item.into_token_stream().into() + Ok(item.into_token_stream().into()) } diff --git a/src/macros/utils.rs b/src/macros/utils.rs index f512c56c..7ae55d39 100644 --- a/src/macros/utils.rs +++ b/src/macros/utils.rs @@ -1,3 +1,18 @@ +use syn::{Expr, Lit, Meta}; + +pub(crate) fn get_named_string(args: &[Meta], name: &str) -> Option { + args.iter().find_map(|arg| { + let value = arg.require_name_value().ok()?; + let Expr::Lit(ref lit) = value.value else { + return None; + }; + let Lit::Str(ref str) = lit.lit else { + return None; + }; + value.path.is_ident(name).then_some(str.value()) + }) +} + #[must_use] pub(crate) fn camel_to_snake_string(s: &str) -> String { let mut output = String::with_capacity(