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(), ¯o_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}