fluent_impl/
lib.rs

1/*
2    This file is a part of term-string.
3
4    Copyright (C) 2018 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
5    https://github.com/rust-alt/term-string
6
7    This Source Code Form is subject to the terms of the Mozilla Public
8    License, v. 2.0. If a copy of the MPL was not distributed with this
9    file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
10*/
11
12//! A procedural macro that generates chaining methods from non-chaining ones in an impl block.
13//!
14//!
15//! When applied to an impl block, `#[fluent_impl]` will scan all methods in the block
16//! in search for chain-able methods, and generate chaining methods
17//! from them.
18//!
19//! Chain-able methods are the ones with `&mut self` as a first argument, and return nothing.
20//! That's it, there are no other restrictions.
21//!
22//! # Usage
23//! Add `fluent-impl` to the dependencies in `Cargo.toml`:
24//!
25//! ``` toml
26//! [dependencies]
27//! fluent-impl = "0.1"
28//! ```
29//!
30//! Then add the following to the top of `src/lib.rs`:
31//!
32//! ``` rust ignore
33//! extern crate fluent_impl;
34//!
35//! use fluent_impl::{fluent_impl, fluent_impl_opts};
36//!
37//! ```
38//!
39//! # Examples
40//!
41//! If we have a simple struct with a simple impl block:
42//!
43//! ``` rust
44//! #[derive(Default, PartialEq, Debug)]
45//! pub struct Simple {
46//!     num: i32,
47//! }
48//!
49//! impl Simple {
50//!     // ...
51//!     pub fn add_1(&mut self) {
52//!         self.num +=1;
53//!     }
54//! }
55//! ```
56//!
57//! Then we add the macro attribute to the impl block:
58//!
59//! ``` rust ignore
60//! # extern crate fluent_impl;
61//! # use fluent_impl::{fluent_impl, fluent_impl_opts};
62//! # struct Simple;
63//! #[fluent_impl]
64//! impl Simple {
65//!     // ...
66//!     pub fn add_1(&mut self) {
67//!         self.num +=1;
68//!     }
69//! }
70//! ```
71//!
72//! The macro will generate a new impl block with the content:
73//!
74//! ``` rust ignore
75//! #[doc = "Chaining (fluent) methods for [`Simple`]."]
76//! impl Simple {
77//!     #[doc = "The chaining (fluent) equivalent of [`add_1()`].\n\n [`add_1`]: Simple::add_1\n [`add_1()`]: Simple::add_1"]
78//!     pub fn with_add_1(mut self) -> Self {
79//!         self.add_1();
80//!         self
81//!     }
82//! }
83//! ```
84//!
85//! A full more involved example can be found bellow the *Attribute Configuration* section.
86//!
87//! # Attribute Configuration
88//!
89//! `#[fluent_impl]` is configurable with comma-separated options passed to the attribute
90//! itself, and options passed to a method-level attribute `#[fluent_impl_opts]`.
91//!
92//! ## `#[fluent_impl]` Attribute Options
93//! *(`inblock`, `non_public`, `prefix`, `impl_doc`, `doc`)*
94//!
95//!  *impl block*-level configuration.
96//!
97//!  ### Example
98//!
99//!  ``` rust ignore
100//!  #[fluent_impl(inblock, non_public, prefix="chain_")]
101//!  impl Simple {
102//!      // ...
103//!  }
104//!  ```
105//!
106//!  ### Options
107//!
108//!  * **`inblock`** (default: unset)
109//!
110//!    By default, a new impl block is generated, and chaining methods are added there.
111//!    If `inblock` is passed, every chaining method will be generated right below
112//!    the chain-able one.
113//!
114//!    The order in which methods appear on docs is probably the only reason why you
115//!    should care about this.
116//!
117//!    There is a corresponding method-level *`inblock`* option which will selectively enable
118//!    this behavior for individual methods.
119//!
120//!  * **`non_public`** (default: unset)
121//!
122//!    By default, non fully-public methods are skipped. If this option is passed, the macro
123//!    will generate chaining equivalents for chain-able private or partially-public methods.
124//!
125//!    There is a corresponding method-level *`non_public`* option which will selectively enable
126//!    this behavior for individual methods.
127//!
128//!  * **`prefix`** (default: "with_")
129//!
130//!    The default chaining method name is this prefix appended by the chain-able method name.
131//!
132//!    * *`prefix`* is not allowed to be an empty string. Check the *`name`* method-level option
133//!    if you want to name a chaining method to whatever you like.
134//!
135//!    There is a corresponding method-level *`prefix`* option which will selectively override
136//!    the value set here (or the default).
137//!
138//!  * **`impl_doc`** (default: "Chaining (fluent) methods for [\`%t%\`].")
139//!
140//!    If a new block is generated for the chaining methods, this is the doc string template
141//!    for it. `%t%` is replaced with the type path.
142//!
143//!  * **`doc`** (default: "The chaining (fluent) equivalent of [\`%f%()\`].")
144//!
145//!    Chaining method doc string template. `%t%` is replaced with the type path. `%f%` is
146//!    replaced with the chain-able method name.
147//!
148//!    Additionally, the following is effectively appended at the end:
149//!    ``` text
150//!     ///
151//!     /// [`%f%`]: %t%::%f%
152//!     /// [`%f%()`]: %t%::%f%
153//!    ```
154//!
155//!    This allows proper hyper-linking of ``[`%t%`]`` and ``[`%t%()`]``.
156//!
157//!    There is a corresponding method-level *`doc`* option which will selectively override
158//!    the value set here (or the default).
159//!
160//! ## `#[fluent_impl_opts]` Attribute Options
161//! *(`inblock`, `non_public`, `skip`, `prefix`, `rename`, `name`, `doc`)*
162//!
163//! Options passed to override block-level defaults, or set method-specific
164//! configurations.
165//!
166//! Unlike `#[fluent_impl]`, this attribute:
167//!  1. Applies to methods instead of impl blocks.
168//!  2. Can be passed multiple times to the same method if you please.
169//!
170//! ### Example
171//!
172//! ``` rust ignore
173//! #[fluent_impl]
174//! impl Simple {
175//!     #[fluent_impl_opts(non_public, inblock)]
176//!     #[fluent_impl_opts(prefix="chain_", rename="added_1")]
177//!     fn add_1(&mut self) {
178//!         // ...
179//!     }
180//! }
181//! ```
182//!
183//!  ### Options
184//!
185//!  #### Inherited
186//!
187//!  * **`inblock`** (default: inherit)
188//!
189//!    Set *`inblock`* for this specific method if it's not set for the block already.
190//!
191//!  * **`non_public`** (default: inherit)
192//!
193//!    Set *`non_public`* for this specific method if it's not set for the block already.
194//!
195//!    This allows generating chaining methods for specific private methods, or
196//!    partially public ones (e.g. `pub(crate)` methods).
197//!
198//!  * **`prefix`** (default: inherit)
199//!
200//!    Override the default, or the block value if set.
201//!
202//!    * *`prefix`* is not allowed to be an empty string.
203//!    * Method-specific *`prefix`* is not allowed to be set if *`name`*(see below) is set.
204//!
205//!  * **`doc`** (default: inherit)
206//!
207//!    Override the default, or the block value if set.
208//!
209//!  #### Method Specific
210//!
211//!  * **`skip`** (default: unset)
212//!
213//!    Skip this method. Don't generate anything from it.
214//!
215//!  * **`rename`** (default: chain-able name)
216//!
217//!    The default chaining method name is the prefix appended by the chain-able method
218//!    name. This option allows you to rename the name that gets added to the prefix.
219//!
220//!    * *`rename`* is not allowed to be an empty string.
221//!    * *`rename`* is not allowed to be set if *`name`*(see below) is set and vise versa.
222//!
223//!  * **`name`** (default: unset)
224//!
225//!    Set the name of the chaining method.
226//!
227//!    * *`name`* is not allowed to be set if method-specific *`prefix`* or *`rename`* is set.
228//!
229//!
230//! # Full Example
231//!
232//! ``` rust
233//! extern crate fluent_impl;
234//!
235//! pub mod m {
236//!     use fluent_impl::{fluent_impl, fluent_impl_opts};
237//!     use std::borrow::Borrow;
238//!     use std::ops::AddAssign;
239//!
240//!     #[derive(PartialEq, Debug)]
241//!     pub struct TCounter(pub u32);
242//!
243//!     #[derive(PartialEq, Debug)]
244//!     pub struct St<A: AddAssign> {
245//!         value: A,
246//!         text: String,
247//!     }
248//!
249//!     #[fluent_impl]
250//!     // impl block with generic arguments works
251//!     impl<A: AddAssign> St<A> {
252//!         // Constants (or any other items) in impl block are okay
253//!         pub(crate) const C_TC: u32 = 100;
254//!
255//!         pub fn new(value: A, text: String) -> Self {
256//!             Self { value, text }
257//!         }
258//!
259//!         pub fn get_value(&self) -> &A {
260//!             &self.value
261//!         }
262//!
263//!         pub fn get_text(&self) -> &str {
264//!             &self.text
265//!         }
266//!
267//!         #[fluent_impl_opts(rename = "added_value")]
268//!         // Destructuring patterns in method arguments are okay
269//!         pub fn add_value(
270//!             &mut self,
271//!             to_be_added: A,
272//!             TCounter(counter): &mut TCounter,
273//!         ) {
274//!             self.value += to_be_added;
275//!             *counter += 1;
276//!         }
277//!
278//!         #[fluent_impl_opts(rename = "appended_text")]
279//!         // Generic method arguments are okay
280//!         pub fn append_text<S: Borrow<str>>(&mut self, arg: S) {
281//!             self.text += arg.borrow();
282//!         }
283//!
284//!         #[fluent_impl_opts(rename = "appended_text_impl_trait")]
285//!         // Needless to say, impl Trait method arguments are also okay
286//!         pub fn append_text_impl_trait(&mut self, arg: impl Borrow<str>) {
287//!             self.text += arg.borrow();
288//!         }
289//!     }
290//! }
291//!
292//! fn main() {
293//!     use m::{St, TCounter};
294//!     // ========
295//!     let mut tc1 = TCounter(St::<u32>::C_TC);
296//!     let mut s1 = St::new(0u32, "".into());
297//!     s1.append_text("simple ");
298//!     s1.append_text::<&str>("turbo fish ");
299//!     s1.append_text_impl_trait("impl trait");
300//!     s1.add_value(5, &mut tc1);
301//!     assert_eq!(s1.get_text(), "simple turbo fish impl trait");
302//!     assert_eq!(tc1, TCounter(St::<u32>::C_TC + 1));
303//!     // ========
304//!     let mut tc2 = TCounter(St::<u32>::C_TC);
305//!     let s2 = St::new(0u32, "".into())
306//!         .with_appended_text("simple ")
307//!         .with_appended_text::<&str>("turbo fish ")
308//!         .with_appended_text_impl_trait("impl trait")
309//!         .with_added_value(5, &mut tc2);
310//!     assert_eq!(s2, s1);
311//!     assert_eq!(tc2, tc1);
312//! }
313//! ```
314
315extern crate proc_macro;
316extern crate proc_macro2;
317
318#[macro_use]
319extern crate syn;
320
321// Required by parse_quote!{}
322#[macro_use]
323extern crate quote;
324
325mod config;
326mod impl_block;
327mod method;
328mod type_utils;
329
330use proc_macro::TokenStream;
331use proc_macro2::TokenStream as TokenStream2;
332use syn::{Attribute, ImplItem, ItemImpl};
333
334use config::MacroConfig;
335
336// Dummy proc-macro for default overrides
337#[proc_macro_attribute]
338/// Check the top-level documentation of this crate
339pub fn fluent_impl_opts(_: TokenStream, input: TokenStream) -> TokenStream {
340    check_if_impl_item_method(input.clone()).expect("Invalid fluent_impl_opts position");
341    input
342}
343
344#[proc_macro_attribute]
345/// Check the top-level documentation of this crate
346pub fn fluent_impl(args: TokenStream, input: TokenStream) -> TokenStream {
347    let args: TokenStream2 = args.into();
348    let attr: Attribute = parse_quote! { #[fluent_impl(#args)] };
349    let attr_info = config::parse_config_from_attr(&attr).expect("Failed to parse macro attributes");
350    let macro_config = config::get_proc_macro_config(attr_info).expect("Failed to get config");
351    gen_fluent(input.into(), &macro_config)
352        .expect("Failed to generate fluent methods")
353        .into()
354}
355
356fn check_if_impl_item_method(input: TokenStream) -> Result<(), String> {
357    let err_msg = "only applies to methods in an impl block";
358    if let Ok(impl_item) = syn::parse::<ImplItem>(input) {
359        match impl_item {
360            ImplItem::Method(_) => (),
361            _ => Err(err_msg)?,
362        }
363    } else {
364        Err(err_msg)?;
365    }
366    Ok(())
367}
368
369fn gen_fluent(input: TokenStream2, macro_config: &MacroConfig) -> Result<TokenStream2, String> {
370    if let Ok(impl_block) = syn::parse2::<ItemImpl>(input) {
371        impl_block::gen_fluent_from_impl_block(&impl_block, macro_config)
372    } else {
373        Err("fluent_impl only applies to impl blocks")?
374    }
375}