1use proc_macro2::{Span, TokenStream, TokenTree};
2use quote::{ToTokens, quote};
3use syn::{ext::IdentExt, parse::Parse, spanned::Spanned, *};
4
5use crate::{
6 util::{self, ErrorRecoverable, Errors, parse_outer_attrs},
7 widget_util::{self, WgtItem, WgtProperty, WgtWhen},
8};
9
10lazy_static! {
11 static ref DOCS_JS: String = {
12 let js = include_str!("../js/widget.js");
13 let js = minifier::js::minify(js);
14 format!("<script>{js}</script>")
15 };
16}
17
18pub fn expand(args: proc_macro::TokenStream, input: proc_macro::TokenStream, mixin: bool) -> proc_macro::TokenStream {
19 let struct_ = parse_macro_input!(input as ItemStruct);
21 let parent;
22
23 if let Fields::Unnamed(f) = &struct_.fields {
24 if f.unnamed.len() != 1 {
25 let mut r = syn::Error::new(struct_.fields.span(), "expected `struct Name(Parent);`")
26 .to_compile_error()
27 .to_token_stream();
28 struct_.to_tokens(&mut r);
29 return r.into();
30 }
31 parent = f.unnamed[0].to_token_stream();
32 } else {
33 let mut r = syn::Error::new(struct_.fields.span(), "expected `struct Name(Parent);`")
34 .to_compile_error()
35 .to_token_stream();
36 struct_.to_tokens(&mut r);
37 return r.into();
38 }
39
40 let crate_core = util::crate_core();
41
42 let (mixin_p, mixin_p_bounded) = if mixin {
43 if struct_.generics.params.is_empty() {
44 let mut r = syn::Error::new(struct_.ident.span(), "mix-ins must have one generic `P`")
45 .to_compile_error()
46 .to_token_stream();
47 struct_.to_tokens(&mut r);
48 return r.into();
49 } else if struct_.generics.params.len() > 1 {
50 let mut r = syn::Error::new(struct_.generics.span(), "mix-ins must have one generic `P` only")
51 .to_compile_error()
52 .to_token_stream();
53 struct_.to_tokens(&mut r);
54 return r.into();
55 }
56 match struct_.generics.params.first().unwrap() {
57 GenericParam::Lifetime(l) => {
58 let mut r = syn::Error::new(l.span(), "mix-ins must have one generic `P` only")
59 .to_compile_error()
60 .to_token_stream();
61 struct_.to_tokens(&mut r);
62 return r.into();
63 }
64 GenericParam::Const(c) => {
65 let mut r = syn::Error::new(c.span(), "mix-ins must have one generic `P` only")
66 .to_compile_error()
67 .to_token_stream();
68 struct_.to_tokens(&mut r);
69 return r.into();
70 }
71
72 GenericParam::Type(t) => {
73 if !t.bounds.is_empty() || t.default.is_some() {
74 let mut r = syn::Error::new(t.span(), "mix-ins must have one unbounded generic `P`")
75 .to_compile_error()
76 .to_token_stream();
77 struct_.to_tokens(&mut r);
78 return r.into();
79 }
80 if let Some(where_) = &struct_.generics.where_clause {
81 let mut r = syn::Error::new(where_.span(), "mix-ins must have one unbounded generic `P`")
82 .to_compile_error()
83 .to_token_stream();
84 struct_.to_tokens(&mut r);
85 return r.into();
86 }
87
88 let id = &t.ident;
89 (quote!(<#id>), quote!(<#id : #crate_core::widget::base::WidgetImpl>))
90 }
91 }
92 } else {
93 if !struct_.generics.params.is_empty() {
94 let mut r = syn::Error::new(struct_.generics.span(), "widgets cannot be generic")
95 .to_compile_error()
96 .to_token_stream();
97 struct_.to_tokens(&mut r);
98 return r.into();
99 }
100 (quote!(), quote!())
101 };
102
103 let (struct_path, custom_rules) = if mixin {
105 if !args.is_empty() {
106 let span = match syn::parse::<Args>(args) {
107 Ok(a) => a.path.span(),
108 Err(e) => e.span(),
109 };
110 let mut r = syn::Error::new(span, "mix-ins do not need a `$crate` path")
111 .to_compile_error()
112 .to_token_stream();
113 struct_.to_tokens(&mut r);
114 return r.into();
115 }
116
117 (quote!(), vec![])
118 } else {
119 match syn::parse::<Args>(args) {
120 Ok(a) => (a.path.to_token_stream(), a.custom_rules),
121 Err(e) => {
122 let mut r = e.to_compile_error().to_token_stream();
123 struct_.to_tokens(&mut r);
124 return r.into();
125 }
126 }
127 };
128
129 let vis = struct_.vis;
130 let ident = struct_.ident;
131 let mut attrs = util::Attributes::new(struct_.attrs);
132 if mixin {
133 attrs.tag_doc("m", "Widget mix-in struct");
134 } else {
135 attrs.tag_doc("W", "Widget struct and macro");
136 }
137 let allow_deprecated = attrs.deprecated.as_ref().map(|_| {
138 quote! {
139 #[allow(deprecated)]
140 }
141 });
142
143 let struct_token = struct_.struct_token;
144
145 let (macro_r, start_r) = if mixin {
146 (quote!(), quote!())
147 } else {
148 let struct_path_str = struct_path.to_string().replace(' ', "");
149 let struct_path_slug = path_slug(&struct_path_str);
150
151 let val_span = util::last_span(struct_path.clone());
152 let validate_path_ident = ident_spanned!(val_span=> "zzz_widget_path_{struct_path_slug}");
153 let validate_path = quote_spanned! {val_span=>
154 #[doc(hidden)]
155 #[allow(unused)]
156 #[allow(non_snake_case)]
157 mod #validate_path_ident {
158 macro_rules! #validate_path_ident {
159 () => {
160 #allow_deprecated
161 use #struct_path;
162 }
163 }
164 #validate_path_ident!{}
165 }
166 };
167
168 let custom_rules = {
169 let mut tt = quote!();
170 for widget_util::WidgetCustomRule { rule, init } in custom_rules {
171 tt.extend(quote! {
172 (#rule) => {
173 #struct_path! {
174 #init
175 }
176 };
177 })
178 }
179 tt
180 };
181
182 let macro_ident = ident!("{}__", struct_path_slug);
183
184 let struct_path = util::set_stream_span(struct_path, Span::call_site());
186
187 let macro_new = quote! {
188 zng::__proc_macro_util::widget::widget_new! {
189 new {
190 let mut wgt__ = #struct_path::widget_new();
191 let wgt__ = &mut wgt__;
192 }
193 build {
194 let wgt__ = wgt__.widget_build();
195 wgt__
196 }
197 set { $($tt)* }
198 }
199 };
200
201 let macro_docs = if util::is_rust_analyzer() {
202 let docs = &attrs.docs;
203 quote! {
204 #(#docs)*
205 }
206 } else {
207 quote! {
208 #[doc(hidden)]
209 }
210 };
211
212 let r = quote! {
213 #macro_docs
214 #[macro_export]
215 macro_rules! #macro_ident {
216 (zng_widget: $($tt:tt)*) => {
218 #macro_new
219 };
220
221 ($(#[$attr:meta])* $($property_path:ident)::+ = $($rest:tt)*) => {
223 #struct_path! {
224 zng_widget: $(#[$attr])* $($property_path)::+ = $($rest)*
225 }
226 };
227 ($(#[$attr:meta])* when $($rest:tt)*) => {
229 #struct_path! {
230 zng_widget: $(#[$attr])* when $($rest)*
231 }
232 };
233
234 #custom_rules
236
237 ($($tt:tt)*) => {
239 #struct_path! {
240 zng_widget: $($tt)*
241 }
242 };
243 }
244 #[doc(hidden)]
245 #[allow(unused_imports)]
246 #vis use #macro_ident as #ident;
247 #validate_path
248 };
249
250 let source_location = widget_util::source_location(&crate_core, ident.span());
251 let start_r = quote! {
252 #allow_deprecated
253 impl #mixin_p_bounded #ident #mixin_p {
254 pub fn widget_new() -> Self {
256 <Self as #crate_core::widget::base::WidgetImpl>::inherit(Self::widget_type())
257 }
258
259 pub fn widget_type() -> #crate_core::widget::builder::WidgetType {
261 #crate_core::widget::builder::WidgetType {
262 type_id: std::any::TypeId::of::<Self>(),
263 path: #struct_path_str,
264 location: #source_location,
265 }
266 }
267 }
268 };
269
270 (r, start_r)
271 };
272
273 let docs_js = if attrs.docs.is_empty() {
274 quote!()
276 } else {
277 let docs_js = DOCS_JS.as_str();
278 quote!(#[doc=#docs_js])
279 };
280
281 let r = quote! {
282 #attrs
283 #docs_js
284 #vis #struct_token #ident #mixin_p(#parent);
285 #allow_deprecated
286 impl #mixin_p std::ops::Deref for #ident #mixin_p {
287 type Target = #parent;
288
289 fn deref(&self) -> &Self::Target {
290 &self.0
291 }
292 }
293 #allow_deprecated
294 impl #mixin_p std::ops::DerefMut for #ident #mixin_p {
295 fn deref_mut(&mut self) -> &mut Self::Target {
296 &mut self.0
297 }
298 }
299 #start_r
300
301 #[doc(hidden)]
302 #allow_deprecated
303 impl #mixin_p_bounded #crate_core::widget::base::WidgetImpl for #ident #mixin_p {
304 fn inherit(widget: #crate_core::widget::builder::WidgetType) -> Self {
305 let mut wgt = Self(<#parent as #crate_core::widget::base::WidgetImpl>::inherit(widget));
306 *#crate_core::widget::base::WidgetImpl::base(&mut wgt).widget_importance() = #crate_core::widget::builder::Importance::WIDGET;
307 {
308 use #crate_core::widget::base::WidgetImpl;
309 wgt.widget_intrinsic();
310 }
311 *#crate_core::widget::base::WidgetImpl::base(&mut wgt).widget_importance() = #crate_core::widget::builder::Importance::INSTANCE;
312 wgt
313 }
314
315 fn base(&mut self) -> &mut #crate_core::widget::base::WidgetBase {
316 #crate_core::widget::base::WidgetImpl::base(&mut self.0)
317 }
318
319 fn base_ref(&self) -> &#crate_core::widget::base::WidgetBase {
320 #crate_core::widget::base::WidgetImpl::base_ref(&self.0)
321 }
322
323 fn info_instance__() -> Self {
324 Self(<#parent as #crate_core::widget::base::WidgetImpl>::info_instance__())
325 }
326 }
327
328 #macro_r
329 };
330 r.into()
331}
332
333struct Args {
334 path: TokenStream,
335 custom_rules: Vec<widget_util::WidgetCustomRule>,
336}
337impl Parse for Args {
338 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
339 let fork = input.fork();
340 match (fork.parse::<Token![$]>(), fork.parse::<syn::Path>()) {
341 (Ok(_), Ok(p)) => {
342 let has_custom_rules = fork.peek(token::Brace);
343 if has_custom_rules {
344 let _ = fork.parse::<TokenTree>();
345 }
346 if fork.is_empty() {
347 if p.segments[0].ident == "crate" {
348 let mut raw_parts = vec![];
349 while !input.is_empty() {
350 raw_parts.push(input.parse::<TokenTree>().unwrap());
351 }
352
353 let mut path = quote!();
354 let mut custom_rules = vec![];
355
356 if has_custom_rules {
357 let rules = raw_parts.pop().unwrap().to_token_stream();
358 custom_rules = syn::parse2::<widget_util::WidgetCustomRules>(rules)?.rules;
359 }
360 for part in &raw_parts {
361 part.to_tokens(&mut path);
362 }
363
364 Ok(Args { path, custom_rules })
365 } else {
366 Err(syn::Error::new(p.segments[0].ident.span(), "expected `crate`"))
367 }
368 } else {
369 Err(syn::Error::new(fork.span(), "unexpected token"))
370 }
371 }
372 (Ok(_), Err(e)) => {
373 if !util::span_is_call_site(e.span()) {
374 Err(e)
375 } else {
376 Err(syn::Error::new(util::last_span(input.parse().unwrap()), e.to_string()))
377 }
378 }
379 _ => Err(syn::Error::new(
380 input.span(),
381 "expected a macro_rules `$crate` path to this widget mod",
382 )),
383 }
384 }
385}
386
387struct Properties {
388 errors: Errors,
389 items: Vec<WgtItem>,
390}
391impl Parse for Properties {
392 fn parse(input: parse::ParseStream) -> Result<Self> {
393 let mut errors = Errors::default();
394 let mut items = vec![];
395
396 while !input.is_empty() {
397 let attrs = parse_outer_attrs(input, &mut errors);
398
399 if input.peek(widget_util::keyword::when) {
400 if let Some(mut when) = WgtWhen::parse(input, &mut errors) {
401 when.attrs = util::Attributes::new(attrs);
402 items.push(WgtItem::When(when));
403 }
404 } else if input.peek(Token![pub])
405 || input.peek(Ident::peek_any)
406 || input.peek(Token![crate])
407 || input.peek(Token![super])
408 || input.peek(Token![self])
409 {
410 match input.parse::<WgtProperty>() {
412 Ok(mut p) => {
413 p.attrs = util::Attributes::new(attrs);
414 if !input.is_empty() && p.semi.is_none() {
415 errors.push("expected `;`", input.span());
416 while !(input.is_empty()
417 || input.peek(Ident::peek_any)
418 || input.peek(Token![crate])
419 || input.peek(Token![super])
420 || input.peek(Token![self])
421 || input.peek(Token![#]) && input.peek(token::Bracket))
422 {
423 let _ = input.parse::<TokenTree>();
425 }
426 }
427 items.push(WgtItem::Property(p));
428 }
429 Err(e) => {
430 let (recoverable, e) = e.recoverable();
431 if recoverable {
432 errors.push_syn(e);
433 } else {
434 return Err(e);
435 }
436 }
437 }
438 } else {
439 errors.push("expected property or when", input.span());
440
441 let _ = input.parse::<TokenStream>();
443 }
444 }
445
446 Ok(Properties { errors, items })
447 }
448}
449
450fn path_slug(path: &str) -> String {
451 path.replace("crate", "")
452 .replace("::", "_")
453 .replace('$', "")
454 .trim()
455 .replace(' ', "")
456}
457
458pub fn expand_new(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
463 let NewArgs {
464 start,
465 end,
466 properties: mut p,
467 } = parse_macro_input!(args as NewArgs);
468
469 let core = util::crate_core();
470
471 let mut items = quote!();
472
473 for item in &p.items {
474 match item {
475 WgtItem::Property(prop) => {
476 items.extend(prop_assign(prop, &mut p.errors, false));
477 }
478 WgtItem::When(when) => {
479 let when_expr = match syn::parse2::<widget_util::WhenExpr>(when.condition_expr.clone()) {
480 Ok(w) => w,
481 Err(e) => {
482 p.errors.push_syn(e);
483 continue;
484 }
485 };
486
487 let mut when_expr_vars = quote!();
488 let mut inputs = quote!();
489 for ((property, member), var) in when_expr.inputs {
490 let (property, generics) = widget_util::split_path_generics(property).unwrap();
491 let p_ident = &property.segments.last().unwrap().ident;
492 let p_meta = ident_spanned!(p_ident.span()=> "{p_ident}_");
493 let var_input = ident!("{var}_in__");
494 let member_ident = ident_spanned!(property.span()=> "__w_{member}__");
495
496 let member = match member {
497 widget_util::WhenInputMember::Named(ident) => {
498 let ident_str = ident.to_string();
499 quote! {
500 Named(#ident_str)
501 }
502 }
503 widget_util::WhenInputMember::Index(i) => quote! {
504 Index(#i)
505 },
506 };
507
508 macro_rules! quote_call {
509 (#$mtd:ident ( $($args:tt)* )) => {
510 if property.get_ident().is_some() {
511 quote! {
512 wgt__.#$mtd #generics($($args)*);
513 }
514 } else {
515 quote! {
516 #property::#$mtd #generics(#core::widget::base::WidgetImpl::base(&mut *wgt__), $($args)*);
517 }
518 }
519 }
520 }
521
522 let get_meta = quote_call!(#p_meta());
523
524 when_expr_vars.extend(quote! {
525 let (#var_input, #var) = {
526 let meta__ = #get_meta
527 meta__.allowed_in_when_expr();
528 meta__.inputs #generics().#member_ident()
529 };
530 });
531
532 inputs.extend(quote! {
533 {
534 let meta__ = #get_meta
535 #core::widget::builder::WhenInput {
536 property: meta__.id(),
537 member: #core::widget::builder::WhenInputMember::#member,
538 var: #var_input,
539 property_default: meta__ .default_fn #generics(),
540 }
541 },
542 });
543 }
544
545 let mut assigns = quote!();
546 for prop in &when.assigns {
547 assigns.extend(prop_assign(prop, &mut p.errors, true));
548 }
549
550 let attrs = when.attrs.cfg_and_lints();
551 let expr = when_expr.expr;
552 let expr_str = &when.condition_expr_str;
553
554 let box_expr = quote_spanned! {expr.span()=>
555 {
556 let expr_var = #core::var::expr_var!{#expr};
557 #core::widget::builder::when_condition_expr_var(expr_var)
558 }
559 };
560
561 let source_location = widget_util::source_location(&core, Span::call_site());
562 items.extend(quote! {
563 #attrs {
564 #when_expr_vars
565 let inputs__ = std::boxed::Box::new([
566 #inputs
567 ]);
568 #core::widget::base::WidgetImpl::base(&mut *wgt__).start_when_block(
569 inputs__,
570 #box_expr,
571 #expr_str,
572 #source_location,
573 );
574
575 #assigns
576
577 #core::widget::base::WidgetImpl::base(&mut *wgt__).end_when_block();
578 }
579 });
580 }
581 }
582 }
583
584 let errors = p.errors;
585
586 let r = quote! {
587 {
588 #errors
589
590 #start
591 #items
592 #end
593 }
594 };
595
596 r.into()
597}
598
599fn prop_assign(prop: &WgtProperty, errors: &mut Errors, is_when: bool) -> TokenStream {
600 let core = util::crate_core();
601
602 let custom_expand = if prop.has_custom_attrs() {
603 prop.custom_attrs_expand(ident!("wgt__"), is_when)
604 } else {
605 quote!()
606 };
607 let attrs = prop.attrs.cfg_and_lints();
608
609 let ident = prop.ident();
610 let path = &prop.path;
611
612 let generics = &prop.generics;
613
614 macro_rules! quote_call {
615 (#$mtd:ident ( $($args:tt)* )) => {
616 if path.get_ident().is_some() {
617 quote! {
618 wgt__.#$mtd #generics($($args)*);
619 }
620 } else {
621 quote! {
622 #path::#$mtd #generics(#core::widget::base::WidgetImpl::base(&mut *wgt__), $($args)*);
623 }
624 }
625 }
626 }
627
628 let ident_meta = ident_spanned!(ident.span()=> "{}_", ident);
629
630 let when_check = if is_when {
631 let meta = quote_call!(#ident_meta());
632 quote! {
633 {
634 let meta__ = #meta
635 meta__.allowed_in_when_assign();
636 }
637 }
638 } else {
639 quote!()
640 };
641
642 let prop_init;
643
644 match &prop.value {
645 Some((_, val)) => match val {
646 widget_util::PropertyValue::Special(special, _) => {
647 if prop.is_unset() {
648 let unset_ident = ident_spanned!(ident.span()=> "unset_{}", ident);
649 prop_init = quote_call! {
650 #unset_ident()
651 };
652 } else {
653 errors.push("unknown value, expected `unset!`", special.span());
654 return quote!();
655 }
656 }
657 widget_util::PropertyValue::Unnamed(val) => {
658 prop_init = quote_call! {
659 #ident(#val)
660 };
661 }
662 widget_util::PropertyValue::Named(_, fields) => {
663 let mut idents_sorted: Vec<_> = fields.iter().map(|f| &f.ident).collect();
664 idents_sorted.sort();
665 let idents = fields.iter().map(|f| &f.ident);
666 let values = fields.iter().map(|f| &f.expr);
667 let ident_sorted = ident_spanned!(ident.span()=> "{}__", ident);
668
669 let call = quote_call! {
670 #ident_sorted(#(#idents_sorted),*)
671 };
672 let meta = if path.get_ident().is_some() {
673 quote! {
674 wgt__.#ident_meta()
675 }
676 } else {
677 quote! {
678 <#core::widget::builder::WgtInfo as #path>::#ident_meta(&#core::widget::builder::WgtInfo)
679 }
680 };
681 prop_init = quote! {
682 {
683 let meta__ = #meta;
684 #(
685 let #idents = meta__.inputs().#idents(#values);
686 )*
687 #call
688 }
689 };
690 }
691 },
692 None => {
693 let ident = &prop.path.segments.last().unwrap().ident;
694 prop_init = quote_call! {
695 #ident(#ident)
696 };
697 }
698 }
699
700 quote! {
701 #attrs {
702 #when_check
703 #custom_expand
704 #prop_init
705 }
706 }
707}
708
709struct NewArgs {
710 start: TokenStream,
711 end: TokenStream,
712 properties: Properties,
713}
714impl Parse for NewArgs {
715 fn parse(input: parse::ParseStream) -> Result<Self> {
716 Ok(Self {
717 start: non_user_braced!(input, "new").parse().unwrap(),
718 end: non_user_braced!(input, "build").parse().unwrap(),
719 properties: non_user_braced!(input, "set").parse()?,
720 })
721 }
722}