dioxus_rsx/
rsx_call.rs

1//! The actual rsx! macro implementation.
2//!
3//! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors.
4//! Currently the additional tooling doesn't do much.
5
6use proc_macro2::{Span, TokenStream as TokenStream2};
7use quote::ToTokens;
8use std::{cell::Cell, fmt::Debug};
9use syn::{
10    parse::{Parse, ParseStream},
11    Result,
12};
13
14use crate::{BodyNode, TemplateBody};
15
16/// The Callbody is the contents of the rsx! macro
17///
18/// It is a list of BodyNodes, which are the different parts of the template.
19/// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens.
20///
21/// Every callbody should be valid, so you can use it to build a template.
22/// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method.
23///
24/// Ideally we don't need the metadata here and can bake the idx-es into the templates themselves but I haven't figured out how to do that yet.
25#[derive(Debug, Clone)]
26pub struct CallBody {
27    pub body: TemplateBody,
28    pub template_idx: Cell<usize>,
29    pub span: Option<Span>,
30}
31
32impl Parse for CallBody {
33    fn parse(input: ParseStream) -> Result<Self> {
34        // Defer to the `new` method such that we can wire up hotreload information
35        let mut body = CallBody::new(input.parse()?);
36        body.span = Some(input.span());
37        Ok(body)
38    }
39}
40
41impl ToTokens for CallBody {
42    fn to_tokens(&self, out: &mut TokenStream2) {
43        self.body.to_tokens(out)
44    }
45}
46
47impl CallBody {
48    /// Create a new CallBody from a TemplateBody
49    ///
50    /// This will overwrite all internal metadata regarding hotreloading.
51    pub fn new(body: TemplateBody) -> Self {
52        let body = CallBody {
53            body,
54            template_idx: Cell::new(0),
55            span: None,
56        };
57
58        body.body.template_idx.set(body.next_template_idx());
59
60        body.cascade_hotreload_info(&body.body.roots);
61
62        body
63    }
64
65    /// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro
66    ///
67    /// This should be preferred over `parse` if you are outside of a macro
68    pub fn parse_strict(input: ParseStream) -> Result<Self> {
69        // todo: actually throw warnings if there are any
70        Self::parse(input)
71    }
72
73    /// With the entire knowledge of the macro call, wire up location information for anything hotreloading
74    /// specific. It's a little bit simpler just to have a global id per callbody than to try and track it
75    /// relative to each template, though we could do that if we wanted to.
76    ///
77    /// For now this is just information for ifmts and templates so that when they generate, they can be
78    /// tracked back to the original location in the source code, to support formatted string hotreloading.
79    ///
80    /// Note that there are some more complex cases we could in theory support, but have bigger plans
81    /// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of
82    /// the simple cases until that proper stuff ships.
83    ///
84    /// We need to make sure to wire up:
85    /// - subtemplate IDs
86    /// - ifmt IDs
87    /// - dynamic node IDs
88    /// - dynamic attribute IDs
89    /// - paths for dynamic nodes and attributes
90    ///
91    /// Lots of wiring!
92    ///
93    /// However, here, we only need to wire up template IDs since TemplateBody will handle the rest.
94    ///
95    /// This is better though since we can save the relevant data on the structures themselves.
96    fn cascade_hotreload_info(&self, nodes: &[BodyNode]) {
97        for node in nodes.iter() {
98            match node {
99                BodyNode::Element(el) => {
100                    self.cascade_hotreload_info(&el.children);
101                }
102
103                BodyNode::Component(comp) => {
104                    comp.children.template_idx.set(self.next_template_idx());
105                    self.cascade_hotreload_info(&comp.children.roots);
106                }
107
108                BodyNode::ForLoop(floop) => {
109                    floop.body.template_idx.set(self.next_template_idx());
110                    self.cascade_hotreload_info(&floop.body.roots);
111                }
112
113                BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| {
114                    body.template_idx.set(self.next_template_idx());
115                    self.cascade_hotreload_info(&body.roots)
116                }),
117
118                _ => {}
119            }
120        }
121    }
122
123    fn next_template_idx(&self) -> usize {
124        let idx = self.template_idx.get();
125        self.template_idx.set(idx + 1);
126        idx
127    }
128}