This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_macro/
stringify.rs

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
9/// Stringify a value at runtime.
10fn 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
16/// Create `AttrValue` construction calls.
17///
18/// This is deliberately not implemented for strings to preserve spans.
19pub trait Stringify {
20    /// Try to turn the value into a string literal.
21    fn try_into_lit(&self) -> Option<LitStr>;
22    /// Create `AttrValue` however possible.
23    fn stringify(&self) -> TokenStream;
24
25    /// Optimize literals to `&'static str`, otherwise keep the value as is.
26    fn optimize_literals(&self) -> TokenStream
27    where
28        Self: ToTokens,
29    {
30        self.optimize_literals_tagged().to_token_stream()
31    }
32
33    /// Like `optimize_literals` but tags static or dynamic strings with [Value]
34    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
55/// A stringified value that can be either static (known at compile time) or dynamic (known only at
56/// runtime)
57pub 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}