zng_app_proc_macros/
widget_util.rs

1use std::{collections::HashMap, fmt, mem};
2
3use proc_macro2::{Ident, Span, TokenStream, TokenTree};
4use quote::{ToTokens, quote};
5use syn::{
6    ext::IdentExt,
7    parse::{Parse, discouraged::Speculative},
8    punctuated::Punctuated,
9    spanned::Spanned,
10    *,
11};
12
13use crate::{
14    util::{self, Attributes, ErrorRecoverable, Errors, parse_outer_attrs, parse_punct_terminated2, path_span, peek_any3},
15    wgt_property_attrs::PropertyAttrData,
16};
17
18/// Represents a property assign or when block.
19pub enum WgtItem {
20    Property(WgtProperty),
21    When(WgtWhen),
22}
23
24/// Represents a property assign.
25pub struct WgtProperty {
26    /// Attributes.
27    pub attrs: Attributes,
28    /// Path to property.
29    pub path: Path,
30    /// The ::<T> part of the path, if present it is removed from `path`.
31    pub generics: TokenStream,
32    /// Optional value, if not defined the property must be assigned to its own name.
33    pub value: Option<(Token![=], PropertyValue)>,
34    /// Optional terminator.
35    pub semi: Option<Token![;]>,
36}
37impl WgtProperty {
38    /// Gets the property name.
39    pub fn ident(&self) -> &Ident {
40        &self.path.segments.last().unwrap().ident
41    }
42
43    /// Gets if this property is assigned `unset!`.
44    pub fn is_unset(&self) -> bool {
45        if let Some((_, PropertyValue::Special(special, _))) = &self.value {
46            special == "unset"
47        } else {
48            false
49        }
50    }
51
52    /// If `custom_attrs_expand` is needed.
53    pub fn has_custom_attrs(&self) -> bool {
54        !self.attrs.others.is_empty()
55    }
56
57    /// Gets custom attributes expansion data if any was present in the property.
58    pub fn custom_attrs_expand(&self, builder: Ident, is_when: bool) -> TokenStream {
59        debug_assert!(self.has_custom_attrs());
60
61        let attrs = &self.attrs.others;
62
63        let path_slug = util::display_path(&self.path).replace("::", "_");
64        PropertyAttrData {
65            pending_attrs: attrs.clone(),
66            data_ident: ident!("__property_custom_expand_data_p_{}__", path_slug),
67            builder,
68            is_unset: self.is_unset(),
69            is_when,
70            property: self.path.clone(),
71        }
72        .to_token_stream()
73    }
74}
75impl Parse for WgtProperty {
76    fn parse(input: parse::ParseStream) -> Result<Self> {
77        let attrs = Attribute::parse_outer(input)?;
78
79        let path: Path = input.parse()?;
80        if input.peek(Token![!]) {
81            // cause error.
82            input.parse::<Token![=]>()?;
83        }
84        let (path, generics) = split_path_generics(path)?;
85
86        let value = if input.peek(Token![=]) {
87            Some((input.parse()?, input.parse()?))
88        } else {
89            None
90        };
91
92        Ok(WgtProperty {
93            attrs: Attributes::new(attrs),
94            path,
95            generics,
96            value,
97            semi: input.parse()?,
98        })
99    }
100}
101
102pub(crate) fn split_path_generics(mut path: Path) -> Result<(Path, TokenStream)> {
103    path.leading_colon = None;
104    if let Some(s) = path.segments.last_mut() {
105        let mut generics = quote!();
106        match mem::replace(&mut s.arguments, PathArguments::None) {
107            PathArguments::None => {}
108            PathArguments::AngleBracketed(p) => {
109                generics = p.to_token_stream();
110            }
111            PathArguments::Parenthesized(p) => return Err(syn::Error::new(p.span(), "expected property path or generics")),
112        }
113        Ok((path, generics))
114    } else {
115        Err(syn::Error::new(path.span(), "expected property ident in path"))
116    }
117}
118
119pub struct PropertyField {
120    pub ident: Ident,
121    #[expect(dead_code)]
122    pub colon: Token![:],
123    pub expr: TokenStream,
124}
125impl Parse for PropertyField {
126    fn parse(input: parse::ParseStream) -> Result<Self> {
127        let ident = input.parse()?;
128        let colon;
129        let expr;
130        if input.peek(Token![:]) {
131            colon = input.parse()?;
132            expr = {
133                let mut t = quote!();
134                while !input.is_empty() {
135                    if input.peek(Token![,]) {
136                        break;
137                    }
138                    let tt = input.parse::<TokenTree>().unwrap();
139                    tt.to_tokens(&mut t);
140                }
141                t
142            };
143        } else {
144            colon = parse_quote!(:);
145            expr = quote!(#ident);
146        };
147
148        Ok(PropertyField { ident, colon, expr })
149    }
150}
151
152// Value assigned in a [`PropertyAssign`].
153pub enum PropertyValue {
154    /// `unset!`.
155    Special(Ident, #[expect(dead_code)] Token![!]),
156    /// `arg0, arg1,`
157    Unnamed(TokenStream),
158    /// `{ field0: true, field1: false, }`
159    Named(#[expect(dead_code)] syn::token::Brace, Punctuated<PropertyField, Token![,]>),
160}
161impl Parse for PropertyValue {
162    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
163        if input.peek(Ident) && input.peek2(Token![!]) && (input.peek3(Token![;]) || input.peek3(Ident::peek_any) || !peek_any3(input)) {
164            let ident: Ident = input.parse().unwrap();
165            if ident != "unset" {
166                return Err(Error::new(ident.span(), "unknown special value, expected `unset!`"));
167            }
168            let r = PropertyValue::Special(ident, input.parse().unwrap());
169            return Ok(r);
170        }
171
172        if input.peek(token::Brace) && !input.peek2(Token![,]) {
173            // Differentiating between a fields declaration and a single unnamed arg declaration gets tricky.
174            //
175            // This is a normal fields decl.: `{ field0: "value" }`
176            // This is a block single argument decl.: `{ foo(); bar() }`
177            //
178            // Fields can use the shorthand field name only `{ field0 }`
179            // witch is also a single arg block expression. In this case
180            // we parse as Unnamed, if it was a field it will still work because
181            // we only have one field.
182
183            let maybe_fields = input.fork();
184            let fields_input;
185            let fields_brace = braced!(fields_input in maybe_fields);
186
187            if fields_input.peek(Ident)
188                && (
189                    // ident:
190                    (fields_input.peek2(Token![:]) && !fields_input.peek2(Token![::]))
191                    // OR ident,
192                    || fields_input.peek2(Token![,])
193                )
194            {
195                // it is fields
196                input.advance_to(&maybe_fields);
197
198                // disconnect syn internal errors
199                let fields_input = fields_input.parse::<TokenStream>().unwrap();
200                let r = parse_punct_terminated2(fields_input).map_err(|e| {
201                    if util::span_is_call_site(e.span()) {
202                        util::recoverable_err(fields_brace.span.join(), e)
203                    } else {
204                        e.set_recoverable()
205                    }
206                })?;
207                return Ok(PropertyValue::Named(fields_brace, r));
208            }
209        }
210
211        // only valid option left is a sequence of "{expr},", we want to parse
212        // in a recoverable way, so first we take raw token trees until we find the
213        // end "`;` | EOF" or we find the start of a new property or when item.
214        let mut args_input = TokenStream::new();
215        while !input.is_empty() && !input.peek(Token![;]) {
216            if peek_next_wgt_item(&input.fork()) {
217                break;
218            }
219            input.parse::<TokenTree>().unwrap().to_tokens(&mut args_input);
220        }
221
222        Ok(PropertyValue::Unnamed(args_input))
223    }
224}
225
226fn peek_next_wgt_item(lookahead: parse::ParseStream) -> bool {
227    let has_attr = lookahead.peek(Token![#]) && lookahead.peek(token::Bracket);
228    if has_attr {
229        let _ = parse_outer_attrs(lookahead, &mut Errors::default());
230    }
231    if lookahead.peek(keyword::when) {
232        return true; // when ..
233    }
234
235    if lookahead.peek(Token![pub]) {
236        let _ = lookahead.parse::<Visibility>();
237    }
238    if lookahead.peek(Ident) {
239        if lookahead.peek2(Token![::]) {
240            let _ = lookahead.parse::<Path>();
241        } else {
242            let _ = lookahead.parse::<Ident>().unwrap();
243        }
244
245        return lookahead.peek(Token![=]) && !lookahead.peek(Token![==]);
246    }
247
248    false
249}
250
251pub mod keyword {
252    syn::custom_keyword!(when);
253}
254
255pub struct WgtWhen {
256    pub attrs: Attributes,
257    #[expect(dead_code)]
258    pub when: keyword::when,
259    pub condition_expr: TokenStream,
260    pub condition_expr_str: String,
261    #[expect(dead_code)]
262    pub brace_token: syn::token::Brace,
263    pub assigns: Vec<WgtProperty>,
264}
265impl WgtWhen {
266    /// Call only if peeked `when`. Parse outer attribute before calling.
267    pub fn parse(input: parse::ParseStream, errors: &mut Errors) -> Option<WgtWhen> {
268        let when = input.parse::<keyword::when>().unwrap_or_else(|e| non_user_error!(e));
269
270        if input.is_empty() {
271            errors.push("expected when expression", when.span());
272            return None;
273        }
274        let condition_expr = parse_without_eager_brace(input);
275
276        let (brace_token, assigns) = if input.peek(syn::token::Brace) {
277            let (brace, inner) = util::parse_braces(input).unwrap();
278            let mut assigns = vec![];
279            while !inner.is_empty() {
280                let attrs = parse_outer_attrs(&inner, errors);
281
282                if !(inner.peek(Ident::peek_any) || inner.peek(Token![super]) || inner.peek(Token![self])) {
283                    errors.push(
284                        "expected property path",
285                        if inner.is_empty() { brace.span.join() } else { inner.span() },
286                    );
287                    while !(inner.is_empty()
288                        || inner.peek(Ident::peek_any)
289                        || inner.peek(Token![super])
290                        || inner.peek(Token![self])
291                        || inner.peek(Token![#]) && inner.peek(token::Bracket))
292                    {
293                        // skip to next property.
294                        let _ = inner.parse::<TokenTree>();
295                    }
296                }
297                if inner.is_empty() {
298                    break;
299                }
300
301                match inner.parse::<WgtProperty>() {
302                    Ok(mut p) => {
303                        p.attrs = Attributes::new(attrs);
304                        if !inner.is_empty() && p.semi.is_none() {
305                            errors.push("expected `,`", inner.span());
306                            while !(inner.is_empty()
307                                || input.peek(Ident::peek_any)
308                                || input.peek(Token![crate])
309                                || input.peek(Token![super])
310                                || input.peek(Token![self])
311                                || inner.peek(Token![#]) && inner.peek(token::Bracket))
312                            {
313                                // skip to next property.
314                                let _ = inner.parse::<TokenTree>();
315                            }
316                        }
317
318                        if let Some((_, PropertyValue::Special(s, _))) = &p.value {
319                            errors.push(format!("cannot {s} in when assign"), s.span());
320                        }
321
322                        assigns.push(p);
323                    }
324                    Err(e) => {
325                        let (recoverable, e) = e.recoverable();
326                        if util::span_is_call_site(e.span()) {
327                            errors.push(e, brace.span.join());
328                        } else {
329                            errors.push_syn(e);
330                        }
331                        if !recoverable {
332                            break;
333                        }
334                    }
335                }
336            }
337            (brace, assigns)
338        } else {
339            errors.push("expected a when block expr and properties", util::last_span(condition_expr));
340            return None;
341        };
342
343        let expr_str = condition_expr.to_string().replace(" # ", "__pound__");
344        let expr_str = util::format_rust_expr(expr_str).replace("__pound__", "#");
345
346        Some(WgtWhen {
347            attrs: Attributes::new(vec![]), // must be parsed before.
348            when,
349            condition_expr_str: expr_str,
350            condition_expr,
351            brace_token,
352            assigns,
353        })
354    }
355}
356
357/// Like [`syn::Expr::parse_without_eager_brace`] but does not actually parse anything and includes
358/// the braces of interpolation.
359fn parse_without_eager_brace(input: parse::ParseStream) -> TokenStream {
360    let mut r = TokenStream::default();
361    let mut is_start = true;
362    while !input.is_empty() {
363        if input.peek(Token![match]) || input.peek(Token![while]) {
364            // keyword
365            input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
366            // expr
367            r.extend(parse_without_eager_brace(input));
368            // block
369            if input.peek(token::Brace) {
370                input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
371            }
372        } else if input.peek(Token![if]) {
373            // keyword
374            input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
375            // expr
376            r.extend(parse_without_eager_brace(input));
377            // block
378            if input.peek(token::Brace) {
379                input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
380
381                if input.peek(Token![else]) {
382                    input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
383                    if input.peek(token::Brace) {
384                        // else { }
385                        input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
386                    } else {
387                        // maybe another if
388                        continue;
389                    }
390                }
391            }
392        } else if input.peek(Token![loop]) {
393            // keyword
394            input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
395            // block
396            if input.peek(token::Brace) {
397                input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
398            }
399        } else if input.peek(Token![for]) {
400            // keyword (for)
401            input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
402            while !input.is_empty() && !input.peek(Token![in]) {
403                input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
404            }
405            if !input.is_empty() {
406                // keyword (in)
407                input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
408                //expr
409                r.extend(parse_without_eager_brace(input));
410                // block
411                if input.peek(token::Brace) {
412                    input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
413                }
414            }
415        } else if input.peek2(token::Brace) {
416            if input.peek(Token![#]) {
417                // #
418                let tt = input.parse::<TokenTree>().unwrap();
419                tt.to_tokens(&mut r);
420                // { .. }
421                let tt = input.parse::<TokenTree>().unwrap();
422                tt.to_tokens(&mut r);
423
424                if input.peek(token::Brace) {
425                    break; // found { } after expr or Struct #{ }
426                }
427            } else {
428                // item before brace
429                let tt = input.parse::<TokenTree>().unwrap();
430                tt.to_tokens(&mut r);
431                break;
432            }
433        } else if !is_start && input.peek(token::Brace) {
434            break; // found { } after expr
435        } else {
436            let tt = input.parse::<TokenTree>().unwrap();
437            tt.to_tokens(&mut r);
438        }
439        is_start = false;
440    }
441    r
442}
443
444#[derive(PartialEq, Eq, Hash)]
445pub(crate) enum WhenInputMember {
446    Named(Ident),
447    Index(usize),
448}
449impl fmt::Display for WhenInputMember {
450    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451        match self {
452            WhenInputMember::Named(n) => write!(f, "{n}"),
453            WhenInputMember::Index(i) => write!(f, "{i}"),
454        }
455    }
456}
457impl ToTokens for WhenInputMember {
458    fn to_tokens(&self, tokens: &mut TokenStream) {
459        match self {
460            WhenInputMember::Named(ident) => ident.to_tokens(tokens),
461            WhenInputMember::Index(i) => i.to_tokens(tokens),
462        }
463    }
464}
465
466pub(crate) struct WhenExpr {
467    /// Map of `(property_path, member) => var_name`, example: `(id, 0) => __w_id__0`.
468    pub inputs: HashMap<(syn::Path, WhenInputMember), Ident>,
469    pub expr: TokenStream,
470}
471impl Parse for WhenExpr {
472    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
473        let mut inputs = HashMap::new();
474        let mut expr = TokenStream::default();
475
476        while !input.is_empty() {
477            if input.peek(Token![#]) && input.peek2(Ident) {
478                let tt = input.parse::<Token![#]>().unwrap();
479                let last_span = tt.span();
480
481                let property = input.parse::<Path>().map_err(|e| {
482                    if util::span_is_call_site(e.span()) {
483                        syn::Error::new(last_span, e)
484                    } else {
485                        e
486                    }
487                })?;
488
489                let path_slug = util::display_path(&property).replace("::", "_");
490
491                let mut member = WhenInputMember::Index(0);
492                let mut var_ident = ident_spanned!(path_span(&property)=> "w_{path_slug}_m_0");
493                if input.peek(Token![.]) && !input.peek2(Token![await]) && !input.peek3(token::Paren) {
494                    let _: Token![.] = input.parse()?;
495                    if input.peek(Ident) {
496                        let m = input.parse::<Ident>().unwrap();
497                        var_ident = ident_spanned!(m.span()=> "w_{path_slug}_m_{m}");
498                        member = WhenInputMember::Named(m);
499                    } else {
500                        let index = input.parse::<syn::Index>().map_err(|e| {
501                            let span = if util::span_is_call_site(e.span()) { last_span } else { e.span() };
502
503                            syn::Error::new(span, "expected identifier or index")
504                        })?;
505                        member = WhenInputMember::Index(index.index as usize);
506                        var_ident = ident_spanned!(index.span()=> "w_{path_slug}_m_{}", index.index);
507                    }
508                }
509
510                expr.extend(quote_spanned! {var_ident.span()=>
511                    #{ #var_ident }
512                });
513
514                inputs.insert((property, member), var_ident);
515            }
516            // recursive parse groups:
517            else if input.peek(token::Brace) {
518                let inner;
519                let group = syn::braced!(inner in input);
520                let inner = WhenExpr::parse(&inner)?;
521                inputs.extend(inner.inputs);
522                group.surround(&mut expr, |e| e.extend(inner.expr));
523            } else if input.peek(token::Paren) {
524                let inner;
525                let group = syn::parenthesized!(inner in input);
526                let inner = WhenExpr::parse(&inner)?;
527                inputs.extend(inner.inputs);
528                group.surround(&mut expr, |e| e.extend(inner.expr));
529            } else if input.peek(token::Bracket) {
530                let inner;
531                let group = syn::bracketed!(inner in input);
532                let inner = WhenExpr::parse(&inner)?;
533                inputs.extend(inner.inputs);
534                group.surround(&mut expr, |e| e.extend(inner.expr));
535            }
536            // keep other tokens the same:
537            else {
538                let tt = input.parse::<TokenTree>().unwrap();
539                tt.to_tokens(&mut expr)
540            }
541        }
542
543        Ok(WhenExpr { inputs, expr })
544    }
545}
546
547pub struct WidgetCustomRules {
548    pub rules: Vec<WidgetCustomRule>,
549}
550impl Parse for WidgetCustomRules {
551    fn parse(input: parse::ParseStream) -> Result<Self> {
552        let inner;
553        braced!(inner in input);
554        let mut rules = vec![];
555        while !inner.is_empty() {
556            rules.push(inner.parse()?);
557        }
558        Ok(WidgetCustomRules { rules })
559    }
560}
561
562/// Represents a custom widget macro rule.
563pub struct WidgetCustomRule {
564    /// Rule tokens, `(<rule>) => { .. };`.
565    pub rule: TokenStream,
566    /// Init tokens, `(..) => { <init> };`
567    pub init: TokenStream,
568}
569impl Parse for WidgetCustomRule {
570    fn parse(input: parse::ParseStream) -> Result<Self> {
571        let rule;
572        parenthesized!(rule in input);
573        let rule = rule.parse()?;
574
575        let _ = input.parse::<Token![=>]>()?;
576
577        let init;
578        braced!(init in input);
579        let init = init.parse()?;
580
581        if input.peek(Token![;]) {
582            let _ = input.parse::<Token![;]>();
583        };
584
585        Ok(WidgetCustomRule { rule, init })
586    }
587}
588
589// expansion of `macro_rules! source_location`
590pub fn source_location(crate_core: &TokenStream, location: Span) -> TokenStream {
591    let source_location = quote_spanned! {location=>
592        #crate_core::widget::builder::SourceLocation {
593            file: std::file!(),
594            line: std::line!(),
595            column: std::column!(),
596        }
597    };
598    util::set_stream_span(source_location, location)
599}