Skip to main content

turbo_rcstr_macros/
lib.rs

1//! Proc macro implementation of `rcstr!`.
2//!
3//! The implementation deliberately avoids `syn` and `quote`. The macro is
4//! invoked thousands of times across the workspace, so per-invocation cost
5//! matters: we pattern-match on `proc_macro::TokenTree` directly to
6//! identify a single string-literal token, ask the compiler for its
7//! unescaped value via [`Literal::str_value`] (gated by the unstable
8//! `proc_macro_value` feature), and emit the chosen expansion by parsing
9//! a string template via `TokenStream::from_str`.
10
11#![feature(proc_macro_value)]
12
13use std::str::FromStr;
14
15use proc_macro::{Literal, TokenStream, TokenTree};
16
17/// `MAX_INLINE_LEN` for the active `turbo-rcstr` configuration. Mirrors
18/// [`turbo_rcstr::tagged_value::MAX_INLINE_LEN`]
19const MAX_INLINE_LEN: usize = if cfg!(feature = "atom_size_128") {
20    15
21} else {
22    7
23};
24
25#[proc_macro]
26pub fn rcstr(input: TokenStream) -> TokenStream {
27    // Fast path: input is a single string-literal token whose unescaped
28    // length we can determine cheaply. Otherwise (multi-token expressions
29    // like `concat!(...)`, identifiers, empty input, non-string literals,
30    // escape-bearing literals) defer to the const-branch expansion so
31    // const evaluation picks the arm at compile time.
32    //
33    // `input.clone()` is cheap — a `TokenStream` is an opaque handle into
34    // the proc-macro server's storage rather than an owned tree of tokens
35    // — so cloning here lets us consume one copy in `classify_literal`
36    // while keeping the original around for the fallback path.
37    {
38        let source = if let Some((lit, len)) = classify_literal(input.clone()) {
39            if len <= MAX_INLINE_LEN {
40                format!("::turbo_rcstr::inline_atom({lit}).unwrap()")
41            } else {
42                format!(
43                    "{{ static RCSTR_STORAGE: ::turbo_rcstr::StaticPrehashedString = \
44                     ::turbo_rcstr::make_const_prehashed_string({lit}); const RCSTR: \
45                     ::turbo_rcstr::RcStr = ::turbo_rcstr::from_static(&RCSTR_STORAGE); \
46                     ::turbo_rcstr::__rcstr_static_submit!(
47                     ::turbo_rcstr::StaticRcStr(&RCSTR_STORAGE) ); RCSTR }}",
48                )
49            }
50        } else {
51            format!(
52                "{{ const TEXT: &str = {input}; if ::turbo_rcstr::is_atom_inlineable(TEXT) {{ \
53                 ::turbo_rcstr::inline_atom(TEXT).unwrap() }} else {{ static RCSTR_STORAGE: \
54                 ::turbo_rcstr::StaticPrehashedString = \
55                 ::turbo_rcstr::make_const_prehashed_string(TEXT); const RCSTR: \
56                 ::turbo_rcstr::RcStr = ::turbo_rcstr::from_static(&RCSTR_STORAGE); \
57                 ::turbo_rcstr::__rcstr_static_submit!(
58                 ::turbo_rcstr::StaticRcStr(&RCSTR_STORAGE) ); RCSTR }} }}",
59            )
60        };
61        TokenStream::from_str(&source).expect("emitted source parses")
62    }
63}
64
65/// If `input` is a single string-literal token, return the literal and
66/// its unescaped length. Returns `None` for non-literal inputs, multi-
67/// token inputs, or non-string literals (numeric, byte string, char,
68/// etc.) so the caller falls back to the const-branch expansion.
69///
70/// [`Literal::str_value`] resolves all escape sequences (regular strings,
71/// raw strings, unicode escapes) and reports an error for non-string
72/// literals — exactly the inspection we want.
73fn classify_literal(input: TokenStream) -> Option<(Literal, usize)> {
74    let mut iter = input.into_iter();
75    let TokenTree::Literal(lit) = iter.next()? else {
76        return None;
77    };
78    if iter.next().is_some() {
79        return None;
80    }
81    let value = lit.str_value().ok()?;
82    Some((lit, value.len()))
83}