dioxus_rsx/
partial_closure.rs1use crate::PartialExpr;
2use proc_macro2::TokenStream;
3use quote::ToTokens;
4use std::hash::{Hash, Hasher};
5use syn::{
6 parse::{Parse, ParseStream},
7 punctuated::Punctuated,
8 Attribute, Expr, Pat, PatType, Result, ReturnType, Token, Type,
9};
10use syn::{BoundLifetimes, ExprClosure};
11
12#[derive(Debug, Clone)]
21pub struct PartialClosure {
22 pub lifetimes: Option<BoundLifetimes>,
23 pub constness: Option<Token![const]>,
24 pub movability: Option<Token![static]>,
25 pub asyncness: Option<Token![async]>,
26 pub capture: Option<Token![move]>,
27 pub or1_token: Token![|],
28 pub inputs: Punctuated<Pat, Token![,]>,
29 pub or2_token: Token![|],
30 pub output: ReturnType,
31 pub body: PartialExpr,
32}
33
34impl Parse for PartialClosure {
35 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
36 let lifetimes: Option<BoundLifetimes> = input.parse()?;
37 let constness: Option<Token![const]> = input.parse()?;
38 let movability: Option<Token![static]> = input.parse()?;
39 let asyncness: Option<Token![async]> = input.parse()?;
40 let capture: Option<Token![move]> = input.parse()?;
41 let or1_token: Token![|] = input.parse()?;
42
43 let mut inputs = Punctuated::new();
44 loop {
45 if input.peek(Token![|]) {
46 break;
47 }
48 let value = closure_arg(input)?;
49 inputs.push_value(value);
50 if input.peek(Token![|]) {
51 break;
52 }
53 let punct: Token![,] = input.parse()?;
54 inputs.push_punct(punct);
55 }
56
57 let or2_token: Token![|] = input.parse()?;
58
59 let output = if input.peek(Token![->]) {
60 let arrow_token: Token![->] = input.parse()?;
61 let ty: Type = input.parse()?;
62 ReturnType::Type(arrow_token, Box::new(ty))
63 } else {
64 ReturnType::Default
65 };
66
67 let body = PartialExpr::parse(input)?;
68
69 Ok(PartialClosure {
70 lifetimes,
71 constness,
72 movability,
73 asyncness,
74 capture,
75 or1_token,
76 inputs,
77 or2_token,
78 output,
79 body,
80 })
81 }
82}
83
84impl ToTokens for PartialClosure {
85 fn to_tokens(&self, tokens: &mut TokenStream) {
86 self.lifetimes.to_tokens(tokens);
87 self.constness.to_tokens(tokens);
88 self.movability.to_tokens(tokens);
89 self.asyncness.to_tokens(tokens);
90 self.capture.to_tokens(tokens);
91 self.or1_token.to_tokens(tokens);
92 self.inputs.to_tokens(tokens);
93 self.or2_token.to_tokens(tokens);
94 self.output.to_tokens(tokens);
95 self.body.to_tokens(tokens);
96 }
97}
98
99impl PartialEq for PartialClosure {
100 fn eq(&self, other: &Self) -> bool {
101 self.lifetimes == other.lifetimes
102 && self.constness == other.constness
103 && self.movability == other.movability
104 && self.asyncness == other.asyncness
105 && self.capture == other.capture
106 && self.or1_token == other.or1_token
107 && self.inputs == other.inputs
108 && self.or2_token == other.or2_token
109 && self.output == other.output
110 && self.body == other.body
111 }
112}
113
114impl Eq for PartialClosure {}
115impl Hash for PartialClosure {
116 fn hash<H: Hasher>(&self, state: &mut H) {
117 self.lifetimes.hash(state);
118 self.constness.hash(state);
119 self.movability.hash(state);
120 self.asyncness.hash(state);
121 self.capture.hash(state);
122 self.or1_token.hash(state);
123 self.inputs.hash(state);
124 self.or2_token.hash(state);
125 self.output.hash(state);
126 self.body.hash(state);
127 }
128}
129
130impl PartialClosure {
131 pub fn as_expr(&self) -> Result<Expr> {
134 let expr_closure = ExprClosure {
135 attrs: Vec::new(),
136 asyncness: self.asyncness,
137 capture: self.capture,
138 inputs: self.inputs.clone(),
139 output: self.output.clone(),
140 lifetimes: self.lifetimes.clone(),
141 constness: self.constness,
142 movability: self.movability,
143 or1_token: self.or1_token,
144 or2_token: self.or2_token,
145
146 body: Box::new(self.body.as_expr()?),
148 };
149
150 Ok(Expr::Closure(expr_closure))
151 }
152}
153
154fn closure_arg(input: ParseStream) -> Result<Pat> {
157 let attrs = input.call(Attribute::parse_outer)?;
158 let mut pat = Pat::parse_single(input)?;
159
160 if input.peek(Token![:]) {
161 Ok(Pat::Type(PatType {
162 attrs,
163 pat: Box::new(pat),
164 colon_token: input.parse()?,
165 ty: input.parse()?,
166 }))
167 } else {
168 match &mut pat {
169 Pat::Const(pat) => pat.attrs = attrs,
170 Pat::Ident(pat) => pat.attrs = attrs,
171 Pat::Lit(pat) => pat.attrs = attrs,
172 Pat::Macro(pat) => pat.attrs = attrs,
173 Pat::Or(pat) => pat.attrs = attrs,
174 Pat::Paren(pat) => pat.attrs = attrs,
175 Pat::Path(pat) => pat.attrs = attrs,
176 Pat::Range(pat) => pat.attrs = attrs,
177 Pat::Reference(pat) => pat.attrs = attrs,
178 Pat::Rest(pat) => pat.attrs = attrs,
179 Pat::Slice(pat) => pat.attrs = attrs,
180 Pat::Struct(pat) => pat.attrs = attrs,
181 Pat::Tuple(pat) => pat.attrs = attrs,
182 Pat::TupleStruct(pat) => pat.attrs = attrs,
183 Pat::Wild(pat) => pat.attrs = attrs,
184 Pat::Type(_) => unreachable!(),
185 Pat::Verbatim(_) => {}
186 _ => {}
187 }
188 Ok(pat)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use quote::quote;
196
197 #[test]
198 fn parses() {
199 let doesnt_parse: Result<ExprClosure> = syn::parse2(quote! {
200 |a, b| { method. }
201 });
202
203 assert!(doesnt_parse.is_err());
205
206 let parses: Result<PartialClosure> = syn::parse2(quote! {
207 |a, b| { method. }
208 });
209
210 let parses = parses.unwrap();
212 dbg!(parses.to_token_stream().to_string());
213 }
214
215 #[test]
216 fn parses_real_world() {
217 let parses: Result<PartialClosure> = syn::parse2(quote! {
218 move |_| {
219 let mut sidebar = SHOW_SIDEBAR.write();
220 *sidebar = !*sidebar;
221 }
222 });
223
224 let parses = parses.unwrap();
226 dbg!(parses.to_token_stream().to_string());
227 parses.as_expr().unwrap();
228
229 let parses: Result<PartialClosure> = syn::parse2(quote! {
230 move |_| {
231 rsx! {
232 div { class: "max-w-lg lg:max-w-2xl mx-auto mb-16 text-center",
233 "gomg"
234 "hi!!"
235 "womh"
236 }
237 };
238 println!("hi")
239 }
240 });
241 parses.unwrap().as_expr().unwrap();
242 }
243
244 #[test]
245 fn partial_eqs() {
246 let a: PartialClosure = syn::parse2(quote! {
247 move |e| {
248 println!("clicked!");
249 }
250 })
251 .unwrap();
252
253 let b: PartialClosure = syn::parse2(quote! {
254 move |e| {
255 println!("clicked!");
256 }
257 })
258 .unwrap();
259
260 let c: PartialClosure = syn::parse2(quote! {
261 move |e| {
262 println!("unclicked");
263 }
264 })
265 .unwrap();
266
267 assert_eq!(a, b);
268 assert_ne!(a, c);
269 }
270
271 #[test]
273 fn same_to_tokens() {
274 let a: PartialClosure = syn::parse2(quote! {
275 move |e| {
276 println!("clicked!");
277 }
278 })
279 .unwrap();
280
281 let b: PartialClosure = syn::parse2(quote! {
282 move |e| {
283 println!("clicked!");
284 }
285 })
286 .unwrap();
287
288 let c: ExprClosure = syn::parse2(quote! {
289 move |e| {
290 println!("clicked!");
291 }
292 })
293 .unwrap();
294
295 assert_eq!(
296 a.to_token_stream().to_string(),
297 b.to_token_stream().to_string()
298 );
299
300 assert_eq!(
301 a.to_token_stream().to_string(),
302 c.to_token_stream().to_string()
303 );
304
305 let a: PartialClosure = syn::parse2(quote! {
306 move |e| println!("clicked!")
307 })
308 .unwrap();
309
310 let b: ExprClosure = syn::parse2(quote! {
311 move |e| println!("clicked!")
312 })
313 .unwrap();
314
315 assert_eq!(
316 a.to_token_stream().to_string(),
317 b.to_token_stream().to_string()
318 );
319 }
320}