bevy_reflect_derive/
string_expr.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{spanned::Spanned, LitStr};
4
5/// Contains tokens representing different kinds of string.
6#[derive(Clone)]
7pub(crate) enum StringExpr {
8    /// A string that is valid at compile time.
9    ///
10    /// This is either a string literal like `"mystring"`,
11    /// or a string created by a macro like [`module_path`]
12    /// or [`concat`].
13    Const(TokenStream),
14    /// A [string slice](str) that is borrowed for a `'static` lifetime.
15    Borrowed(TokenStream),
16    /// An [owned string](String).
17    Owned(TokenStream),
18}
19
20impl<T: ToString + Spanned> From<T> for StringExpr {
21    fn from(value: T) -> Self {
22        Self::from_lit(&LitStr::new(&value.to_string(), value.span()))
23    }
24}
25
26impl StringExpr {
27    /// Creates a [constant] [`StringExpr`] from a [`struct@LitStr`].
28    ///
29    /// [constant]: StringExpr::Const
30    pub fn from_lit(lit: &LitStr) -> Self {
31        Self::Const(lit.to_token_stream())
32    }
33
34    /// Creates a [constant] [`StringExpr`] by interpreting a [string slice][str] as a [`struct@LitStr`].
35    ///
36    /// [constant]: StringExpr::Const
37    pub fn from_str(string: &str) -> Self {
38        Self::Const(string.into_token_stream())
39    }
40
41    /// Returns tokens for an [owned string](String).
42    ///
43    /// The returned expression will allocate unless the [`StringExpr`] is [already owned].
44    ///
45    /// [already owned]: StringExpr::Owned
46    pub fn into_owned(self) -> TokenStream {
47        let bevy_reflect_path = crate::meta::get_bevy_reflect_path();
48
49        match self {
50            Self::Const(tokens) | Self::Borrowed(tokens) => quote! {
51                #bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(#tokens)
52            },
53            Self::Owned(owned) => owned,
54        }
55    }
56
57    /// Returns tokens for a statically borrowed [string slice](str).
58    pub fn into_borrowed(self) -> TokenStream {
59        match self {
60            Self::Const(tokens) | Self::Borrowed(tokens) => tokens,
61            Self::Owned(owned) => quote! {
62                &#owned
63            },
64        }
65    }
66
67    /// Appends a [`StringExpr`] to another.
68    ///
69    /// If both expressions are [`StringExpr::Const`] this will use [`concat`] to merge them.
70    pub fn appended_by(mut self, other: StringExpr) -> Self {
71        if let Self::Const(tokens) = self {
72            if let Self::Const(more) = other {
73                return Self::Const(quote! {
74                    ::core::concat!(#tokens, #more)
75                });
76            }
77            self = Self::Const(tokens);
78        }
79
80        let owned = self.into_owned();
81        let borrowed = other.into_borrowed();
82        Self::Owned(quote! {
83            ::core::ops::Add::<&str>::add(#owned, #borrowed)
84        })
85    }
86}
87
88impl Default for StringExpr {
89    fn default() -> Self {
90        StringExpr::from_str("")
91    }
92}
93
94impl FromIterator<StringExpr> for StringExpr {
95    fn from_iter<T: IntoIterator<Item = StringExpr>>(iter: T) -> Self {
96        let mut iter = iter.into_iter();
97        match iter.next() {
98            Some(mut expr) => {
99                for next in iter {
100                    expr = expr.appended_by(next);
101                }
102
103                expr
104            }
105            None => Default::default(),
106        }
107    }
108}