batch_impl/lib.rs
1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/tutorial.md"))]
3// The library uses no unsafe; missing docs are rejected as errors (only for pub items;
4// internal pub(crate) is exempt).
5#![forbid(unsafe_code)]
6#![deny(missing_docs)]
7// The MSVC linker prints "creating library ... and object ..." to stdout, which rustc
8// treats as linker_messages warnings; these are harmless Windows link-product notices,
9// suppressed globally.
10#![allow(linker_messages)]
11// The `delimiter!` macro is defined at the top of preprocess and imported into the crate
12// root via `#[macro_use]`; textual scope requires its declaration to precede all users
13// (fuzz / parse / this module).
14#[macro_use]
15pub(crate) mod preprocess;
16#[cfg(test)]
17mod testing;
18use proc_macro2::{TokenStream, TokenTree};
19use syn::{ItemTrait, parse_macro_input};
20
21mod analyze;
22mod apply;
23mod ast;
24mod codegen;
25mod entry;
26mod parse;
27mod util;
28
29pub(crate) use analyze::TraitBounds;
30pub(crate) use entry::{expand_attr_macro, expand_batch_trait};
31
32use preprocess::{build_from_item, get_trait_item, parse_names_from_tokens};
33use util::compile_error_str;
34
35/// Attribute macro that generates `impl` blocks for a trait in batch.
36///
37/// Annotate a trait definition with `#[batch_impl(...)]`; every impl-spec in the macro
38/// arguments generates a corresponding `impl` block for that trait.
39///
40/// ## Syntax
41///
42/// ```text
43/// #[batch_impl( impl-spec [, impl-spec]* [{ body }]? )]
44/// ```
45///
46/// An impl-spec has three parts (the tail of each part may be omitted):
47/// - `<impl generics>` — generic params of the `impl` block
48/// - `Trait name<trait generics>` — the trait's generic args and associated type bindings
49/// - target type — wrapped in `[]` for a parallel list, `^`/`-` for generic application
50///
51/// ## Examples
52///
53/// ```
54/// # use batch_impl::batch_impl;
55/// #[batch_impl(usize, isize)]
56/// trait Numeric {}
57///
58/// #[batch_impl(<T> Vec<T>)]
59/// trait Collection {}
60///
61/// #[batch_impl(<T> FromValue<T> [i32 { fn wrap(_: T) -> Self { 0 }}, u32 #wrap{0}] )]
62/// trait FromValue<T> { fn wrap(val: T) -> Self; }
63///
64/// // #name{body} also supports const and type items
65/// #[batch_impl(usize #MY_CONST{42})]
66/// trait HasConst { const MY_CONST: usize; }
67///
68/// ```
69#[proc_macro_attribute]
70pub fn batch_impl(
71 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
72) -> proc_macro::TokenStream {
73 let trait_item = parse_macro_input!(item as ItemTrait);
74 expand_attr_macro(attr.into(), trait_item, true)
75 .map(proc_macro::TokenStream::from)
76 .unwrap_or_else(Into::into)
77}
78
79/// Same as `#[batch_impl]`, but discards the annotated trait definition and only emits
80/// `impl` blocks.
81///
82/// For traits already defined elsewhere where only batched impl generation is needed. The
83/// annotated trait merely serves as the "signature source of truth" for the directive system:
84/// `#name`/`#fill`/`#delegate` read item signatures from it, and the open extension
85/// `#name(args){body}` hands (method name list, body, the whole trait) to the user's
86/// same-named function-like macro (see README "Directive System"). The syntax is identical
87/// to `#[batch_impl]`.
88///
89/// ## Examples
90///
91/// ```
92/// # use batch_impl::batch_impl_only;
93/// trait Greet { fn hello(&self) -> &str; }
94///
95/// #[batch_impl_only(usize #hello{"hi"})]
96/// trait Greet { fn hello(&self) -> &str; } // this trait definition is dropped, existing definitions are unaffected
97/// // Written with batch_impl_only instead of batch_trait to use the directive system; write it verbatim at the trait definition site
98/// ```
99#[proc_macro_attribute]
100pub fn batch_impl_only(
101 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
102) -> proc_macro::TokenStream {
103 let trait_item = parse_macro_input!(item as ItemTrait);
104 expand_attr_macro(attr.into(), trait_item, false)
105 .map(proc_macro::TokenStream::from)
106 .unwrap_or_else(Into::into)
107}
108
109/// Function-like macro that generates `impl` blocks for a declared trait in batch.
110///
111/// Syntax: `unsafe? Trait path: impl-specs;`, with `;` separating multiple trait segments.
112/// After each segment's `:` comes a DSL expression (type DSL + `@` constants, same as
113/// `#[batch_impl]`).
114///
115/// **`#` directives are not supported** (`#fill`/`#delegate`/`#blanket`/open extension):
116/// directives need the trait definition as the signature source of truth, which `batch_trait!`
117/// as a function-like macro cannot access; use `#[batch_impl]` / `#[batch_impl_only]` when
118/// you need directives.
119///
120/// ## Examples
121///
122/// ```
123/// # use batch_impl::batch_trait;
124/// trait A {}
125/// trait B<T> {}
126/// unsafe trait UnsafeTrait{}
127///
128/// batch_trait!(
129/// A: usize, isize;
130/// B: <T> B<T> Vec<T>;
131/// unsafe UnsafeTrait: usize
132/// );
133/// ```
134///
135/// Path traits (such as `foo::C`) are supported too; see tests/regression.rs.
136#[proc_macro]
137pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
138 expand_batch_trait(input).unwrap_or_else(Into::into)
139}
140
141/// Test-only open-extension macro (function-like): `name!{(method name list){body} trait T {...}}`.
142///
143/// Parses the method name list, body, and trait definition from the macro input, generating
144/// `fn signature { body }` per method (reusing the trait signature) — equivalent to handing
145/// the `#fill` implementation to the user.
146///
147/// Used to verify open instruction extension: `#name(args){body}` expands to
148/// `{name!{(args){body} trait ...}}`, with the macro call landing in the impl body and being
149/// expanded by the user macro into the needed fn definitions based on the trait
150/// (see section 28 of `tests/dsl.rs`).
151///
152/// Design point: this must be a **function-like macro call** `name!{...}`, not an
153/// `#[name[...]] trait ...` attribute — a trait is not a valid item inside an impl block
154/// (`#[attr] trait` cannot appear in an impl), whereas a function-like macro in an impl
155/// body position is expanded by rustc into associated items.
156#[doc(hidden)]
157#[proc_macro]
158pub fn batch_preprocess_test(
159 input: proc_macro::TokenStream,
160) -> proc_macro::TokenStream {
161 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
162 let tokens = match preprocess::angle_collect(&tokens) {
163 Ok(v) => v,
164 Err(e) => return e.into(),
165 };
166 // Shape: `(add, inc) {*self+1} trait AddInc {...}`
167 let Some(TokenTree::Group(names_group)) = tokens.first() else {
168 return compile_error_str(
169 "batch-impl: batch_preprocess_test expects `(method name list){body} trait ...`",
170 tokens
171 .first()
172 .map(|t| t.span())
173 .unwrap_or_else(proc_macro2::Span::call_site),
174 )
175 .into();
176 };
177 if names_group.delimiter() != delimiter![()] {
178 return compile_error_str(
179 "batch-impl: batch_preprocess_test expects `(method name list){body} trait ...`",
180 tokens
181 .first()
182 .map(|t| t.span())
183 .unwrap_or_else(proc_macro2::Span::call_site),
184 )
185 .into();
186 }
187 let Some(TokenTree::Group(body_group)) = tokens.get(1) else {
188 return compile_error_str(
189 "batch-impl: batch_preprocess_test expects `(method name list){body} trait ...`",
190 tokens
191 .get(1)
192 .map(|t| t.span())
193 .unwrap_or_else(proc_macro2::Span::call_site),
194 )
195 .into();
196 };
197 if body_group.delimiter() != delimiter![{}] {
198 return compile_error_str(
199 "batch-impl: batch_preprocess_test expects `(method name list){body} trait ...`",
200 tokens
201 .get(1)
202 .map(|t| t.span())
203 .unwrap_or_else(proc_macro2::Span::call_site),
204 )
205 .into();
206 }
207 let trait_ts = tokens[2..].iter().cloned().collect();
208 let trait_item = match syn::parse2(trait_ts) {
209 Ok(t) => t,
210 Err(_) => {
211 return compile_error_str(
212 "batch-impl: batch_preprocess_test cannot parse the trait definition",
213 proc_macro2::Span::call_site(),
214 )
215 .into();
216 }
217 };
218 let names = match parse_names_from_tokens(
219 &names_group.stream().into_iter().collect::<Vec<_>>(),
220 &trait_item,
221 ) {
222 Ok(names) => names,
223 Err(e) => return e.into(),
224 };
225 let body = body_group.stream();
226 let mut methods = TokenStream::new();
227 for name in &names {
228 let item = match get_trait_item(&trait_item, name) {
229 Ok(item) => item,
230 Err(e) => return e.into(),
231 };
232 methods.extend(build_from_item(item, &body));
233 }
234 preprocess::render_angles(methods).into()
235}