1use proc_macro2::TokenStream;
7use quote::quote;
8use syn::{Expr, Pat};
9
10use crate::ir::{DynNode, Node, Prop, PropType, Root, TagIdent, TagNode, TextNode};
11
12pub struct Codegen {
13 }
15
16impl Codegen {
17 pub fn root(&self, root: &Root) -> TokenStream {
18 match &root.0[..] {
19 [] => quote! {
20 ::sycamore::rt::View::new()
21 },
22 [node] => self.node(node),
23 nodes => {
24 let nodes = nodes.iter().map(|node| self.node(node));
25 quote! {
26 ::std::convert::Into::<::sycamore::rt::View>::into(::std::vec![#(#nodes),*])
27 }
28 }
29 }
30 }
31
32 pub fn node(&self, node: &Node) -> TokenStream {
34 match node {
35 Node::Tag(tag) => {
36 if is_component(&tag.ident) {
37 self.component(tag)
38 } else {
39 self.element(tag)
40 }
41 }
42 Node::Text(TextNode { value }) => quote! {
43 ::std::convert::Into::<::sycamore::rt::View>::into(#value)
44 },
45 Node::Dyn(DynNode { value }) => {
46 let is_dynamic = is_dyn(value);
47 if is_dynamic {
48 quote! {
49 ::sycamore::rt::View::from_dynamic(
50 move || ::std::convert::Into::<::sycamore::rt::View>::into(#value)
51 )
52 }
53 } else {
54 quote! {
55 ::std::convert::Into::<::sycamore::rt::View>::into(#value)
56 }
57 }
58 }
59 }
60 }
61
62 pub fn element(&self, element: &TagNode) -> TokenStream {
63 let TagNode {
64 ident,
65 props,
66 children,
67 } = element;
68
69 let attributes = props.iter().map(|attr| self.attribute(attr));
70
71 let children = children
72 .0
73 .iter()
74 .map(|child| self.node(child))
75 .collect::<Vec<_>>();
76
77 match ident {
78 TagIdent::Path(tag) => {
79 assert!(tag.get_ident().is_some(), "elements must be an ident");
80 quote! {
81 ::sycamore::rt::View::from(
82 ::sycamore::rt::tags::#tag().children(::std::vec![#(#children),*])#(#attributes)*
83 )
84 }
85 }
86 TagIdent::Hyphenated(tag) => quote! {
87 ::sycamore::rt::View::from(
88 ::sycamore::rt::custom_element(#tag).children(::std::vec![#(#children),*])#(#attributes)*
89 )
90 },
91 }
92 }
93
94 pub fn attribute(&self, attr: &Prop) -> TokenStream {
95 let value = &attr.value;
96 let is_dynamic = is_dyn(value);
97 let dyn_value = if is_dynamic {
98 quote! { move || #value }
99 } else {
100 quote! { #value }
101 };
102 match &attr.ty {
103 PropType::Plain { ident } => {
104 quote! { .#ident(#dyn_value) }
105 }
106 PropType::PlainHyphenated { ident } => {
107 quote! { .attr(#ident, #dyn_value) }
108 }
109 PropType::PlainQuoted { ident } => {
110 quote! { .attr(#ident, #dyn_value) }
111 }
112 PropType::Directive { dir, ident } => match dir.to_string().as_str() {
113 "on" => quote! { .on(::sycamore::rt::events::#ident, #value) },
114 "prop" => {
115 let ident = ident.to_string();
116 quote! { .prop(#ident, #dyn_value) }
117 }
118 "bind" => quote! { .bind(::sycamore::rt::bind::#ident, #value) },
119 _ => syn::Error::new(dir.span(), format!("unknown directive `{dir}`"))
120 .to_compile_error(),
121 },
122 PropType::Ref => quote! { .r#ref(#value) },
123 PropType::Spread => quote! { .spread(#value) },
124 }
125 }
126
127 pub fn component(
128 &self,
129 TagNode {
130 ident,
131 props,
132 children,
133 }: &TagNode,
134 ) -> TokenStream {
135 let ident = match ident {
136 TagIdent::Path(path) => path,
137 TagIdent::Hyphenated(_) => unreachable!("hyphenated tags are not components"),
138 };
139
140 let plain = props
141 .iter()
142 .filter_map(|prop| match &prop.ty {
143 PropType::Plain { ident } => Some((ident, prop.value.clone())),
144 _ => None,
145 })
146 .collect::<Vec<_>>();
147 let plain_names = plain.iter().map(|(ident, _)| ident);
148 let plain_values = plain.iter().map(|(_, value)| value);
149
150 let other_props = props
151 .iter()
152 .filter(|prop| !matches!(&prop.ty, PropType::Plain { .. }))
153 .collect::<Vec<_>>();
154 let other_attributes = other_props.iter().map(|prop| self.attribute(prop));
155
156 let children_quoted = if children.0.is_empty() {
157 quote! {}
158 } else {
159 let codegen = Codegen {};
160 let children = codegen.root(children);
161 quote! {
162 .children(
163 ::sycamore::rt::Children::new(move || {
164 #children
165 })
166 )
167 }
168 };
169 quote! {{
170 let __component = &#ident; ::sycamore::rt::component_scope(move || ::sycamore::rt::Component::create(
172 __component,
173 ::sycamore::rt::element_like_component_builder(__component)
174 #(.#plain_names(#plain_values))*
175 #(#other_attributes)*
176 #children_quoted
177 .build()
178 ))
179 }}
180 }
181}
182
183fn is_component(ident: &TagIdent) -> bool {
184 match ident {
185 TagIdent::Path(path) => {
186 path.get_ident().is_none()
187 || path
188 .get_ident()
189 .unwrap()
190 .to_string()
191 .chars()
192 .next()
193 .unwrap()
194 .is_ascii_uppercase()
195 }
196 TagIdent::Hyphenated(_) => false,
198 }
199}
200
201fn is_dyn(ex: &Expr) -> bool {
202 match ex {
203 Expr::Lit(_) | Expr::Closure(_) | Expr::Path(_) => false,
204
205 Expr::Field(f) => is_dyn(&f.base),
206 Expr::Paren(p) => is_dyn(&p.expr),
207 Expr::Group(g) => is_dyn(&g.expr),
208 Expr::Tuple(t) => t.elems.iter().any(is_dyn),
209 Expr::Array(a) => a.elems.iter().any(is_dyn),
210 Expr::Repeat(r) => is_dyn(&r.expr) || is_dyn(&r.len),
211 Expr::Struct(s) => s.fields.iter().any(|fv: &syn::FieldValue| is_dyn(&fv.expr)),
212
213 Expr::Cast(c) => is_dyn(&c.expr),
214 Expr::Macro(m) => is_dyn_macro(&m.mac),
215 Expr::Block(b) => is_dyn_block(&b.block),
216 Expr::Const(_const_block) => false,
217
218 Expr::Loop(l) => is_dyn_block(&l.body),
219 Expr::While(w) => is_dyn(&w.cond) || is_dyn_block(&w.body),
220 Expr::ForLoop(f) => is_dyn_pattern(&f.pat) || is_dyn(&f.expr) || is_dyn_block(&f.body),
221 Expr::Break(_) | Expr::Continue(_) => false,
222
223 Expr::Let(e) => is_dyn_pattern(&e.pat) || is_dyn(&e.expr),
224
225 Expr::Match(m) => {
226 is_dyn(&m.expr)
227 || m.arms.iter().any(|a: &syn::Arm| {
228 is_dyn_pattern(&a.pat)
229 || a.guard.as_ref().is_some_and(|(_, g_expr)| is_dyn(g_expr))
230 || is_dyn(&a.body)
231 })
232 }
233
234 Expr::If(i) => {
235 is_dyn(&i.cond)
236 || is_dyn_block(&i.then_branch)
237 || i.else_branch.as_ref().is_some_and(|(_, e)| is_dyn(e))
238 }
239
240 Expr::Unary(u) => is_dyn(&u.expr),
241 Expr::Binary(b) => is_dyn(&b.left) || is_dyn(&b.right),
242 Expr::Index(i) => is_dyn(&i.expr) || is_dyn(&i.index),
243 Expr::Range(r) => {
244 r.start.as_deref().is_some_and(is_dyn) || r.end.as_deref().is_some_and(is_dyn)
245 }
246
247 _ => true,
248 }
249}
250
251fn is_dyn_pattern(pat: &Pat) -> bool {
252 match pat {
253 Pat::Wild(_) | Pat::Lit(_) | Pat::Path(_) | Pat::Rest(_) | Pat::Type(_) | Pat::Const(_) => {
254 false
255 }
256
257 Pat::Paren(p) => is_dyn_pattern(&p.pat),
258 Pat::Or(o) => o.cases.iter().any(is_dyn_pattern),
259 Pat::Tuple(t) => t.elems.iter().any(is_dyn_pattern),
260 Pat::TupleStruct(s) => s.elems.iter().any(is_dyn_pattern),
261 Pat::Slice(s) => s.elems.iter().any(is_dyn_pattern),
262 Pat::Range(r) => {
263 r.start.as_deref().is_some_and(is_dyn) || r.end.as_deref().is_some_and(is_dyn)
264 }
265
266 Pat::Reference(r) => r.mutability.is_some(),
267 Pat::Ident(id) => {
268 (id.by_ref.is_some() && id.mutability.is_some())
269 || id
270 .subpat
271 .as_ref()
272 .is_some_and(|(_, pat)| is_dyn_pattern(pat))
273 }
274
275 Pat::Struct(s) => s
276 .fields
277 .iter()
278 .any(|fp: &syn::FieldPat| is_dyn_pattern(&fp.pat)),
279
280 _ => true,
282 }
283}
284
285fn is_dyn_macro(m: &syn::Macro) -> bool {
286 #[allow(clippy::nonminimal_bool)]
291 !m.path.get_ident().is_some_and(|ident| ident == "view")
292}
293
294fn is_dyn_block(block: &syn::Block) -> bool {
295 block.stmts.iter().any(|s: &syn::Stmt| match s {
296 syn::Stmt::Expr(ex, _) => is_dyn(ex),
297 syn::Stmt::Macro(m) => is_dyn_macro(&m.mac),
298 syn::Stmt::Local(loc) => {
299 is_dyn_pattern(&loc.pat)
300 || loc.init.as_ref().is_some_and(|i| {
301 is_dyn(&i.expr) || i.diverge.as_ref().is_some_and(|(_, ex)| is_dyn(ex))
302 })
303 }
304 syn::Stmt::Item(_) => false,
305 })
306}