1#![feature(if_let_guard, try_blocks)]
5
6use {
8 core::iter,
9 proc_macro::TokenStream,
10 quote::ToTokens,
11 syn::{punctuated::Punctuated, spanned::Spanned},
12};
13
14#[proc_macro_attribute]
15pub fn cloned(attr: TokenStream, input: TokenStream) -> TokenStream {
16 let attrs = syn::parse_macro_input!(attr as Attrs);
17 let mut input = syn::parse_macro_input!(input as Input);
18
19 let clones = attrs
20 .clones
21 .into_iter()
22 .map(|attr| {
23 let ident = attr.ident;
24 let expr = match attr.expr {
25 Some(expr) => expr,
26 None => self::ident_to_expr(ident.clone()),
27 };
28 syn::parse_quote! {
29 let #ident = #expr.clone();
30 }
31 })
32 .collect::<Vec<syn::Stmt>>();
33
34 let wrap_expr = |expr: &mut syn::Expr, trailing_semi: Option<syn::Token![;]>| {
36 *expr = syn::parse_quote_spanned! {expr.span() => {
37 #![allow(clippy::semicolon_outside_block)]
38 #![allow(clippy::semicolon_if_nothing_returned)]
39
40 #( #clones )*
41 #expr
42 #trailing_semi
43 }};
44 };
45
46 match &mut input {
48 Input::Stmt { stmt } => match stmt {
49 syn::Stmt::Local(local) => match &mut local.init {
50 Some(init) => wrap_expr(&mut init.expr, None),
52 None => self::cannot_attach("uninitialized let binding"),
53 },
54 syn::Stmt::Item(_) => self::cannot_attach("item"),
55
56 syn::Stmt::Expr(expr, trailing_semi) => wrap_expr(expr, *trailing_semi),
58 syn::Stmt::Macro(_) => self::cannot_attach("macro call"),
59 },
60
61 Input::Expr { expr } => wrap_expr(expr, attrs.semi),
63 };
64
65 let output = match input {
67 Input::Stmt { stmt } => stmt.to_token_stream(),
68 Input::Expr { expr } => expr.to_token_stream(),
69 };
70
71 TokenStream::from(output)
72}
73
74
75enum Input {
77 Stmt { stmt: syn::Stmt },
78 Expr { expr: syn::Expr },
79}
80
81impl syn::parse::Parse for Input {
82 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
83 let is_stmt = input.fork().parse::<syn::Stmt>().is_ok();
86 if is_stmt {
87 let stmt = input.parse::<syn::Stmt>()?;
88 return Ok(Self::Stmt { stmt });
89 }
90
91 let expr = input.parse::<syn::Expr>()?;
93
94 let _trailing_comma = input.parse::<Option<syn::Token![,]>>()?;
96
97 Ok(Self::Expr { expr })
98 }
99}
100
101struct Attrs {
103 semi: Option<syn::Token![;]>,
105
106 clones: Vec<Attr>,
108}
109
110impl syn::parse::Parse for Attrs {
111 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
112 let clones = Punctuated::<_, syn::Token![,]>::parse_terminated_with(input, |input| {
113 let attr = input.parse::<Attr>()?;
114 let semi = input.parse::<Option<syn::Token![;]>>()?;
115
116 Ok((attr, semi))
117 })?;
118
119 let mut semi = None::<syn::Token![;]>;
120 let clones = clones
121 .into_iter()
122 .map(|(attr, new_semi)| {
123 if let Some(new_semi) = new_semi {
124 match semi {
125 Some(old_semi) =>
126 return Err(syn::Error::new(
127 old_semi.span,
128 "Unexpected `;`, only allowed to be trailing",
129 )),
130 None => semi = Some(new_semi),
131 }
132 }
133
134 Ok(attr)
135 })
136 .collect::<Result<_, _>>()?;
137
138 Ok(Self { semi, clones })
139 }
140}
141
142struct Attr {
144 ident: syn::Ident,
145 expr: Option<syn::Expr>,
146}
147
148impl syn::parse::Parse for Attr {
149 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
150 let ident = input.parse()?;
151 let expr = match input.parse::<Option<syn::Token![=]>>()? {
152 Some(_eq) => {
153 let expr = input.parse::<syn::Expr>()?;
154 Some(expr)
155 },
156 None => None,
157 };
158
159 Ok(Self { ident, expr })
160 }
161}
162
163fn ident_to_expr(ident: syn::Ident) -> syn::Expr {
165 syn::Expr::Path(syn::ExprPath {
166 attrs: Vec::new(),
167 qself: None,
168 path: syn::Path {
169 leading_colon: None,
170 segments: iter::once(syn::PathSegment {
171 ident,
172 arguments: syn::PathArguments::None,
173 })
174 .collect(),
175 },
176 })
177}
178
179fn cannot_attach(kind: &str) -> ! {
181 panic!("Cannot attach `#[cloned]` to {kind}");
182}