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
18pub enum WgtItem {
20 Property(WgtProperty),
21 When(WgtWhen),
22}
23
24pub struct WgtProperty {
26 pub attrs: Attributes,
28 pub path: Path,
30 pub generics: TokenStream,
32 pub value: Option<(Token![=], PropertyValue)>,
34 pub semi: Option<Token![;]>,
36}
37impl WgtProperty {
38 pub fn ident(&self) -> &Ident {
40 &self.path.segments.last().unwrap().ident
41 }
42
43 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 pub fn has_custom_attrs(&self) -> bool {
54 !self.attrs.others.is_empty()
55 }
56
57 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 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
152pub enum PropertyValue {
154 Special(Ident, #[expect(dead_code)] Token![!]),
156 Unnamed(TokenStream),
158 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 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 (fields_input.peek2(Token![:]) && !fields_input.peek2(Token![::]))
191 || fields_input.peek2(Token![,])
193 )
194 {
195 input.advance_to(&maybe_fields);
197
198 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 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; }
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 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 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 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![]), when,
349 condition_expr_str: expr_str,
350 condition_expr,
351 brace_token,
352 assigns,
353 })
354 }
355}
356
357fn 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 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
366 r.extend(parse_without_eager_brace(input));
368 if input.peek(token::Brace) {
370 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
371 }
372 } else if input.peek(Token![if]) {
373 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
375 r.extend(parse_without_eager_brace(input));
377 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 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
386 } else {
387 continue;
389 }
390 }
391 }
392 } else if input.peek(Token![loop]) {
393 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
395 if input.peek(token::Brace) {
397 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
398 }
399 } else if input.peek(Token![for]) {
400 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 input.parse::<TokenTree>().unwrap().to_tokens(&mut r);
408 r.extend(parse_without_eager_brace(input));
410 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 let tt = input.parse::<TokenTree>().unwrap();
419 tt.to_tokens(&mut r);
420 let tt = input.parse::<TokenTree>().unwrap();
422 tt.to_tokens(&mut r);
423
424 if input.peek(token::Brace) {
425 break; }
427 } else {
428 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; } 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 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 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 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
562pub struct WidgetCustomRule {
564 pub rule: TokenStream,
566 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
589pub 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}