Skip to main content

rustc_ast/util/
literal.rs

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