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}