Skip to main content

letclone/
lib.rs

1//! # letclone
2//!
3//! A procedural macro for convenient variable cloning in Rust.
4//!
5//! ## Overview
6//!
7//! `letclone` provides a [`clone!`] macro that simplifies the common pattern of
8//! cloning variables into new bindings. Instead of writing verbose `let` statements
9//! with `.clone()` calls, you can use the concise `clone!` macro.
10//!
11//! The macro is especially useful when working with closures that need to capture
12//! cloned values, as it reduces boilerplate code significantly.
13//!
14//! ## Notes
15//!
16//! `clone!` transparently unwraps `syn::Expr::Group` and continues processing the
17//! inner expression. This matters mostly for tokens produced by macro expansion,
18//! where `Group` is an implementation detail rather than a user-written Rust
19//! expression.
20//!
21//! This is different from a source-level parenthesized expression such as `(a)`
22//! or `(a + b)`, which parses as `syn::Expr::Paren` and is not treated as a
23//! supported clone target.
24//!
25//! ## Examples
26//!
27//! ### Basic Usage
28//!
29//! ```rust
30//! use letclone::clone;
31//!
32//! let original = String::from("hello");
33//! clone!(original);
34//! // Equivalent to: let original = original.clone();
35//! ```
36//!
37//! ### Field Access
38//!
39//! ```rust
40//! use letclone::clone;
41//!
42//! struct Person {
43//!     name: String,
44//! }
45//!
46//! let person = Person { name: String::from("Alice") };
47//! clone!(person.name);
48//! // Equivalent to: let name = person.name.clone();
49//! assert_eq!(name, "Alice");
50//! ```
51//!
52//! ### Mutable Bindings
53//!
54//! ```rust
55//! use letclone::clone;
56//!
57//! let original = String::from("hello");
58//! clone!(mut original);
59//! original.push_str(" world");
60//! assert_eq!(original, "hello world");
61//! ```
62//!
63//! ### Multiple Expressions
64//!
65//! ```rust
66//! use letclone::clone;
67//!
68//! let a = String::from("a");
69//! let b = String::from("b");
70//! clone!(a, b);
71//! // Equivalent to:
72//! // let a = a.clone();
73//! // let b = b.clone();
74//! ```
75//!
76//! ### Usage in Closures
77//!
78//! ```rust
79//! use letclone::clone;
80//!
81//! let name = String::from("Alice");
82//! let scores = vec![85, 90, 95];
83//!
84//! let closure = {
85//!     clone!(name, scores);
86//!     move || {
87//!         println!("Name: {}, Scores: {:?}", name, scores);
88//!     }
89//! };
90//! ```
91
92use quote::{ToTokens, quote};
93use syn::parse::{Parse, ParseStream};
94use syn::spanned::Spanned;
95use syn::{Expr, ExprGroup, Token};
96
97/// Represents a cloneable expression with optional `mut` modifier
98struct CloneExpr {
99    mutability: Option<Token![mut]>,
100    inner: Expr,
101}
102
103impl Parse for CloneExpr {
104    fn parse(input: ParseStream) -> syn::Result<Self> {
105        let mutability = if input.peek(Token![mut]) {
106            Some(input.parse()?)
107        } else {
108            None
109        };
110        let inner: Expr = input.parse()
111            .map_err(|e| syn::Error::new(e.span(), "expected a valid expression: field access (a.b), tuple index access (a.0), method call (a.method()), or path (var)"))?;
112        Ok(CloneExpr { mutability, inner })
113    }
114}
115
116impl ToTokens for CloneExpr {
117    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
118        tokens.extend(quote! { let });
119        if let Some(m) = &self.mutability {
120            tokens.extend(quote! { #m });
121        }
122        let inner = &self.inner;
123        extend(inner, tokens);
124    }
125}
126
127fn expr_variant_description(expr: &Expr) -> &'static str {
128    match expr {
129        Expr::Array(_) => "array expression",
130        Expr::Assign(_) => "assignment expression",
131        Expr::Async(_) => "async block",
132        Expr::Await(_) => "await expression",
133        Expr::Binary(_) => "binary expression",
134        Expr::Block(_) => "block expression",
135        Expr::Break(_) => "break expression",
136        Expr::Call(_) => "function call expression",
137        Expr::Cast(_) => "cast expression",
138        Expr::Closure(_) => "closure expression",
139        Expr::Const(_) => "const block",
140        Expr::Continue(_) => "continue expression",
141        Expr::Field(_) => "field access expression",
142        Expr::ForLoop(_) => "for loop expression",
143        Expr::Group(_) => "grouped expression",
144        Expr::If(_) => "if expression",
145        Expr::Index(_) => "index expression",
146        Expr::Infer(_) => "inferred expression",
147        Expr::Let(_) => "let expression",
148        Expr::Lit(_) => "literal expression",
149        Expr::Loop(_) => "loop expression",
150        Expr::Macro(_) => "macro expression",
151        Expr::Match(_) => "match expression",
152        Expr::MethodCall(_) => "method call expression",
153        Expr::Paren(_) => "parenthesized expression",
154        Expr::Path(_) => "path expression",
155        Expr::Range(_) => "range expression",
156        Expr::RawAddr(_) => "raw address expression",
157        Expr::Reference(_) => "reference expression",
158        Expr::Repeat(_) => "array repeat expression",
159        Expr::Return(_) => "return expression",
160        Expr::Struct(_) => "struct literal expression",
161        Expr::Try(_) => "try expression",
162        Expr::TryBlock(_) => "try block",
163        Expr::Tuple(_) => "tuple expression",
164        Expr::Unary(_) => "unary expression",
165        Expr::Unsafe(_) => "unsafe block",
166        Expr::Verbatim(_) => "verbatim expression",
167        Expr::While(_) => "while expression",
168        Expr::Yield(_) => "yield expression",
169        _ => "expression",
170    }
171}
172
173fn extend(expr: &Expr, tokens: &mut proc_macro2::TokenStream) {
174    match expr {
175        Expr::Field(syn::ExprField {
176            base,
177            member: syn::Member::Named(field_name),
178            ..
179        }) => {
180            tokens.extend(quote! {
181                #field_name = #base.#field_name.clone();
182            });
183        }
184        Expr::Field(syn::ExprField {
185            base,
186            member: syn::Member::Unnamed(index),
187            ..
188        }) => {
189            let index_num = index.index;
190            let ident = syn::Ident::new(&format!("field_{}", index_num), index.span());
191            tokens.extend(quote! {
192                #ident = #base.#index.clone();
193            });
194        }
195        Expr::MethodCall(expr_method_call) => {
196            let method = &expr_method_call.method;
197            tokens.extend(quote! {
198                #method = #expr.clone();
199            });
200        }
201        Expr::Path(syn::ExprPath { path, .. }) => {
202            let ident = &path.segments.last().unwrap().ident;
203            tokens.extend(quote! {
204                #ident = #expr.clone();
205            });
206        }
207        Expr::Group(ExprGroup { expr, .. }) => {
208            extend(&expr, tokens);
209        }
210        _ => {
211            panic!(
212                "clone! macro does not support {}. Supported forms: field access (`a.b`), tuple index access (`a.0`), method call (`a.method()`), or path (`var`).",
213                expr_variant_description(expr)
214            );
215        }
216    }
217}
218
219/// Represents a list of clone expressions
220struct CloneExprList {
221    exprs: Vec<CloneExpr>,
222}
223
224impl Parse for CloneExprList {
225    fn parse(input: ParseStream) -> syn::Result<Self> {
226        let mut exprs = Vec::new();
227        while !input.is_empty() {
228            let expr: CloneExpr = input.parse().map_err(|e| {
229                syn::Error::new(e.span(), format!("failed to parse clone expression: {}", e))
230            })?;
231            exprs.push(expr);
232            if input.peek(Token![,]) {
233                let _comma: Token![,] = input.parse()?;
234            } else {
235                break;
236            }
237        }
238        if exprs.is_empty() {
239            return Err(syn::Error::new(
240                input.span(),
241                "clone! macro requires at least one expression",
242            ));
243        }
244        Ok(CloneExprList { exprs })
245    }
246}
247
248impl ToTokens for CloneExprList {
249    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
250        for expr in &self.exprs {
251            expr.to_tokens(tokens);
252        }
253    }
254}
255
256/// Generates `let var = expr.clone();` statements for one or more expressions
257///
258/// # Supported expression types
259/// - Field access: `clone!(obj.field)` -> `let field = obj.field.clone();`
260/// - Nested field access: `clone!(a.b.c)` -> `let c = a.b.c.clone();`
261/// - Tuple index: `clone!(tuple.0)` -> `let field_0 = tuple.0.clone();`
262/// - Nested tuple index: `clone!(obj.tuple.0)` -> `let field_0 = obj.tuple.0.clone();`
263/// - Method call: `clone!(obj.method())` -> `let method = obj.method().clone();`
264/// - Nested method call: `clone!(a.b.method())` -> `let method = a.b.method().clone();`
265/// - Path/variable: `clone!(var)` -> `let var = var.clone();`
266///
267/// # Using `mut` modifier
268/// - `clone!(mut obj.field)` -> `let mut field = obj.field.clone();`
269/// - `clone!(mut tuple.0)` -> `let mut field_0 = tuple.0.clone();`
270/// - `clone!(mut a.b.c)` -> `let mut c = a.b.c.clone();`
271///
272/// # Multiple expressions
273/// - `clone!(a, b.field, mut c)` -> generates multiple let statements
274///
275/// # Usage in closures
276/// The macro is particularly useful for cloning values before moving them into closures:
277/// ```
278/// use letclone::clone;
279///
280/// let name = String::from("Alice");
281/// let scores = vec![85, 90, 95];
282///
283/// let closure = {
284///     clone!(name, scores);
285///     move || {
286///         println!("Name: {}, Scores: {:?}", name, scores);
287///     }
288/// };
289/// ```
290#[proc_macro]
291pub fn clone(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
292    let expr_list = syn::parse_macro_input!(input as CloneExprList);
293    let mut tokens = proc_macro2::TokenStream::new();
294    expr_list.to_tokens(&mut tokens);
295    proc_macro::TokenStream::from(tokens)
296}