1use std::borrow::Cow;
2use std::mem::size_of;
3
4use proc_macro2::{Span, TokenStream};
5use quote::{quote_spanned, ToTokens};
6use syn::spanned::Spanned;
7use syn::{Expr, Lit, LitStr};
8
9fn stringify_at_runtime(src: impl ToTokens) -> TokenStream {
11 quote_spanned! {src.span().resolved_at(Span::call_site())=>
12 ::std::convert::Into::<::yew::virtual_dom::AttrValue>::into(#src)
13 }
14}
15
16pub trait Stringify {
20 fn try_into_lit(&self) -> Option<LitStr>;
22 fn stringify(&self) -> TokenStream;
24
25 fn optimize_literals(&self) -> TokenStream
27 where
28 Self: ToTokens,
29 {
30 self.optimize_literals_tagged().to_token_stream()
31 }
32
33 fn optimize_literals_tagged(&self) -> Value
35 where
36 Self: ToTokens,
37 {
38 if let Some(lit) = self.try_into_lit() {
39 Value::Static(lit.to_token_stream())
40 } else {
41 Value::Dynamic(self.to_token_stream())
42 }
43 }
44}
45impl<T: Stringify + ?Sized> Stringify for &T {
46 fn try_into_lit(&self) -> Option<LitStr> {
47 (*self).try_into_lit()
48 }
49
50 fn stringify(&self) -> TokenStream {
51 (*self).stringify()
52 }
53}
54
55pub enum Value {
58 Static(TokenStream),
59 Dynamic(TokenStream),
60}
61
62impl ToTokens for Value {
63 fn to_tokens(&self, tokens: &mut TokenStream) {
64 tokens.extend(match self {
65 Value::Static(tt) | Value::Dynamic(tt) => tt.clone(),
66 });
67 }
68}
69
70impl Stringify for LitStr {
71 fn try_into_lit(&self) -> Option<LitStr> {
72 Some(self.clone())
73 }
74
75 fn stringify(&self) -> TokenStream {
76 quote_spanned! {self.span()=>
77 ::yew::virtual_dom::AttrValue::Static(#self)
78 }
79 }
80}
81
82impl Stringify for Lit {
83 fn try_into_lit(&self) -> Option<LitStr> {
84 let mut buf = [0; size_of::<char>()];
85 let s: Cow<'_, str> = match self {
86 Lit::Str(v) => return v.try_into_lit(),
87 Lit::Char(v) => (&*v.value().encode_utf8(&mut buf)).into(),
88 Lit::Int(v) => v.base10_digits().into(),
89 Lit::Float(v) => v.base10_digits().into(),
90 Lit::Bool(v) => if v.value() { "true" } else { "false" }.into(),
91 Lit::Byte(v) => v.value().to_string().into(),
92 Lit::Verbatim(_) | Lit::ByteStr(_) => return None,
93 _ => unreachable!("unknown Lit {:?}", self),
94 };
95 Some(LitStr::new(&s, self.span()))
96 }
97
98 fn stringify(&self) -> TokenStream {
99 self.try_into_lit()
100 .as_ref()
101 .map_or_else(|| stringify_at_runtime(self), Stringify::stringify)
102 }
103}
104
105impl Stringify for Expr {
106 fn try_into_lit(&self) -> Option<LitStr> {
107 if let Expr::Lit(v) = self {
108 v.lit.try_into_lit()
109 } else {
110 None
111 }
112 }
113
114 fn stringify(&self) -> TokenStream {
115 self.try_into_lit()
116 .as_ref()
117 .map_or_else(|| stringify_at_runtime(self), Stringify::stringify)
118 }
119}