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