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