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}