1use std::hash::{Hash, Hasher};
2
3use base64::Engine;
4use proc_macro2::{Ident, Span, TokenStream, TokenTree};
5use quote::{ToTokens, quote};
6use rustc_hash::FxHasher;
7use syn::{
8 Expr, LitStr, Local, Pat, Stmt, Token, braced,
9 parse::{Parse, ParseStream},
10 spanned::Spanned as _,
11 token::Brace,
12};
13
14use super::{AnyBlock, Generate, Generator, Node, Nodes};
15use crate::{
16 Attribute, AttributeKind, AttributeName, AttributeValueNode, Context, ElementNode,
17 SyntaxStatic, basics::Literal,
18};
19
20fn tokens_contain_ident(tokens: &TokenStream, needle: &str) -> bool {
21 tokens.clone().into_iter().any(|token| match token {
22 TokenTree::Ident(ident) => ident == needle,
23 TokenTree::Group(group) => tokens_contain_ident(&group.stream(), needle),
24 TokenTree::Punct(_) | TokenTree::Literal(_) => false,
25 })
26}
27
28fn tokens_contain_any_ident(tokens: &TokenStream, needles: &[Ident]) -> bool {
29 tokens.clone().into_iter().any(|token| match token {
30 TokenTree::Ident(ident) => needles.iter().any(|needle| needle == &ident),
31 TokenTree::Group(group) => tokens_contain_any_ident(&group.stream(), needles),
32 TokenTree::Punct(_) | TokenTree::Literal(_) => false,
33 })
34}
35
36fn collect_pat_bindings(pat: &Pat, bindings: &mut Vec<(TokenStream, Ident)>) {
37 match pat {
38 Pat::Ident(pat) => {
39 let mut binding = TokenStream::new();
40 pat.mutability.to_tokens(&mut binding);
41 pat.ident.to_tokens(&mut binding);
42
43 if let Some(existing) = bindings.iter_mut().find(|(_, ident)| ident == &pat.ident) {
44 *existing = (binding, pat.ident.clone());
45 } else {
46 bindings.push((binding, pat.ident.clone()));
47 }
48 }
49 Pat::Or(pat) => {
50 for case in &pat.cases {
51 collect_pat_bindings(case, bindings);
52 }
53 }
54 Pat::Paren(pat) => collect_pat_bindings(&pat.pat, bindings),
55 Pat::Reference(pat) => collect_pat_bindings(&pat.pat, bindings),
56 Pat::Slice(pat) => {
57 for elem in &pat.elems {
58 collect_pat_bindings(elem, bindings);
59 }
60 }
61 Pat::Struct(pat) => {
62 for field in &pat.fields {
63 collect_pat_bindings(&field.pat, bindings);
64 }
65 }
66 Pat::Tuple(pat) => {
67 for elem in &pat.elems {
68 collect_pat_bindings(elem, bindings);
69 }
70 }
71 Pat::TupleStruct(pat) => {
72 for elem in &pat.elems {
73 collect_pat_bindings(elem, bindings);
74 }
75 }
76 Pat::Type(pat) => collect_pat_bindings(&pat.pat, bindings),
77 Pat::Const(_)
78 | Pat::Lit(_)
79 | Pat::Macro(_)
80 | Pat::Path(_)
81 | Pat::Range(_)
82 | Pat::Rest(_)
83 | Pat::Verbatim(_)
84 | Pat::Wild(_) => {}
85 _ => {}
86 }
87}
88
89fn leading_let_bindings(
90 nodes: &[ElementNode],
91 leading_let_count: usize,
92) -> Vec<(TokenStream, Ident)> {
93 let mut bindings = Vec::new();
94
95 for node in nodes.iter().take(leading_let_count) {
96 if let ElementNode::Control(Control {
97 kind: ControlKind::Let(Let(local)),
98 ..
99 }) = node
100 {
101 collect_pat_bindings(&local.pat, &mut bindings);
102 }
103 }
104
105 bindings
106}
107
108fn leading_lets_can_be_moved_into_hot_args(
109 nodes: &[ElementNode],
110 leading_let_count: usize,
111) -> bool {
112 let mut prior_bindings = Vec::new();
113
114 for node in nodes.iter().take(leading_let_count) {
115 let ElementNode::Control(Control {
116 kind: ControlKind::Let(Let(local)),
117 ..
118 }) = node
119 else {
120 continue;
121 };
122
123 let Some(init) = &local.init else {
124 return false;
125 };
126
127 if tokens_contain_any_ident(&init.expr.to_token_stream(), &prior_bindings) {
128 return false;
129 }
130
131 let mut bindings = Vec::new();
132 collect_pat_bindings(&local.pat, &mut bindings);
133 prior_bindings.extend(bindings.into_iter().map(|(_, ident)| ident));
134 }
135
136 true
137}
138
139fn source_offset(source: &str, line: usize, column: usize) -> Option<usize> {
140 let mut offset = 0;
141 for (index, segment) in source.split_inclusive('\n').enumerate() {
142 if index + 1 == line {
143 let mut column_offset = column.min(segment.len());
144 while column_offset > 0 && !segment.is_char_boundary(column_offset) {
145 column_offset -= 1;
146 }
147 return Some(offset + column_offset);
148 }
149 offset += segment.len();
150 }
151
152 None
153}
154
155fn skip_rust_block_comment(source: &str, mut offset: usize) -> Option<usize> {
156 let bytes = source.as_bytes();
157 let mut depth = 0usize;
158
159 while offset + 1 < bytes.len() {
160 if bytes[offset] == b'/' && bytes[offset + 1] == b'*' {
161 depth += 1;
162 offset += 2;
163 } else if bytes[offset] == b'*' && bytes[offset + 1] == b'/' {
164 depth = depth.checked_sub(1)?;
165 offset += 2;
166 if depth == 0 {
167 return Some(offset);
168 }
169 } else {
170 offset += 1;
171 }
172 }
173
174 None
175}
176
177fn skip_rust_trivia(source: &str, mut offset: usize) -> usize {
178 loop {
179 let rest = &source[offset..];
180 if let Some(ch) = rest.chars().next()
181 && ch.is_whitespace()
182 {
183 offset += ch.len_utf8();
184 continue;
185 }
186
187 if rest.starts_with("//") {
188 offset += rest.find('\n').map_or(rest.len(), |newline| newline + 1);
189 continue;
190 }
191
192 if rest.starts_with("/*") {
193 offset = skip_rust_block_comment(source, offset).unwrap_or(source.len());
194 continue;
195 }
196
197 return offset;
198 }
199}
200
201fn is_ident_continue(ch: char) -> bool {
202 ch == '_' || ch.is_alphanumeric()
203}
204
205fn starts_with_async_keyword(source: &str, offset: usize) -> bool {
206 let Some(rest) = source.get(offset..) else {
207 return false;
208 };
209 let Some(after_async) = rest.strip_prefix("async") else {
210 return false;
211 };
212
213 after_async
214 .chars()
215 .next()
216 .is_none_or(|ch| !is_ident_continue(ch))
217}
218
219fn async_marker_count(source: &str) -> usize {
220 let mut count = 0;
221 let mut offset = 0;
222
223 while let Some(at_offset) = source[offset..].find('@').map(|rel| offset + rel) {
224 let after_at = skip_rust_trivia(source, at_offset + '@'.len_utf8());
225 if starts_with_async_keyword(source, after_at) {
226 count += 1;
227 }
228 offset = at_offset + '@'.len_utf8();
229 }
230
231 count
232}
233
234fn async_source_ordinal(file: &str, line: usize, column: usize) -> Option<usize> {
235 let source = std::fs::read_to_string(file).ok()?;
236 let offset = source_offset(&source, line, column)?;
237 Some(async_marker_count(&source[..offset]))
238}
239
240fn element_body_contains_async(body: &crate::ElementBody) -> bool {
241 match body {
242 crate::ElementBody::Normal { children, .. } => element_nodes_contain_async(&children.0),
243 crate::ElementBody::Void { .. } => false,
244 }
245}
246
247fn element_nodes_contain_async(nodes: &[ElementNode]) -> bool {
248 nodes.iter().any(element_node_contains_async)
249}
250
251fn element_nodes_are_static(nodes: &[ElementNode]) -> bool {
252 nodes.iter().all(SyntaxStatic::is_static)
253}
254
255fn element_node_contains_async(node: &ElementNode) -> bool {
256 match node {
257 ElementNode::Element(element) => element_body_contains_async(&element.body),
258 ElementNode::Component(component) => element_body_contains_async(&component.body),
259 ElementNode::Control(Control { kind, .. }) => element_control_contains_async(kind),
260 ElementNode::Group(group) => element_nodes_contain_async(&group.nodes.0),
261 ElementNode::Literal(_) | ElementNode::Expr(_) => false,
262 }
263}
264
265fn element_control_contains_async(kind: &ControlKind<ElementNode>) -> bool {
266 match kind {
267 ControlKind::Let(_) => false,
268 ControlKind::If(if_) => {
269 control_block_contains_async(&if_.then_block)
270 || if_
271 .else_branch
272 .as_ref()
273 .is_some_and(|(_, _, branch)| control_if_or_block_contains_async(branch))
274 }
275 ControlKind::For(for_) => control_block_contains_async(&for_.block),
276 ControlKind::While(while_) => control_block_contains_async(&while_.block),
277 ControlKind::Match(match_) => match_.arms.iter().any(|arm| match &arm.body {
278 MatchNodeArmBody::Block(block) => control_block_contains_async(block),
279 MatchNodeArmBody::Node(node) => element_node_contains_async(node),
280 }),
281 ControlKind::Async(_) => true,
282 }
283}
284
285fn control_block_contains_async(block: &ControlBlock<ElementNode>) -> bool {
286 element_nodes_contain_async(&block.nodes.0)
287}
288
289fn control_if_or_block_contains_async(branch: &ControlIfOrBlock<ElementNode>) -> bool {
290 match branch {
291 ControlIfOrBlock::If(if_) => {
292 control_block_contains_async(&if_.then_block)
293 || if_
294 .else_branch
295 .as_ref()
296 .is_some_and(|(_, _, branch)| control_if_or_block_contains_async(branch))
297 }
298 ControlIfOrBlock::Block(block) => control_block_contains_async(block),
299 }
300}
301
302#[allow(clippy::large_enum_variant)]
303pub enum ControlKind<N: Node> {
304 Let(Let),
305 If(If<N>),
306 For(For<N>),
307 While(While<N>),
308 Match(Match<N>),
309 Async(Async),
310}
311
312pub struct Control<N: Node> {
313 pub at_token: Token![@],
314 pub kind: ControlKind<N>,
315}
316
317impl<N: Node> SyntaxStatic for Control<N> {
318 fn is_static(&self) -> bool {
319 false
320 }
321}
322
323impl<N: Node + Parse> Parse for Control<N> {
324 fn parse(input: ParseStream) -> syn::Result<Self> {
325 let at_token = input.parse::<Token![@]>()?;
326
327 let lookahead = input.lookahead1();
328
329 let kind = if lookahead.peek(Token![let]) {
330 input.parse().map(ControlKind::Let)
331 } else if lookahead.peek(Token![if]) {
332 input.parse().map(ControlKind::If)
333 } else if lookahead.peek(Token![for]) {
334 input.parse().map(ControlKind::For)
335 } else if lookahead.peek(Token![while]) {
336 input.parse().map(ControlKind::While)
337 } else if lookahead.peek(Token![match]) {
338 input.parse().map(ControlKind::Match)
339 } else if lookahead.peek(Token![async]) {
340 input.parse().map(ControlKind::Async)
341 } else {
342 Err(lookahead.error())
343 }?;
344
345 Ok(Self { at_token, kind })
346 }
347}
348
349impl<N: Node> Generate for Control<N> {
350 const CONTEXT: Context = N::CONTEXT;
351
352 fn generate(&mut self, g: &mut Generator<'_>) {
353 match &mut self.kind {
354 ControlKind::Let(let_) => g.push(let_),
355 ControlKind::If(if_) => g.push(if_),
356 ControlKind::For(for_) => g.push(for_),
357 ControlKind::While(while_) => g.push(while_),
358 ControlKind::Match(match_) => g.push(match_),
359 ControlKind::Async(suspense) => g.push(suspense),
360 }
361 }
362}
363
364pub struct Let(pub Local);
365
366impl Parse for Let {
367 fn parse(input: ParseStream) -> syn::Result<Self> {
368 let local = match input.parse()? {
369 Stmt::Local(local) => local,
370 stmt => return Err(syn::Error::new_spanned(stmt, "expected `let` statement")),
371 };
372
373 Ok(Self(local))
374 }
375}
376
377impl Generate for Let {
378 const CONTEXT: Context = Context::Element;
379
380 fn generate(&mut self, g: &mut Generator<'_>) {
381 g.push_stmt(&self.0);
382 }
383}
384
385pub struct ControlBlock<N: Node> {
386 pub brace_token: Brace,
387 pub nodes: Nodes<N>,
388}
389
390impl<N: Node> ControlBlock<N> {
391 fn block(&mut self, g: &mut Generator<'_>) -> AnyBlock {
392 self.nodes.block(g, self.brace_token)
393 }
394}
395
396impl<N: Node + Parse> Parse for ControlBlock<N> {
397 fn parse(input: ParseStream) -> syn::Result<Self> {
398 let content;
399
400 Ok(Self {
401 brace_token: braced!(content in input),
402 nodes: content.parse()?,
403 })
404 }
405}
406
407pub struct If<N: Node> {
408 if_token: Token![if],
409 pub cond: Expr,
410 pub then_block: ControlBlock<N>,
411 pub else_branch: Option<(Token![@], Token![else], Box<ControlIfOrBlock<N>>)>,
412}
413
414impl<N: Node> If<N> {
415 pub fn if_token(&self) -> &Token![if] {
416 &self.if_token
417 }
418}
419
420impl<N: Node + Parse> Parse for If<N> {
421 fn parse(input: ParseStream) -> syn::Result<Self> {
422 Ok(Self {
423 if_token: input.parse()?,
424 cond: input.call(Expr::parse_without_eager_brace)?,
425 then_block: input.parse()?,
426 else_branch: if input.peek(Token![@]) && input.peek2(Token![else]) {
427 Some((input.parse()?, input.parse()?, input.parse()?))
428 } else {
429 None
430 },
431 })
432 }
433}
434
435impl<N: Node> Generate for If<N> {
436 const CONTEXT: Context = N::CONTEXT;
437
438 fn generate(&mut self, g: &mut Generator<'_>) {
439 fn to_expr<N: Node>(if_: &mut If<N>, g: &mut Generator<'_>) -> TokenStream {
440 let if_token = if_.if_token;
441 let cond = &if_.cond;
442 let then_block = if_.then_block.block(g);
443 let else_branch = if_
444 .else_branch
445 .as_mut()
446 .map(|(_, else_token, if_or_block)| {
447 let else_block = match &mut **if_or_block {
448 ControlIfOrBlock::If(if_) => to_expr(if_, g),
449 ControlIfOrBlock::Block(block) => block.block(g).to_token_stream(),
450 };
451
452 quote! {
453 #else_token #else_block
454 }
455 });
456
457 quote! {
458 #if_token #cond
459 #then_block
460 #else_branch
461 }
462 }
463
464 let expr = to_expr(self, g);
465
466 g.push_stmt(expr);
467 }
468}
469
470#[allow(clippy::large_enum_variant)]
471pub enum ControlIfOrBlock<N: Node> {
472 If(If<N>),
473 Block(ControlBlock<N>),
474}
475
476impl<N: Node + Parse> Parse for ControlIfOrBlock<N> {
477 fn parse(input: ParseStream) -> syn::Result<Self> {
478 let lookahead = input.lookahead1();
479
480 if lookahead.peek(Token![if]) {
481 input.parse().map(Self::If)
482 } else if lookahead.peek(Brace) {
483 input.parse().map(Self::Block)
484 } else {
485 Err(lookahead.error())
486 }
487 }
488}
489
490pub struct For<N: Node> {
491 for_token: Token![for],
492 pub pat: Pat,
493 in_token: Token![in],
494 pub expr: Expr,
495 pub block: ControlBlock<N>,
496}
497
498impl<N: Node + Parse> Parse for For<N> {
499 fn parse(input: ParseStream) -> syn::Result<Self> {
500 Ok(Self {
501 for_token: input.parse()?,
502 pat: input.call(Pat::parse_multi_with_leading_vert)?,
503 in_token: input.parse()?,
504 expr: input.call(Expr::parse_without_eager_brace)?,
505 block: input.parse()?,
506 })
507 }
508}
509
510impl<N: Node> Generate for For<N> {
511 const CONTEXT: Context = N::CONTEXT;
512
513 fn generate(&mut self, g: &mut Generator<'_>) {
514 let for_token = self.for_token;
515 let pat = &self.pat;
516 let in_token = self.in_token;
517 let expr = &self.expr;
518 let block = self.block.block(g);
519
520 g.push_stmt(quote! {
521 #for_token #pat #in_token #expr
522 #block
523 });
524 }
525}
526
527pub struct While<N: Node> {
528 while_token: Token![while],
529 pub cond: Expr,
530 pub block: ControlBlock<N>,
531}
532
533impl<N: Node + Parse> Parse for While<N> {
534 fn parse(input: ParseStream) -> syn::Result<Self> {
535 Ok(Self {
536 while_token: input.parse()?,
537 cond: input.call(Expr::parse_without_eager_brace)?,
538 block: input.parse()?,
539 })
540 }
541}
542
543impl<N: Node> Generate for While<N> {
544 const CONTEXT: Context = N::CONTEXT;
545
546 fn generate(&mut self, g: &mut Generator<'_>) {
547 let while_token = self.while_token;
548 let cond = &self.cond;
549 let block = self.block.block(g);
550
551 g.push_stmt(quote! {
552 #while_token #cond
553 #block
554 });
555 }
556}
557
558pub struct Match<N: Node> {
559 match_token: Token![match],
560 pub expr: Expr,
561 pub brace_token: Brace,
562 pub arms: Vec<MatchNodeArm<N>>,
563}
564
565impl<N: Node + Parse> Parse for Match<N> {
566 fn parse(input: ParseStream) -> syn::Result<Self> {
567 let content;
568
569 Ok(Self {
570 match_token: input.parse()?,
571 expr: input.call(Expr::parse_without_eager_brace)?,
572 brace_token: braced!(content in input),
573 arms: {
574 let mut arms = Vec::new();
575
576 while !content.is_empty() {
577 arms.push(content.parse()?);
578 }
579
580 arms
581 },
582 })
583 }
584}
585
586impl<N: Node> Generate for Match<N> {
587 const CONTEXT: Context = N::CONTEXT;
588
589 fn generate(&mut self, g: &mut Generator<'_>) {
590 let arms = self
591 .arms
592 .iter_mut()
593 .map(|arm| {
594 let pat = arm.pat.clone();
595 let guard = arm
596 .guard
597 .as_ref()
598 .map(|(if_token, guard)| quote!(#if_token #guard));
599 let fat_arrow_token = arm.fat_arrow_token;
600 let block = match &mut arm.body {
601 MatchNodeArmBody::Block(block) => block.block(g),
602 MatchNodeArmBody::Node(node) => {
603 g.block_with(Brace::default(), |g| g.push(node), true)
604 }
605 };
606 let comma = arm.comma_token;
607
608 quote!(#pat #guard #fat_arrow_token #block #comma)
609 })
610 .collect::<TokenStream>();
611
612 let match_token = self.match_token;
613 let expr = &self.expr;
614
615 let mut stmt = quote!(#match_token #expr);
616
617 self.brace_token
618 .surround(&mut stmt, |tokens| tokens.extend(arms));
619
620 g.push_stmt(stmt);
621 }
622}
623
624pub struct MatchNodeArm<N: Node> {
625 pub pat: Pat,
626 pub guard: Option<(Token![if], Expr)>,
627 fat_arrow_token: Token![=>],
628 pub body: MatchNodeArmBody<N>,
629 comma_token: Option<Token![,]>,
630}
631
632impl<N: Node> MatchNodeArm<N> {
633 pub fn fat_arrow_span(&self) -> Span {
634 self.fat_arrow_token.span()
635 }
636}
637
638impl<N: Node + Parse> Parse for MatchNodeArm<N> {
639 fn parse(input: ParseStream) -> syn::Result<Self> {
640 Ok(Self {
641 pat: input.call(Pat::parse_multi_with_leading_vert)?,
642 guard: if input.peek(Token![if]) {
643 Some((input.parse()?, input.parse()?))
644 } else {
645 None
646 },
647 fat_arrow_token: input.parse()?,
648 body: input.parse()?,
649 comma_token: input.parse()?,
650 })
651 }
652}
653
654pub enum MatchNodeArmBody<N: Node> {
655 Block(ControlBlock<N>),
656 Node(N),
657}
658
659impl<N: Node + Parse> Parse for MatchNodeArmBody<N> {
660 fn parse(input: ParseStream) -> syn::Result<Self> {
661 if input.peek(Brace) {
662 input.parse().map(Self::Block)
663 } else {
664 input.parse().map(Self::Node)
665 }
666 }
667}
668
669pub struct Async {
670 pub async_token: Token![async],
671 pub async_block: ControlBlock<ElementNode>,
672 pub else_at_token: Token![@],
673 pub else_token: Token![else],
674 pub else_block: ControlBlock<ElementNode>,
675 else_block_first_elem_idx: usize,
676}
677
678impl Parse for Async {
679 fn parse(input: ParseStream) -> syn::Result<Self> {
680 let async_token = input.parse::<Token![async]>()?;
681 let async_block = input.parse()?;
682 let else_at_token = input.parse::<Token![@]>()?;
683 let else_token = input.parse::<Token![else]>()?;
684 let mut else_block: ControlBlock<ElementNode> = input.parse()?;
685 let else_block_first_elem_idx = else_block
686 .nodes
687 .0
688 .iter_mut()
689 .position(|n| matches!(n, ElementNode::Element(_)))
690 .ok_or_else(|| {
691 syn::Error::new_spanned(
692 else_token,
693 "expected at least a single element in the `else` block of `async`",
694 )
695 })?;
696
697 Ok(Self {
698 async_token,
699 async_block,
700 else_at_token,
701 else_token,
702 else_block,
703 else_block_first_elem_idx,
704 })
705 }
706}
707
708impl Async {
709 fn stream_tokens_expr(
710 async_token: Token![async],
711 content_code: &TokenStream,
712 key: &str,
713 ) -> TokenStream {
714 let marker_ident = ElementNode::CONTEXT.marker_type();
715 let buffer_ident = Generator::buffer_ident();
716 let template_start = format!(r#"<template data-ssr="{key}-t">"#);
717 let stream_script =
718 format!(r#"</template><script data-ssr="{key}-s">__ssrStream('{key}')</script>"#);
719
720 quote! {
721 ::cheers::__internal::futures::stream::once(#async_token move {
722 let mut buffer = ::cheers::prelude::Buffer::<#marker_ident>::new();
723 buffer.dangerously_get_string().push_str(#template_start);
725 let #buffer_ident = &mut buffer;
726 #content_code
727 buffer.dangerously_get_string().push_str(#stream_script);
729
730 ::cheers::Raw::<_, #marker_ident>::dangerously_create(
731 buffer.rendered().into_inner()
732 ).render()
733 })
734 }
735 }
736
737 fn stream_with_hot_render_call_tokens_expr(
738 async_token: Token![async],
739 load_code: &TokenStream,
740 render_code: &TokenStream,
741 leading_bindings: &[(TokenStream, Ident)],
742 key: &str,
743 ) -> TokenStream {
744 let marker_ident = ElementNode::CONTEXT.marker_type();
745 let buffer_ident = Generator::buffer_ident();
746 let template_start = format!(r#"<template data-ssr="{key}-t">"#);
747 let stream_script =
748 format!(r#"</template><script data-ssr="{key}-s">__ssrStream('{key}')</script>"#);
749 let binding_params = leading_bindings.iter().map(|(param, _)| param);
750 let binding_args = leading_bindings.iter().map(|(_, arg)| arg);
751
752 quote! {
753 ::cheers::__internal::futures::stream::once(#async_token move {
754 #load_code
755
756 let mut buffer = ::cheers::prelude::Buffer::<#marker_ident>::new();
757 buffer.dangerously_get_string().push_str(#template_start);
759 let #buffer_ident = &mut buffer;
760 ::cheers::__internal::subsecond::hot_call_with_arg(
761 |(#buffer_ident, #(#binding_params),*)| {
762 use ::cheers::validation::attributes::*;
763 #render_code
764 },
765 (#buffer_ident, #(#binding_args),*),
766 );
767 buffer.dangerously_get_string().push_str(#stream_script);
769
770 ::cheers::Raw::<_, #marker_ident>::dangerously_create(
771 buffer.rendered().into_inner()
772 ).render()
773 })
774 }
775 }
776
777 fn stream_with_nested_tokens_expr(
778 async_token: Token![async],
779 content_code: &TokenStream,
780 key: &str,
781 ) -> TokenStream {
782 let marker_ident = ElementNode::CONTEXT.marker_type();
783 let buffer_ident = Generator::buffer_ident();
784 let template_start = format!(r#"<template data-ssr="{key}-t">"#);
785 let stream_script =
786 format!(r#"</template><script data-ssr="{key}-s">__ssrStream('{key}')</script>"#);
787
788 quote! {
789 ::cheers::__internal::futures::StreamExt::flat_map(
790 ::cheers::__internal::futures::stream::once(#async_token move {
791 let mut buffer = ::std::boxed::Box::new(
792 ::cheers::prelude::Buffer::<#marker_ident>::new()
793 );
794 let __cheers_async_stream_collection =
795 ::cheers::__internal::async_streams::enter(&mut *buffer);
796 buffer.dangerously_get_string().push_str(#template_start);
798 let #buffer_ident = &mut *buffer;
799 #content_code
800 buffer.dangerously_get_string().push_str(#stream_script);
802
803 let __cheers_nested_streams = __cheers_async_stream_collection.finish();
804 let buffer = *buffer;
805 let __cheers_parent_rendered = ::cheers::Raw::<_, #marker_ident>::dangerously_create(
806 buffer.rendered().into_inner()
807 ).render();
808
809 (__cheers_parent_rendered, __cheers_nested_streams)
810 }),
811 |(__cheers_parent_rendered, __cheers_nested_streams)| {
812 ::cheers::__internal::futures::StreamExt::chain(
813 ::cheers::__internal::futures::stream::once(async move {
814 __cheers_parent_rendered
815 }),
816 ::cheers::__internal::futures::stream::select_all(
817 __cheers_nested_streams
818 ),
819 )
820 },
821 )
822 }
823 }
824
825 fn hot_island_stream_tokens_expr(
826 async_token: Token![async],
827 load_code: &TokenStream,
828 render_code: &TokenStream,
829 key: &str,
830 ) -> TokenStream {
831 let marker_ident = ElementNode::CONTEXT.marker_type();
832 let buffer_ident = Generator::buffer_ident();
833 let template_start = format!(r#"<template data-ssr="{key}-t">"#);
834 let stream_script =
835 format!(r#"</template><script data-ssr="{key}-s">__ssrStream('{key}')</script>"#);
836
837 quote! {
838 ::cheers::__internal::futures::stream::once(#async_token move {
839 #load_code
840
841 let __cheers_async_island_render_fn: fn() -> ::std::string::String = || {
842 let mut buffer = ::cheers::prelude::Buffer::<#marker_ident>::new();
843 let #buffer_ident = &mut buffer;
844 use ::cheers::validation::attributes::*;
845 #render_code
846 buffer.rendered().into_inner()
847 };
848 let mut __cheers_async_island_render = move || {
849 ::cheers::__internal::subsecond::call(__cheers_async_island_render_fn)
850 };
851
852 let __cheers_async_island_html = __cheers_async_island_render();
853 ::cheers::__internal::async_islands::register(
854 #key,
855 __cheers_async_island_render,
856 );
857
858 let mut buffer = ::cheers::prelude::Buffer::<#marker_ident>::new();
859 buffer.dangerously_get_string().push_str(#template_start);
861 buffer.dangerously_get_string().push_str(&__cheers_async_island_html);
863 buffer.dangerously_get_string().push_str(#stream_script);
865
866 ::cheers::Raw::<_, #marker_ident>::dangerously_create(
867 buffer.rendered().into_inner()
868 ).render()
869 })
870 }
871 }
872
873 fn add_data_ssr_key(&mut self) -> String {
874 let key = {
875 let span = self.async_token.span;
876 let file = span.file();
877 let start = span.start();
878 let line = start.line;
879 let column = start.column;
880
881 let mut hasher = FxHasher::default();
882 file.hash(&mut hasher);
883 async_source_ordinal(&file, line, column)
890 .unwrap_or(line)
891 .hash(&mut hasher);
892 column.hash(&mut hasher);
893 let hash64 = hasher.finish();
894 let hash32 = (hash64 as u32) ^ ((hash64 >> 32) as u32);
895 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(hash32.to_be_bytes())
896 };
897 let elem = self
898 .else_block
899 .nodes
900 .0
901 .get_mut(self.else_block_first_elem_idx)
902 .expect("the else block to have at least a single element node");
903 if let ElementNode::Element(elem) = elem {
904 elem.attrs.push(Attribute::Regular {
905 name: AttributeName::Unchecked(LitStr::new("data-ssr", Span::mixed_site())),
906 kind: AttributeKind::Value {
907 value: AttributeValueNode::Literal(Literal::Str(LitStr::new(
908 &key,
909 Span::mixed_site(),
910 ))),
911 toggle: None,
912 },
913 });
914 } else {
915 panic!("the first element node is not an element")
916 }
917
918 key
919 }
920}
921
922impl Generate for Async {
923 const CONTEXT: Context = ElementNode::CONTEXT;
924
925 fn generate(&mut self, g: &mut Generator<'_>) {
926 let source_key = self.add_data_ssr_key();
927 let leading_let_count = self
928 .async_block
929 .nodes
930 .0
931 .iter()
932 .take_while(|node| {
933 matches!(
934 node,
935 ElementNode::Control(Control {
936 kind: ControlKind::Let(_),
937 ..
938 })
939 )
940 })
941 .count();
942 let render_nodes = &self.async_block.nodes.0[leading_let_count..];
943 let has_nested_async = element_nodes_contain_async(render_nodes);
944 let render_is_static = element_nodes_are_static(render_nodes);
945 let leading_bindings = leading_let_bindings(&self.async_block.nodes.0, leading_let_count);
946 let can_split_leading_lets = leading_let_count > 0 && !has_nested_async;
947
948 let async_token = self.async_token;
949 let else_block = self.else_block.block(g);
950 let buffer_ident = Generator::buffer_ident();
951 let async_root_start =
952 format!(r#"<div data-cheers-async-root="{source_key}" data-ssr="{source_key}">"#);
953 let else_block = quote! {
954 if ::cheers::__internal::async_islands::enabled() {
955 #buffer_ident.dangerously_get_string().push_str(#async_root_start);
957 #else_block
958 #buffer_ident.dangerously_get_string().push_str("</div>");
960 } else {
961 #else_block
962 }
963 };
964
965 let async_block = if has_nested_async {
966 g.with_async_stream_collection(|g| {
967 g.block_with(
968 self.async_block.brace_token,
969 |g| {
970 g.push(&mut self.async_block.nodes);
971 },
972 false,
973 )
974 })
975 } else if can_split_leading_lets {
976 g.block_with(
977 self.async_block.brace_token,
978 |g| {
979 for node in self.async_block.nodes.0.iter_mut().skip(leading_let_count) {
980 g.push(node);
981 }
982 },
983 false,
984 )
985 } else {
986 g.block_with(
987 self.async_block.brace_token,
988 |g| {
989 g.push(&mut self.async_block.nodes);
990 },
991 false,
992 )
993 };
994 let content_code = &async_block.stmts;
995 debug_assert!(
996 async_block.async_stmts.is_empty(),
997 "nested @async streams should be emitted through the buffer-scoped collector"
998 );
999
1000 let load_code = if can_split_leading_lets {
1001 self.async_block
1002 .nodes
1003 .0
1004 .iter()
1005 .take(leading_let_count)
1006 .map(|node| match node {
1007 ElementNode::Control(Control {
1008 kind: ControlKind::Let(Let(local)),
1009 ..
1010 }) => {
1011 quote!(#local;)
1012 }
1013 _ => TokenStream::new(),
1014 })
1015 .collect::<TokenStream>()
1016 } else {
1017 TokenStream::new()
1018 };
1019
1020 let render_contains_await = tokens_contain_ident(content_code, "await");
1021 let can_register_hot_island = render_is_static && !has_nested_async;
1022 let can_move_leading_lets_into_hot_args = can_split_leading_lets
1023 && leading_lets_can_be_moved_into_hot_args(
1024 &self.async_block.nodes.0,
1025 leading_let_count,
1026 );
1027 let can_hot_call_dynamic_render =
1034 can_move_leading_lets_into_hot_args && !render_contains_await;
1035
1036 let async_stream = if !has_nested_async {
1037 let stream = if can_register_hot_island {
1038 Self::hot_island_stream_tokens_expr(
1039 async_token,
1040 &load_code,
1041 content_code,
1042 &source_key,
1043 )
1044 } else if can_hot_call_dynamic_render {
1045 Self::stream_with_hot_render_call_tokens_expr(
1046 async_token,
1047 &load_code,
1048 content_code,
1049 &leading_bindings,
1050 &source_key,
1051 )
1052 } else if can_split_leading_lets {
1053 let stream_content_code = quote! {
1054 #load_code
1055 #content_code
1056 };
1057 Self::stream_tokens_expr(async_token, &stream_content_code, &source_key)
1058 } else {
1059 Self::stream_tokens_expr(async_token, content_code, &source_key)
1060 };
1061 quote! {
1062 {
1063 ::std::boxed::Box::pin(#stream) as ::std::pin::Pin<::std::boxed::Box<dyn ::cheers::__internal::futures::stream::Stream<Item = ::cheers::Rendered<::std::string::String>> + ::std::marker::Send>>
1064 }
1065 }
1066 } else {
1067 let stream =
1068 Self::stream_with_nested_tokens_expr(async_token, content_code, &source_key);
1069 quote! {
1070 {
1071 #stream
1072 }
1073 }
1074 };
1075
1076 g.push_async_stmt(async_stream);
1077 g.push_stmt(else_block);
1078 }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083 use quote::quote;
1084
1085 use super::async_marker_count;
1086 use crate::{Document, generate::lazy};
1087
1088 #[test]
1089 fn async_marker_count_accepts_rust_trivia_after_at() {
1090 let source = concat!(
1091 "@async {}\n",
1092 "@ async {}\n",
1093 "@\nasync {}\n",
1094 "@/* comment */async {}\n",
1095 "@ /* nested /* comment */ still comment */ async {}\n",
1096 "@asyncness {}\n",
1097 );
1098
1099 assert_eq!(async_marker_count(source), 5);
1100 }
1101
1102 #[test]
1103 fn split_dynamic_async_render_uses_argument_hot_call() {
1104 let expanded = lazy::<Document>(quote! {
1105 div {
1106 @async {
1107 @let items = load_items().await;
1108 List items;
1109 } @else {
1110 p { "Loading" }
1111 }
1112 }
1113 })
1114 .expect("document should generate")
1115 .to_string();
1116
1117 assert!(expanded.contains("hot_call_with_arg"), "{expanded}");
1118 }
1119
1120 #[test]
1121 fn dependent_leading_lets_skip_argument_hot_call() {
1122 let expanded = lazy::<Document>(quote! {
1123 div {
1124 @async {
1125 @let owner = String::from("Data");
1126 @let borrow = owner.as_str();
1127 p { (borrow) }
1128 } @else {
1129 p { "Loading" }
1130 }
1131 }
1132 })
1133 .expect("document should generate")
1134 .to_string();
1135
1136 assert!(!expanded.contains("hot_call_with_arg"), "{expanded}");
1137 }
1138
1139 #[test]
1140 fn async_render_with_await_skips_argument_hot_call() {
1141 let expanded = lazy::<Document>(quote! {
1142 div {
1143 @async {
1144 @let items = load_items().await;
1145 p { (render_later(items).await) }
1146 } @else {
1147 p { "Loading" }
1148 }
1149 }
1150 })
1151 .expect("document should generate")
1152 .to_string();
1153
1154 assert!(!expanded.contains("hot_call_with_arg"), "{expanded}");
1155 }
1156}