Skip to main content

rustc_ast/util/
literal.rs

1//! Code related to parsing literals.
2
3use std::fmt::Write as _;
4use std::{ascii, fmt, str};
5
6use rustc_literal_escaper::{
7    MixedUnit, unescape_byte, unescape_byte_str, unescape_c_str, unescape_char, unescape_str,
8};
9use rustc_span::{ByteSymbol, Span, Symbol, kw, sym};
10use tracing::debug;
11
12use crate::ast::{self, LitKind, MetaItemLit, StrStyle};
13use crate::token::{self, Token};
14
15// Escapes a string, represented as a symbol. Reuses the original symbol,
16// avoiding interning, if no changes are required.
17pub fn escape_string_symbol(symbol: Symbol) -> Symbol {
18    // Don't use escape_default() here, because using it in conjunction with to_string()
19    // is slow.
20    let s = symbol.as_str();
21    let mut escaped = String::with_capacity(s.len());
22    for c in s.chars() {
23        match c {
24            '\t' => escaped.push_str("\\t"),
25            '\r' => escaped.push_str("\\r"),
26            '\n' => escaped.push_str("\\n"),
27            '\\' => escaped.push_str("\\\\"),
28            '\'' => escaped.push_str("\\'"),
29            '\"' => escaped.push_str("\\\""),
30            '\x20'..='\x7e' => escaped.push(c),
31            c => escaped.write_fmt(format_args!("\\u{{{0:x}}}", c as u32))write!(escaped, "\\u{{{:x}}}", c as u32).unwrap(),
32        }
33    }
34    if s == escaped { symbol } else { Symbol::intern(&escaped) }
35}
36
37// Escapes a char.
38pub fn escape_char_symbol(ch: char) -> Symbol {
39    let s: String = ch.escape_default().map(Into::<char>::into).collect();
40    Symbol::intern(&s)
41}
42
43// Escapes a byte string.
44pub fn escape_byte_str_symbol(bytes: &[u8]) -> Symbol {
45    let s = bytes.escape_ascii().to_string();
46    Symbol::intern(&s)
47}
48
49#[derive(#[automatically_derived]
impl ::core::fmt::Debug for LitError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            LitError::InvalidSuffix(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "InvalidSuffix", &__self_0),
            LitError::InvalidIntSuffix(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "InvalidIntSuffix", &__self_0),
            LitError::InvalidFloatSuffix(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "InvalidFloatSuffix", &__self_0),
            LitError::NonDecimalFloat(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "NonDecimalFloat", &__self_0),
            LitError::IntTooLarge(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "IntTooLarge", &__self_0),
        }
    }
}Debug)]
50pub enum LitError {
51    InvalidSuffix(Symbol),
52    InvalidIntSuffix(Symbol),
53    InvalidFloatSuffix(Symbol),
54    NonDecimalFloat(u32), // u32 is the base
55    IntTooLarge(u32),     // u32 is the base
56}
57
58impl LitKind {
59    /// Converts literal token into a semantic literal.
60    pub fn from_token_lit(lit: token::Lit) -> Result<LitKind, LitError> {
61        let token::Lit { kind, symbol, suffix } = lit;
62        if let Some(suffix) = suffix
63            && !kind.may_have_suffix()
64        {
65            return Err(LitError::InvalidSuffix(suffix));
66        }
67
68        // For byte/char/string literals, chars and escapes have already been
69        // checked in the lexer (in `cook_lexer_literal`). So we can assume all
70        // chars and escapes are valid here.
71        Ok(match kind {
72            token::Bool => {
73                if !symbol.is_bool_lit() {
    ::core::panicking::panic("assertion failed: symbol.is_bool_lit()")
};assert!(symbol.is_bool_lit());
74                LitKind::Bool(symbol == kw::True)
75            }
76            token::Byte => {
77                return unescape_byte(symbol.as_str())
78                    .map(LitKind::Byte)
79                    .map_err(|_| {
    ::core::panicking::panic_fmt(format_args!("failed to unescape byte literal"));
}panic!("failed to unescape byte literal"));
80            }
81            token::Char => {
82                return unescape_char(symbol.as_str())
83                    .map(LitKind::Char)
84                    .map_err(|_| {
    ::core::panicking::panic_fmt(format_args!("failed to unescape char literal"));
}panic!("failed to unescape char literal"));
85            }
86
87            // There are some valid suffixes for integer and float literals,
88            // so all the handling is done internally.
89            token::Integer => return integer_lit(symbol, suffix),
90            token::Float => return float_lit(symbol, suffix),
91
92            token::Str => {
93                // If there are no characters requiring special treatment we can
94                // reuse the symbol from the token. Otherwise, we must generate a
95                // new symbol because the string in the LitKind is different to the
96                // string in the token.
97                let s = symbol.as_str();
98                // Vanilla strings are so common we optimize for the common case where no chars
99                // requiring special behaviour are present.
100                let symbol = if s.contains('\\') {
101                    let mut buf = String::with_capacity(s.len());
102                    // Force-inlining here is aggressive but the closure is
103                    // called on every char in the string, so it can be hot in
104                    // programs with many long strings containing escapes.
105                    unescape_str(
106                        s,
107                        #[inline(always)]
108                        |_, res| match res {
109                            Ok(c) => buf.push(c),
110                            Err(err) => {
111                                if !!err.is_fatal() {
    {
        ::core::panicking::panic_fmt(format_args!("failed to unescape string literal"));
    }
}assert!(!err.is_fatal(), "failed to unescape string literal")
112                            }
113                        },
114                    );
115                    Symbol::intern(&buf)
116                } else {
117                    symbol
118                };
119                LitKind::Str(symbol, ast::StrStyle::Cooked)
120            }
121            token::StrRaw(n) => {
122                // Raw strings have no escapes so no work is needed here.
123                LitKind::Str(symbol, ast::StrStyle::Raw(n))
124            }
125            token::ByteStr => {
126                let s = symbol.as_str();
127                let mut buf = Vec::with_capacity(s.len());
128                unescape_byte_str(s, |_, res| match res {
129                    Ok(b) => buf.push(b),
130                    Err(err) => {
131                        if !!err.is_fatal() {
    {
        ::core::panicking::panic_fmt(format_args!("failed to unescape string literal"));
    }
}assert!(!err.is_fatal(), "failed to unescape string literal")
132                    }
133                });
134                LitKind::ByteStr(ByteSymbol::intern(&buf), StrStyle::Cooked)
135            }
136            token::ByteStrRaw(n) => {
137                // Raw byte strings have no escapes so no work is needed here.
138                let buf = symbol.as_str().to_owned().into_bytes();
139                LitKind::ByteStr(ByteSymbol::intern(&buf), StrStyle::Raw(n))
140            }
141            token::CStr => {
142                let s = symbol.as_str();
143                let mut buf = Vec::with_capacity(s.len());
144                unescape_c_str(s, |_span, res| match res {
145                    Ok(MixedUnit::Char(c)) => {
146                        buf.extend_from_slice(c.get().encode_utf8(&mut [0; 4]).as_bytes())
147                    }
148                    Ok(MixedUnit::HighByte(b)) => buf.push(b.get()),
149                    Err(err) => {
150                        if !!err.is_fatal() {
    {
        ::core::panicking::panic_fmt(format_args!("failed to unescape C string literal"));
    }
}assert!(!err.is_fatal(), "failed to unescape C string literal")
151                    }
152                });
153                buf.push(0);
154                LitKind::CStr(ByteSymbol::intern(&buf), StrStyle::Cooked)
155            }
156            token::CStrRaw(n) => {
157                // Raw strings have no escapes so we can convert the symbol
158                // directly to a `Arc<u8>` after appending the terminating NUL
159                // char.
160                let mut buf = symbol.as_str().to_owned().into_bytes();
161                buf.push(0);
162                LitKind::CStr(ByteSymbol::intern(&buf), StrStyle::Raw(n))
163            }
164            token::Err(guar) => LitKind::Err(guar),
165        })
166    }
167}
168
169impl fmt::Display for LitKind {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match *self {
172            LitKind::Byte(b) => {
173                let b: String = ascii::escape_default(b).map(Into::<char>::into).collect();
174                f.write_fmt(format_args!("b\'{0}\'", b))write!(f, "b'{b}'")?;
175            }
176            LitKind::Char(ch) => f.write_fmt(format_args!("\'{0}\'", escape_char_symbol(ch)))write!(f, "'{}'", escape_char_symbol(ch))?,
177            LitKind::Str(sym, StrStyle::Cooked) => f.write_fmt(format_args!("\"{0}\"", escape_string_symbol(sym)))write!(f, "\"{}\"", escape_string_symbol(sym))?,
178            LitKind::Str(sym, StrStyle::Raw(n)) => f.write_fmt(format_args!("r{0}\"{1}\"{0}", "#".repeat(n as usize), sym))write!(
179                f,
180                "r{delim}\"{string}\"{delim}",
181                delim = "#".repeat(n as usize),
182                string = sym
183            )?,
184            LitKind::ByteStr(ref byte_sym, StrStyle::Cooked) => {
185                f.write_fmt(format_args!("b\"{0}\"",
        escape_byte_str_symbol(byte_sym.as_byte_str())))write!(f, "b\"{}\"", escape_byte_str_symbol(byte_sym.as_byte_str()))?
186            }
187            LitKind::ByteStr(ref byte_sym, StrStyle::Raw(n)) => {
188                // Unwrap because raw byte string literals can only contain ASCII.
189                let symbol = str::from_utf8(byte_sym.as_byte_str()).unwrap();
190                f.write_fmt(format_args!("br{0}\"{1}\"{0}", "#".repeat(n as usize), symbol))write!(
191                    f,
192                    "br{delim}\"{string}\"{delim}",
193                    delim = "#".repeat(n as usize),
194                    string = symbol
195                )?;
196            }
197            LitKind::CStr(ref bytes, StrStyle::Cooked) => {
198                f.write_fmt(format_args!("c\"{0}\"",
        escape_byte_str_symbol(bytes.as_byte_str())))write!(f, "c\"{}\"", escape_byte_str_symbol(bytes.as_byte_str()))?
199            }
200            LitKind::CStr(ref bytes, StrStyle::Raw(n)) => {
201                // This can only be valid UTF-8.
202                let symbol = str::from_utf8(bytes.as_byte_str()).unwrap();
203                f.write_fmt(format_args!("cr{0}\"{1}\"{0}", "#".repeat(n as usize), symbol))write!(f, "cr{delim}\"{symbol}\"{delim}", delim = "#".repeat(n as usize),)?;
204            }
205            LitKind::Int(n, ty) => {
206                f.write_fmt(format_args!("{0}", n))write!(f, "{n}")?;
207                match ty {
208                    ast::LitIntType::Unsigned(ty) => f.write_fmt(format_args!("{0}", ty.name_str()))write!(f, "{}", ty.name_str())?,
209                    ast::LitIntType::Signed(ty) => f.write_fmt(format_args!("{0}", ty.name_str()))write!(f, "{}", ty.name_str())?,
210                    ast::LitIntType::Unsuffixed => {}
211                }
212            }
213            LitKind::Float(symbol, ty) => {
214                f.write_fmt(format_args!("{0}", symbol))write!(f, "{symbol}")?;
215                match ty {
216                    ast::LitFloatType::Suffixed(ty) => f.write_fmt(format_args!("{0}", ty.name_str()))write!(f, "{}", ty.name_str())?,
217                    ast::LitFloatType::Unsuffixed => {}
218                }
219            }
220            LitKind::Bool(b) => f.write_fmt(format_args!("{0}", if b { "true" } else { "false" }))write!(f, "{}", if b { "true" } else { "false" })?,
221            LitKind::Err(_) => {
222                // This only shows up in places like `-Zunpretty=hir` output, so we
223                // don't bother to produce something useful.
224                f.write_fmt(format_args!("<bad-literal>"))write!(f, "<bad-literal>")?;
225            }
226        }
227
228        Ok(())
229    }
230}
231
232impl MetaItemLit {
233    /// Converts a token literal into a meta item literal.
234    pub fn from_token_lit(token_lit: token::Lit, span: Span) -> Result<MetaItemLit, LitError> {
235        Ok(MetaItemLit {
236            symbol: token_lit.symbol,
237            suffix: token_lit.suffix,
238            kind: LitKind::from_token_lit(token_lit)?,
239            span,
240        })
241    }
242
243    /// Cheaply converts a meta item literal into a token literal.
244    pub fn as_token_lit(&self) -> token::Lit {
245        let kind = match self.kind {
246            LitKind::Bool(_) => token::Bool,
247            LitKind::Str(_, ast::StrStyle::Cooked) => token::Str,
248            LitKind::Str(_, ast::StrStyle::Raw(n)) => token::StrRaw(n),
249            LitKind::ByteStr(_, ast::StrStyle::Cooked) => token::ByteStr,
250            LitKind::ByteStr(_, ast::StrStyle::Raw(n)) => token::ByteStrRaw(n),
251            LitKind::CStr(_, ast::StrStyle::Cooked) => token::CStr,
252            LitKind::CStr(_, ast::StrStyle::Raw(n)) => token::CStrRaw(n),
253            LitKind::Byte(_) => token::Byte,
254            LitKind::Char(_) => token::Char,
255            LitKind::Int(..) => token::Integer,
256            LitKind::Float(..) => token::Float,
257            LitKind::Err(guar) => token::Err(guar),
258        };
259
260        token::Lit::new(kind, self.symbol, self.suffix)
261    }
262
263    /// Converts an arbitrary token into meta item literal.
264    pub fn from_token(token: &Token) -> Option<MetaItemLit> {
265        token::Lit::from_token(token)
266            .and_then(|token_lit| MetaItemLit::from_token_lit(token_lit, token.span).ok())
267    }
268}
269
270fn strip_underscores(symbol: Symbol) -> Symbol {
271    // Do not allocate a new string unless necessary.
272    let s = symbol.as_str();
273    if s.contains('_') {
274        let mut s = s.to_string();
275        s.retain(|c| c != '_');
276        return Symbol::intern(&s);
277    }
278    symbol
279}
280
281fn filtered_float_lit(
282    symbol: Symbol,
283    suffix: Option<Symbol>,
284    base: u32,
285) -> Result<LitKind, LitError> {
286    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_ast/src/util/literal.rs:286",
                        "rustc_ast::util::literal", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_ast/src/util/literal.rs"),
                        ::tracing_core::__macro_support::Option::Some(286u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_ast::util::literal"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("filtered_float_lit: {0:?}, {1:?}, {2:?}",
                                                    symbol, suffix, base) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("filtered_float_lit: {:?}, {:?}, {:?}", symbol, suffix, base);
287    if base != 10 {
288        return Err(LitError::NonDecimalFloat(base));
289    }
290    Ok(match suffix {
291        Some(suffix) => LitKind::Float(
292            symbol,
293            ast::LitFloatType::Suffixed(match suffix {
294                sym::f16 => ast::FloatTy::F16,
295                sym::f32 => ast::FloatTy::F32,
296                sym::f64 => ast::FloatTy::F64,
297                sym::f128 => ast::FloatTy::F128,
298                _ => return Err(LitError::InvalidFloatSuffix(suffix)),
299            }),
300        ),
301        None => LitKind::Float(symbol, ast::LitFloatType::Unsuffixed),
302    })
303}
304
305fn float_lit(symbol: Symbol, suffix: Option<Symbol>) -> Result<LitKind, LitError> {
306    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_ast/src/util/literal.rs:306",
                        "rustc_ast::util::literal", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_ast/src/util/literal.rs"),
                        ::tracing_core::__macro_support::Option::Some(306u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_ast::util::literal"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("float_lit: {0:?}, {1:?}",
                                                    symbol, suffix) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("float_lit: {:?}, {:?}", symbol, suffix);
307    filtered_float_lit(strip_underscores(symbol), suffix, 10)
308}
309
310fn integer_lit(symbol: Symbol, suffix: Option<Symbol>) -> Result<LitKind, LitError> {
311    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_ast/src/util/literal.rs:311",
                        "rustc_ast::util::literal", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_ast/src/util/literal.rs"),
                        ::tracing_core::__macro_support::Option::Some(311u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_ast::util::literal"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("integer_lit: {0:?}, {1:?}",
                                                    symbol, suffix) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("integer_lit: {:?}, {:?}", symbol, suffix);
312    let symbol = strip_underscores(symbol);
313    let s = symbol.as_str();
314
315    let base = match s.as_bytes() {
316        [b'0', b'x', ..] => 16,
317        [b'0', b'o', ..] => 8,
318        [b'0', b'b', ..] => 2,
319        _ => 10,
320    };
321
322    let ty = match suffix {
323        Some(suf) => match suf {
324            sym::isize => ast::LitIntType::Signed(ast::IntTy::Isize),
325            sym::i8 => ast::LitIntType::Signed(ast::IntTy::I8),
326            sym::i16 => ast::LitIntType::Signed(ast::IntTy::I16),
327            sym::i32 => ast::LitIntType::Signed(ast::IntTy::I32),
328            sym::i64 => ast::LitIntType::Signed(ast::IntTy::I64),
329            sym::i128 => ast::LitIntType::Signed(ast::IntTy::I128),
330            sym::usize => ast::LitIntType::Unsigned(ast::UintTy::Usize),
331            sym::u8 => ast::LitIntType::Unsigned(ast::UintTy::U8),
332            sym::u16 => ast::LitIntType::Unsigned(ast::UintTy::U16),
333            sym::u32 => ast::LitIntType::Unsigned(ast::UintTy::U32),
334            sym::u64 => ast::LitIntType::Unsigned(ast::UintTy::U64),
335            sym::u128 => ast::LitIntType::Unsigned(ast::UintTy::U128),
336            // `1f64` and `2f32` etc. are valid float literals, and
337            // `fxxx` looks more like an invalid float literal than invalid integer literal.
338            _ if suf.as_str().starts_with('f') => return filtered_float_lit(symbol, suffix, base),
339            _ => return Err(LitError::InvalidIntSuffix(suf)),
340        },
341        _ => ast::LitIntType::Unsuffixed,
342    };
343
344    let s = &s[if base != 10 { 2 } else { 0 }..];
345    u128::from_str_radix(s, base)
346        .map(|i| LitKind::Int(i.into(), ty))
347        .map_err(|_| LitError::IntTooLarge(base))
348}