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