rama_macros/lib.rs
1//! Macros for [`rama`].
2//!
3//! There are no more macros for Rama. We used to have an `AsRef` one,
4//! but it is recommended to either not use a macro for that anymore,
5//! write one yourself or use a thirdparty crate such as `derive_more`.
6//!
7//! [`rama`]: https://crates.io/crates/rama
8//!
9//! ## Paste
10//!
11//! The nightly-only [`concat_idents!`] macro in the Rust standard library is
12//! notoriously underpowered in that its concatenated identifiers can only refer to
13//! existing items, they can never be used to define something new.
14//!
15//! [`concat_idents!`]: https://doc.rust-lang.org/std/macro.concat_idents.html
16//!
17//! This crate provides a flexible way to paste together identifiers in a macro,
18//! including using pasted identifiers to define new items.
19//!
20//! This approach works with any Rust compiler 1.31+.
21//!
22//! <br>
23//!
24//! # Pasting identifiers
25//!
26//! Within the `paste!` macro, identifiers inside `[<`...`>]` are pasted
27//! together to form a single identifier.
28//!
29//! ```
30//! use rama_macros::paste;
31//!
32//! paste! {
33//! // Defines a const called `QRST`.
34//! const [<Q R S T>]: &str = "success!";
35//! }
36//!
37//! assert_eq!(
38//! paste! { [<Q R S T>].len() },
39//! 8,
40//! );
41//! ```
42//!
43//! <br><br>
44//!
45//! # More elaborate example
46//!
47//! The next example shows a macro that generates accessor methods for some
48//! struct fields. It demonstrates how you might find it useful to bundle a
49//! paste invocation inside of a macro\_rules macro.
50//!
51//! ```
52//! use rama_macros::paste;
53//!
54//! macro_rules! make_a_struct_and_getters {
55//! ($name:ident { $($field:ident),* }) => {
56//! // Define a struct. This expands to:
57//! //
58//! // pub struct S {
59//! // a: String,
60//! // b: String,
61//! // c: String,
62//! // }
63//! pub struct $name {
64//! $(
65//! $field: String,
66//! )*
67//! }
68//!
69//! // Build an impl block with getters. This expands to:
70//! //
71//! // impl S {
72//! // pub fn get_a(&self) -> &str { &self.a }
73//! // pub fn get_b(&self) -> &str { &self.b }
74//! // pub fn get_c(&self) -> &str { &self.c }
75//! // }
76//! paste! {
77//! impl $name {
78//! $(
79//! pub fn [<get_ $field>](&self) -> &str {
80//! &self.$field
81//! }
82//! )*
83//! }
84//! }
85//! }
86//! }
87//!
88//! make_a_struct_and_getters!(S { a, b, c });
89//!
90//! fn call_some_getters(s: &S) -> bool {
91//! s.get_a() == s.get_b() && s.get_c().is_empty()
92//! }
93//! #
94//! # fn main() {}
95//! ```
96//!
97//! <br><br>
98//!
99//! # Case conversion
100//!
101//! Use `$var:lower` or `$var:upper` in the segment list to convert an
102//! interpolated segment to lower- or uppercase as part of the paste. For
103//! example, `[<ld_ $reg:lower _expr>]` would paste to `ld_bc_expr` if invoked
104//! with $reg=`Bc`.
105//!
106//! Use `$var:snake` to convert CamelCase input to snake\_case.
107//! Use `$var:camel` to convert snake\_case to CamelCase.
108//! These compose, so for example `$var:snake:upper` would give you SCREAMING\_CASE.
109//!
110//! The precise Unicode conversions are as defined by [`str::to_lowercase`] and
111//! [`str::to_uppercase`].
112//!
113//! [`str::to_lowercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase
114//! [`str::to_uppercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase
115//!
116//! <br>
117//!
118//! # Pasting documentation strings
119//!
120//! Within the `paste!` macro, arguments to a #\[doc ...\] attribute are
121//! implicitly concatenated together to form a coherent documentation string.
122//!
123//! ```
124//! use rama_macros::paste;
125//!
126//! macro_rules! method_new {
127//! ($ret:ident) => {
128//! paste! {
129//! #[doc = "Create a new `" $ret "` object."]
130//! pub fn new() -> $ret { todo!() }
131//! }
132//! };
133//! }
134//!
135//! pub struct Paste {}
136//!
137//! method_new!(Paste); // expands to #[doc = "Create a new `Paste` object"]
138//! ```
139
140#![doc(
141 html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
142)]
143#![doc(
144 html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
145)]
146#![cfg_attr(docsrs, feature(doc_cfg))]
147#![cfg_attr(test, allow(clippy::float_cmp))]
148#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))]
149#![expect(
150 clippy::unwrap_used,
151 clippy::expect_used,
152 clippy::panic,
153 clippy::unwrap_in_result,
154 clippy::panic_in_result_fn,
155 reason = "proc-macro crate: panics are the historical compile-error mechanism for vendored macros (paste, etc.)"
156)]
157// `unreachable!()` is used in test fixtures (e.g. `|_| unreachable!()` callbacks for
158// resolve_path tests in include_dir_macro). Scope the expect to cfg(test) so it only
159// applies where it actually fires.
160#![cfg_attr(
161 test,
162 expect(
163 clippy::unreachable,
164 reason = "test fixtures use unreachable!() in callbacks that aren't invoked"
165 )
166)]
167
168use proc_macro::TokenStream;
169
170mod extension_macro;
171mod from_extensions_macro;
172mod from_ref_macro;
173mod include_dir_macro;
174mod paste_macro;
175mod utils;
176
177#[proc_macro]
178pub fn paste(input: TokenStream) -> TokenStream {
179 let mut contains_paste = false;
180 let flatten_single_interpolation = true;
181 match paste_macro::expand(
182 input.clone(),
183 &mut contains_paste,
184 flatten_single_interpolation,
185 ) {
186 Ok(expanded) => {
187 if contains_paste {
188 expanded
189 } else {
190 input
191 }
192 }
193 Err(err) => err.to_compile_error(),
194 }
195}
196
197/// Embed the contents of a directory in your crate.
198#[proc_macro]
199pub fn include_dir(input: TokenStream) -> TokenStream {
200 include_dir_macro::execute(input)
201}
202
203/// Derive an implementation of [`FromRef`] for each field in a struct.
204///
205/// `#[from_ref(skip)]` can be used to skip specific fields
206#[proc_macro_derive(FromRef, attributes(from_ref))]
207pub fn derive_from_ref(item: TokenStream) -> TokenStream {
208 from_ref_macro::expand_with(item, from_ref_macro::from_ref::expand)
209}
210
211/// Derive an implementation of `rama_core::extensions::Extension` for a type.
212///
213/// Note that all type parameters of the derived type must satisfy:
214/// `Any + Send + Sync + Debug + 'static`.
215///
216/// Optional derive attributes:
217/// - `#[extension(tags(...))]`: implement one or more marker traits used to group
218/// extensions in rustdoc (`tls`, `http`, `net`, `ua`, `proxy`, `ws`, `dns`, `grpc`).
219#[proc_macro_derive(Extension, attributes(extension))]
220pub fn derive_extension(item: TokenStream) -> TokenStream {
221 from_ref_macro::expand_with(item, extension_macro::extension::expand)
222}
223
224/// Derive a `from_extensions` constructor that gathers extension pieces from a
225/// `rama_core::extensions::Extensions` store in a single pass.
226///
227/// On a struct, each named field must be `Option<&'a T>` (borrowed) or
228/// `Option<Arc<T>>` (owned Arc clone), and the two may be mixed. A field may
229/// also be `Option<(&'a T, usize)>` / `Option<(Arc<T>, usize)>` to additionally
230/// capture the entry's traversal rank (`0` is the newest value seen, growing for
231/// older ones, so ranks order fields by recency). A borrowed field requires the
232/// struct to carry the matching lifetime (`struct View<'a>`); an all-`Arc`
233/// struct needs no lifetime. Generates `fn from_extensions(ext: &Extensions) ->
234/// Self`, where each field uses the same lookup as `Extensions::get_ref` but the
235/// store is traversed only once.
236///
237/// A rank is a completely opaque type and should only be used to compare positions,
238/// it does not tell anything about the absolute position.
239///
240/// On an enum, each variant is a one-field tuple variant naming a candidate
241/// type (`Variant(&'a T)` or `Variant(Arc<T>)`, optionally `Variant((&'a T,
242/// usize))`). Generates `fn from_extensions(ext: &Extensions) -> Option<Self>`
243/// which returns the candidate inserted most recently (newest-wins by traversal
244/// rank), or `None` if none are present. If two variants name the same type the
245/// tie is broken deterministically in favour of the earlier declared variant.
246#[proc_macro_derive(FromExtensions)]
247pub fn derive_from_extensions(item: TokenStream) -> TokenStream {
248 from_ref_macro::expand_with(item, from_extensions_macro::expand)
249}