rustc_parse/lexer/
mod.rs

1use std::ops::Range;
2
3use diagnostics::make_unclosed_delims_error;
4use rustc_ast::ast::{self, AttrStyle};
5use rustc_ast::token::{self, CommentKind, Delimiter, IdentIsRaw, Token, TokenKind};
6use rustc_ast::tokenstream::TokenStream;
7use rustc_ast::util::unicode::contains_text_flow_control_chars;
8use rustc_errors::codes::*;
9use rustc_errors::{Applicability, Diag, DiagCtxtHandle, StashKey};
10use rustc_lexer::{
11    Base, Cursor, DocStyle, FrontmatterAllowed, LiteralKind, RawStrError, is_whitespace,
12};
13use rustc_literal_escaper::{EscapeError, Mode, unescape_mixed, unescape_unicode};
14use rustc_session::lint::BuiltinLintDiag;
15use rustc_session::lint::builtin::{
16    RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX,
17    TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
18};
19use rustc_session::parse::ParseSess;
20use rustc_span::{BytePos, Pos, Span, Symbol, sym};
21use tracing::debug;
22
23use crate::errors;
24use crate::lexer::diagnostics::TokenTreeDiagInfo;
25use crate::lexer::unicode_chars::UNICODE_ARRAY;
26
27mod diagnostics;
28mod tokentrees;
29mod unescape_error_reporting;
30mod unicode_chars;
31
32use unescape_error_reporting::{emit_unescape_error, escaped_char};
33
34// This type is used a lot. Make sure it doesn't unintentionally get bigger.
35//
36// This assertion is in this crate, rather than in `rustc_lexer`, because that
37// crate cannot depend on `rustc_data_structures`.
38#[cfg(target_pointer_width = "64")]
39rustc_data_structures::static_assert_size!(rustc_lexer::Token, 12);
40
41#[derive(Clone, Debug)]
42pub(crate) struct UnmatchedDelim {
43    pub found_delim: Option<Delimiter>,
44    pub found_span: Span,
45    pub unclosed_span: Option<Span>,
46    pub candidate_span: Option<Span>,
47}
48
49pub(crate) fn lex_token_trees<'psess, 'src>(
50    psess: &'psess ParseSess,
51    mut src: &'src str,
52    mut start_pos: BytePos,
53    override_span: Option<Span>,
54) -> Result<TokenStream, Vec<Diag<'psess>>> {
55    // Skip `#!`, if present.
56    if let Some(shebang_len) = rustc_lexer::strip_shebang(src) {
57        src = &src[shebang_len..];
58        start_pos = start_pos + BytePos::from_usize(shebang_len);
59    }
60
61    let cursor = Cursor::new(src, FrontmatterAllowed::Yes);
62    let mut lexer = Lexer {
63        psess,
64        start_pos,
65        pos: start_pos,
66        src,
67        cursor,
68        override_span,
69        nbsp_is_whitespace: false,
70        last_lifetime: None,
71        token: Token::dummy(),
72        diag_info: TokenTreeDiagInfo::default(),
73    };
74    let res = lexer.lex_token_trees(/* is_delimited */ false);
75
76    let mut unmatched_delims: Vec<_> = lexer
77        .diag_info
78        .unmatched_delims
79        .into_iter()
80        .filter_map(|unmatched_delim| make_unclosed_delims_error(unmatched_delim, psess))
81        .collect();
82
83    match res {
84        Ok((_open_spacing, stream)) => {
85            if unmatched_delims.is_empty() {
86                Ok(stream)
87            } else {
88                // Return error if there are unmatched delimiters or unclosed delimiters.
89                Err(unmatched_delims)
90            }
91        }
92        Err(errs) => {
93            // We emit delimiter mismatch errors first, then emit the unclosing delimiter mismatch
94            // because the delimiter mismatch is more likely to be the root cause of error
95            unmatched_delims.extend(errs);
96            Err(unmatched_delims)
97        }
98    }
99}
100
101struct Lexer<'psess, 'src> {
102    psess: &'psess ParseSess,
103    /// Initial position, read-only.
104    start_pos: BytePos,
105    /// The absolute offset within the source_map of the current character.
106    pos: BytePos,
107    /// Source text to tokenize.
108    src: &'src str,
109    /// Cursor for getting lexer tokens.
110    cursor: Cursor<'src>,
111    override_span: Option<Span>,
112    /// When a "unknown start of token: \u{a0}" has already been emitted earlier
113    /// in this file, it's safe to treat further occurrences of the non-breaking
114    /// space character as whitespace.
115    nbsp_is_whitespace: bool,
116
117    /// Track the `Span` for the leading `'` of the last lifetime. Used for
118    /// diagnostics to detect possible typo where `"` was meant.
119    last_lifetime: Option<Span>,
120
121    /// The current token.
122    token: Token,
123
124    diag_info: TokenTreeDiagInfo,
125}
126
127impl<'psess, 'src> Lexer<'psess, 'src> {
128    fn dcx(&self) -> DiagCtxtHandle<'psess> {
129        self.psess.dcx()
130    }
131
132    fn mk_sp(&self, lo: BytePos, hi: BytePos) -> Span {
133        self.override_span.unwrap_or_else(|| Span::with_root_ctxt(lo, hi))
134    }
135
136    /// Returns the next token, paired with a bool indicating if the token was
137    /// preceded by whitespace.
138    fn next_token_from_cursor(&mut self) -> (Token, bool) {
139        let mut preceded_by_whitespace = false;
140        let mut swallow_next_invalid = 0;
141        // Skip trivial (whitespace & comments) tokens
142        loop {
143            let str_before = self.cursor.as_str();
144            let token = self.cursor.advance_token();
145            let start = self.pos;
146            self.pos = self.pos + BytePos(token.len);
147
148            debug!("next_token: {:?}({:?})", token.kind, self.str_from(start));
149
150            if let rustc_lexer::TokenKind::Semi
151            | rustc_lexer::TokenKind::LineComment { .. }
152            | rustc_lexer::TokenKind::BlockComment { .. }
153            | rustc_lexer::TokenKind::CloseParen
154            | rustc_lexer::TokenKind::CloseBrace
155            | rustc_lexer::TokenKind::CloseBracket = token.kind
156            {
157                // Heuristic: we assume that it is unlikely we're dealing with an unterminated
158                // string surrounded by single quotes.
159                self.last_lifetime = None;
160            }
161
162            // Now "cook" the token, converting the simple `rustc_lexer::TokenKind` enum into a
163            // rich `rustc_ast::TokenKind`. This turns strings into interned symbols and runs
164            // additional validation.
165            let kind = match token.kind {
166                rustc_lexer::TokenKind::LineComment { doc_style } => {
167                    // Skip non-doc comments
168                    let Some(doc_style) = doc_style else {
169                        self.lint_unicode_text_flow(start);
170                        preceded_by_whitespace = true;
171                        continue;
172                    };
173
174                    // Opening delimiter of the length 3 is not included into the symbol.
175                    let content_start = start + BytePos(3);
176                    let content = self.str_from(content_start);
177                    self.cook_doc_comment(content_start, content, CommentKind::Line, doc_style)
178                }
179                rustc_lexer::TokenKind::BlockComment { doc_style, terminated } => {
180                    if !terminated {
181                        self.report_unterminated_block_comment(start, doc_style);
182                    }
183
184                    // Skip non-doc comments
185                    let Some(doc_style) = doc_style else {
186                        self.lint_unicode_text_flow(start);
187                        preceded_by_whitespace = true;
188                        continue;
189                    };
190
191                    // Opening delimiter of the length 3 and closing delimiter of the length 2
192                    // are not included into the symbol.
193                    let content_start = start + BytePos(3);
194                    let content_end = self.pos - BytePos(if terminated { 2 } else { 0 });
195                    let content = self.str_from_to(content_start, content_end);
196                    self.cook_doc_comment(content_start, content, CommentKind::Block, doc_style)
197                }
198                rustc_lexer::TokenKind::Frontmatter { has_invalid_preceding_whitespace, invalid_infostring } => {
199                    self.validate_frontmatter(start, has_invalid_preceding_whitespace, invalid_infostring);
200                    preceded_by_whitespace = true;
201                    continue;
202                }
203                rustc_lexer::TokenKind::Whitespace => {
204                    preceded_by_whitespace = true;
205                    continue;
206                }
207                rustc_lexer::TokenKind::Ident => self.ident(start),
208                rustc_lexer::TokenKind::RawIdent => {
209                    let sym = nfc_normalize(self.str_from(start + BytePos(2)));
210                    let span = self.mk_sp(start, self.pos);
211                    self.psess.symbol_gallery.insert(sym, span);
212                    if !sym.can_be_raw() {
213                        self.dcx().emit_err(errors::CannotBeRawIdent { span, ident: sym });
214                    }
215                    self.psess.raw_identifier_spans.push(span);
216                    token::Ident(sym, IdentIsRaw::Yes)
217                }
218                rustc_lexer::TokenKind::UnknownPrefix => {
219                    self.report_unknown_prefix(start);
220                    self.ident(start)
221                }
222                rustc_lexer::TokenKind::UnknownPrefixLifetime => {
223                    self.report_unknown_prefix(start);
224                    // Include the leading `'` in the real identifier, for macro
225                    // expansion purposes. See #12512 for the gory details of why
226                    // this is necessary.
227                    let lifetime_name = self.str_from(start);
228                    self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
229                    let ident = Symbol::intern(lifetime_name);
230                    token::Lifetime(ident, IdentIsRaw::No)
231                }
232                rustc_lexer::TokenKind::InvalidIdent
233                    // Do not recover an identifier with emoji if the codepoint is a confusable
234                    // with a recoverable substitution token, like `➖`.
235                    if !UNICODE_ARRAY.iter().any(|&(c, _, _)| {
236                        let sym = self.str_from(start);
237                        sym.chars().count() == 1 && c == sym.chars().next().unwrap()
238                    }) =>
239                {
240                    let sym = nfc_normalize(self.str_from(start));
241                    let span = self.mk_sp(start, self.pos);
242                    self.psess
243                        .bad_unicode_identifiers
244                        .borrow_mut()
245                        .entry(sym)
246                        .or_default()
247                        .push(span);
248                    token::Ident(sym, IdentIsRaw::No)
249                }
250                // split up (raw) c string literals to an ident and a string literal when edition <
251                // 2021.
252                rustc_lexer::TokenKind::Literal {
253                    kind: kind @ (LiteralKind::CStr { .. } | LiteralKind::RawCStr { .. }),
254                    suffix_start: _,
255                } if !self.mk_sp(start, self.pos).edition().at_least_rust_2021() => {
256                    let prefix_len = match kind {
257                        LiteralKind::CStr { .. } => 1,
258                        LiteralKind::RawCStr { .. } => 2,
259                        _ => unreachable!(),
260                    };
261
262                    // reset the state so that only the prefix ("c" or "cr")
263                    // was consumed.
264                    let lit_start = start + BytePos(prefix_len);
265                    self.pos = lit_start;
266                    self.cursor = Cursor::new(&str_before[prefix_len as usize..], FrontmatterAllowed::No);
267                    self.report_unknown_prefix(start);
268                    let prefix_span = self.mk_sp(start, lit_start);
269                    return (Token::new(self.ident(start), prefix_span), preceded_by_whitespace);
270                }
271                rustc_lexer::TokenKind::GuardedStrPrefix => {
272                    self.maybe_report_guarded_str(start, str_before)
273                }
274                rustc_lexer::TokenKind::Literal { kind, suffix_start } => {
275                    let suffix_start = start + BytePos(suffix_start);
276                    let (kind, symbol) = self.cook_lexer_literal(start, suffix_start, kind);
277                    let suffix = if suffix_start < self.pos {
278                        let string = self.str_from(suffix_start);
279                        if string == "_" {
280                            self.dcx().emit_err(errors::UnderscoreLiteralSuffix {
281                                span: self.mk_sp(suffix_start, self.pos),
282                            });
283                            None
284                        } else {
285                            Some(Symbol::intern(string))
286                        }
287                    } else {
288                        None
289                    };
290                    token::Literal(token::Lit { kind, symbol, suffix })
291                }
292                rustc_lexer::TokenKind::Lifetime { starts_with_number } => {
293                    // Include the leading `'` in the real identifier, for macro
294                    // expansion purposes. See #12512 for the gory details of why
295                    // this is necessary.
296                    let lifetime_name = self.str_from(start);
297                    self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
298                    if starts_with_number {
299                        let span = self.mk_sp(start, self.pos);
300                        self.dcx()
301                            .struct_err("lifetimes cannot start with a number")
302                            .with_span(span)
303                            .stash(span, StashKey::LifetimeIsChar);
304                    }
305                    let ident = Symbol::intern(lifetime_name);
306                    token::Lifetime(ident, IdentIsRaw::No)
307                }
308                rustc_lexer::TokenKind::RawLifetime => {
309                    self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
310
311                    let ident_start = start + BytePos(3);
312                    let prefix_span = self.mk_sp(start, ident_start);
313
314                    if prefix_span.at_least_rust_2021() {
315                        // If the raw lifetime is followed by \' then treat it a normal
316                        // lifetime followed by a \', which is to interpret it as a character
317                        // literal. In this case, it's always an invalid character literal
318                        // since the literal must necessarily have >3 characters (r#...) inside
319                        // of it, which is invalid.
320                        if self.cursor.as_str().starts_with('\'') {
321                            let lit_span = self.mk_sp(start, self.pos + BytePos(1));
322                            let contents = self.str_from_to(start + BytePos(1), self.pos);
323                            emit_unescape_error(
324                                self.dcx(),
325                                contents,
326                                lit_span,
327                                lit_span,
328                                Mode::Char,
329                                0..contents.len(),
330                                EscapeError::MoreThanOneChar,
331                            )
332                            .expect("expected error");
333                        }
334
335                        let span = self.mk_sp(start, self.pos);
336
337                        let lifetime_name_without_tick =
338                            Symbol::intern(&self.str_from(ident_start));
339                        if !lifetime_name_without_tick.can_be_raw() {
340                            self.dcx().emit_err(
341                                errors::CannotBeRawLifetime {
342                                    span,
343                                    ident: lifetime_name_without_tick
344                                }
345                            );
346                        }
347
348                        // Put the `'` back onto the lifetime name.
349                        let mut lifetime_name =
350                            String::with_capacity(lifetime_name_without_tick.as_str().len() + 1);
351                        lifetime_name.push('\'');
352                        lifetime_name += lifetime_name_without_tick.as_str();
353                        let sym = Symbol::intern(&lifetime_name);
354
355                        // Make sure we mark this as a raw identifier.
356                        self.psess.raw_identifier_spans.push(span);
357
358                        token::Lifetime(sym, IdentIsRaw::Yes)
359                    } else {
360                        // Otherwise, this should be parsed like `'r`. Warn about it though.
361                        self.psess.buffer_lint(
362                            RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
363                            prefix_span,
364                            ast::CRATE_NODE_ID,
365                            BuiltinLintDiag::RawPrefix(prefix_span),
366                        );
367
368                        // Reset the state so we just lex the `'r`.
369                        let lt_start = start + BytePos(2);
370                        self.pos = lt_start;
371                        self.cursor = Cursor::new(&str_before[2 as usize..], FrontmatterAllowed::No);
372
373                        let lifetime_name = self.str_from(start);
374                        let ident = Symbol::intern(lifetime_name);
375                        token::Lifetime(ident, IdentIsRaw::No)
376                    }
377                }
378                rustc_lexer::TokenKind::Semi => token::Semi,
379                rustc_lexer::TokenKind::Comma => token::Comma,
380                rustc_lexer::TokenKind::Dot => token::Dot,
381                rustc_lexer::TokenKind::OpenParen => token::OpenParen,
382                rustc_lexer::TokenKind::CloseParen => token::CloseParen,
383                rustc_lexer::TokenKind::OpenBrace => token::OpenBrace,
384                rustc_lexer::TokenKind::CloseBrace => token::CloseBrace,
385                rustc_lexer::TokenKind::OpenBracket => token::OpenBracket,
386                rustc_lexer::TokenKind::CloseBracket => token::CloseBracket,
387                rustc_lexer::TokenKind::At => token::At,
388                rustc_lexer::TokenKind::Pound => token::Pound,
389                rustc_lexer::TokenKind::Tilde => token::Tilde,
390                rustc_lexer::TokenKind::Question => token::Question,
391                rustc_lexer::TokenKind::Colon => token::Colon,
392                rustc_lexer::TokenKind::Dollar => token::Dollar,
393                rustc_lexer::TokenKind::Eq => token::Eq,
394                rustc_lexer::TokenKind::Bang => token::Bang,
395                rustc_lexer::TokenKind::Lt => token::Lt,
396                rustc_lexer::TokenKind::Gt => token::Gt,
397                rustc_lexer::TokenKind::Minus => token::Minus,
398                rustc_lexer::TokenKind::And => token::And,
399                rustc_lexer::TokenKind::Or => token::Or,
400                rustc_lexer::TokenKind::Plus => token::Plus,
401                rustc_lexer::TokenKind::Star => token::Star,
402                rustc_lexer::TokenKind::Slash => token::Slash,
403                rustc_lexer::TokenKind::Caret => token::Caret,
404                rustc_lexer::TokenKind::Percent => token::Percent,
405
406                rustc_lexer::TokenKind::Unknown | rustc_lexer::TokenKind::InvalidIdent => {
407                    // Don't emit diagnostics for sequences of the same invalid token
408                    if swallow_next_invalid > 0 {
409                        swallow_next_invalid -= 1;
410                        continue;
411                    }
412                    let mut it = self.str_from_to_end(start).chars();
413                    let c = it.next().unwrap();
414                    if c == '\u{00a0}' {
415                        // If an error has already been reported on non-breaking
416                        // space characters earlier in the file, treat all
417                        // subsequent occurrences as whitespace.
418                        if self.nbsp_is_whitespace {
419                            preceded_by_whitespace = true;
420                            continue;
421                        }
422                        self.nbsp_is_whitespace = true;
423                    }
424                    let repeats = it.take_while(|c1| *c1 == c).count();
425                    // FIXME: the lexer could be used to turn the ASCII version of unicode
426                    // homoglyphs, instead of keeping a table in `check_for_substitution`into the
427                    // token. Ideally, this should be inside `rustc_lexer`. However, we should
428                    // first remove compound tokens like `<<` from `rustc_lexer`, and then add
429                    // fancier error recovery to it, as there will be less overall work to do this
430                    // way.
431                    let (token, sugg) =
432                        unicode_chars::check_for_substitution(self, start, c, repeats + 1);
433                    self.dcx().emit_err(errors::UnknownTokenStart {
434                        span: self.mk_sp(start, self.pos + Pos::from_usize(repeats * c.len_utf8())),
435                        escaped: escaped_char(c),
436                        sugg,
437                        null: if c == '\x00' { Some(errors::UnknownTokenNull) } else { None },
438                        repeat: if repeats > 0 {
439                            swallow_next_invalid = repeats;
440                            Some(errors::UnknownTokenRepeat { repeats })
441                        } else {
442                            None
443                        },
444                    });
445
446                    if let Some(token) = token {
447                        token
448                    } else {
449                        preceded_by_whitespace = true;
450                        continue;
451                    }
452                }
453                rustc_lexer::TokenKind::Eof => token::Eof,
454            };
455            let span = self.mk_sp(start, self.pos);
456            return (Token::new(kind, span), preceded_by_whitespace);
457        }
458    }
459
460    fn ident(&self, start: BytePos) -> TokenKind {
461        let sym = nfc_normalize(self.str_from(start));
462        let span = self.mk_sp(start, self.pos);
463        self.psess.symbol_gallery.insert(sym, span);
464        token::Ident(sym, IdentIsRaw::No)
465    }
466
467    /// Detect usages of Unicode codepoints changing the direction of the text on screen and loudly
468    /// complain about it.
469    fn lint_unicode_text_flow(&self, start: BytePos) {
470        // Opening delimiter of the length 2 is not included into the comment text.
471        let content_start = start + BytePos(2);
472        let content = self.str_from(content_start);
473        if contains_text_flow_control_chars(content) {
474            let span = self.mk_sp(start, self.pos);
475            self.psess.buffer_lint(
476                TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
477                span,
478                ast::CRATE_NODE_ID,
479                BuiltinLintDiag::UnicodeTextFlow(span, content.to_string()),
480            );
481        }
482    }
483
484    fn validate_frontmatter(
485        &self,
486        start: BytePos,
487        has_invalid_preceding_whitespace: bool,
488        invalid_infostring: bool,
489    ) {
490        let s = self.str_from(start);
491        let real_start = s.find("---").unwrap();
492        let frontmatter_opening_pos = BytePos(real_start as u32) + start;
493        let s_new = &s[real_start..];
494        let within = s_new.trim_start_matches('-');
495        let len_opening = s_new.len() - within.len();
496
497        let frontmatter_opening_end_pos = frontmatter_opening_pos + BytePos(len_opening as u32);
498        if has_invalid_preceding_whitespace {
499            let line_start =
500                BytePos(s[..real_start].rfind("\n").map_or(0, |i| i as u32 + 1)) + start;
501            let span = self.mk_sp(line_start, frontmatter_opening_end_pos);
502            let label_span = self.mk_sp(line_start, frontmatter_opening_pos);
503            self.dcx().emit_err(errors::FrontmatterInvalidOpeningPrecedingWhitespace {
504                span,
505                note_span: label_span,
506            });
507        }
508
509        if invalid_infostring {
510            let line_end = s[real_start..].find('\n').unwrap_or(s[real_start..].len());
511            let span = self.mk_sp(
512                frontmatter_opening_end_pos,
513                frontmatter_opening_pos + BytePos(line_end as u32),
514            );
515            self.dcx().emit_err(errors::FrontmatterInvalidInfostring { span });
516        }
517
518        let last_line_start = within.rfind('\n').map_or(0, |i| i + 1);
519        let last_line = &within[last_line_start..];
520        let last_line_trimmed = last_line.trim_start_matches(is_whitespace);
521        let last_line_start_pos = frontmatter_opening_end_pos + BytePos(last_line_start as u32);
522
523        let frontmatter_span = self.mk_sp(frontmatter_opening_pos, self.pos);
524        self.psess.gated_spans.gate(sym::frontmatter, frontmatter_span);
525
526        if !last_line_trimmed.starts_with("---") {
527            let label_span = self.mk_sp(frontmatter_opening_pos, frontmatter_opening_end_pos);
528            self.dcx().emit_err(errors::FrontmatterUnclosed {
529                span: frontmatter_span,
530                note_span: label_span,
531            });
532            return;
533        }
534
535        if last_line_trimmed.len() != last_line.len() {
536            let line_end = last_line_start_pos + BytePos(last_line.len() as u32);
537            let span = self.mk_sp(last_line_start_pos, line_end);
538            let whitespace_end =
539                last_line_start_pos + BytePos((last_line.len() - last_line_trimmed.len()) as u32);
540            let label_span = self.mk_sp(last_line_start_pos, whitespace_end);
541            self.dcx().emit_err(errors::FrontmatterInvalidClosingPrecedingWhitespace {
542                span,
543                note_span: label_span,
544            });
545        }
546
547        let rest = last_line_trimmed.trim_start_matches('-');
548        let len_close = last_line_trimmed.len() - rest.len();
549        if len_close != len_opening {
550            let span = self.mk_sp(frontmatter_opening_pos, self.pos);
551            let opening = self.mk_sp(frontmatter_opening_pos, frontmatter_opening_end_pos);
552            let last_line_close_pos = last_line_start_pos + BytePos(len_close as u32);
553            let close = self.mk_sp(last_line_start_pos, last_line_close_pos);
554            self.dcx().emit_err(errors::FrontmatterLengthMismatch {
555                span,
556                opening,
557                close,
558                len_opening,
559                len_close,
560            });
561        }
562
563        if !rest.trim_matches(is_whitespace).is_empty() {
564            let span = self.mk_sp(last_line_start_pos, self.pos);
565            self.dcx().emit_err(errors::FrontmatterExtraCharactersAfterClose { span });
566        }
567    }
568
569    fn cook_doc_comment(
570        &self,
571        content_start: BytePos,
572        content: &str,
573        comment_kind: CommentKind,
574        doc_style: DocStyle,
575    ) -> TokenKind {
576        if content.contains('\r') {
577            for (idx, _) in content.char_indices().filter(|&(_, c)| c == '\r') {
578                let span = self.mk_sp(
579                    content_start + BytePos(idx as u32),
580                    content_start + BytePos(idx as u32 + 1),
581                );
582                let block = matches!(comment_kind, CommentKind::Block);
583                self.dcx().emit_err(errors::CrDocComment { span, block });
584            }
585        }
586
587        let attr_style = match doc_style {
588            DocStyle::Outer => AttrStyle::Outer,
589            DocStyle::Inner => AttrStyle::Inner,
590        };
591
592        token::DocComment(comment_kind, attr_style, Symbol::intern(content))
593    }
594
595    fn cook_lexer_literal(
596        &self,
597        start: BytePos,
598        end: BytePos,
599        kind: rustc_lexer::LiteralKind,
600    ) -> (token::LitKind, Symbol) {
601        match kind {
602            rustc_lexer::LiteralKind::Char { terminated } => {
603                if !terminated {
604                    let mut err = self
605                        .dcx()
606                        .struct_span_fatal(self.mk_sp(start, end), "unterminated character literal")
607                        .with_code(E0762);
608                    if let Some(lt_sp) = self.last_lifetime {
609                        err.multipart_suggestion(
610                            "if you meant to write a string literal, use double quotes",
611                            vec![
612                                (lt_sp, "\"".to_string()),
613                                (self.mk_sp(start, start + BytePos(1)), "\"".to_string()),
614                            ],
615                            Applicability::MaybeIncorrect,
616                        );
617                    }
618                    err.emit()
619                }
620                self.cook_unicode(token::Char, Mode::Char, start, end, 1, 1) // ' '
621            }
622            rustc_lexer::LiteralKind::Byte { terminated } => {
623                if !terminated {
624                    self.dcx()
625                        .struct_span_fatal(
626                            self.mk_sp(start + BytePos(1), end),
627                            "unterminated byte constant",
628                        )
629                        .with_code(E0763)
630                        .emit()
631                }
632                self.cook_unicode(token::Byte, Mode::Byte, start, end, 2, 1) // b' '
633            }
634            rustc_lexer::LiteralKind::Str { terminated } => {
635                if !terminated {
636                    self.dcx()
637                        .struct_span_fatal(
638                            self.mk_sp(start, end),
639                            "unterminated double quote string",
640                        )
641                        .with_code(E0765)
642                        .emit()
643                }
644                self.cook_unicode(token::Str, Mode::Str, start, end, 1, 1) // " "
645            }
646            rustc_lexer::LiteralKind::ByteStr { terminated } => {
647                if !terminated {
648                    self.dcx()
649                        .struct_span_fatal(
650                            self.mk_sp(start + BytePos(1), end),
651                            "unterminated double quote byte string",
652                        )
653                        .with_code(E0766)
654                        .emit()
655                }
656                self.cook_unicode(token::ByteStr, Mode::ByteStr, start, end, 2, 1) // b" "
657            }
658            rustc_lexer::LiteralKind::CStr { terminated } => {
659                if !terminated {
660                    self.dcx()
661                        .struct_span_fatal(
662                            self.mk_sp(start + BytePos(1), end),
663                            "unterminated C string",
664                        )
665                        .with_code(E0767)
666                        .emit()
667                }
668                self.cook_mixed(token::CStr, Mode::CStr, start, end, 2, 1) // c" "
669            }
670            rustc_lexer::LiteralKind::RawStr { n_hashes } => {
671                if let Some(n_hashes) = n_hashes {
672                    let n = u32::from(n_hashes);
673                    let kind = token::StrRaw(n_hashes);
674                    self.cook_unicode(kind, Mode::RawStr, start, end, 2 + n, 1 + n) // r##" "##
675                } else {
676                    self.report_raw_str_error(start, 1);
677                }
678            }
679            rustc_lexer::LiteralKind::RawByteStr { n_hashes } => {
680                if let Some(n_hashes) = n_hashes {
681                    let n = u32::from(n_hashes);
682                    let kind = token::ByteStrRaw(n_hashes);
683                    self.cook_unicode(kind, Mode::RawByteStr, start, end, 3 + n, 1 + n) // br##" "##
684                } else {
685                    self.report_raw_str_error(start, 2);
686                }
687            }
688            rustc_lexer::LiteralKind::RawCStr { n_hashes } => {
689                if let Some(n_hashes) = n_hashes {
690                    let n = u32::from(n_hashes);
691                    let kind = token::CStrRaw(n_hashes);
692                    self.cook_unicode(kind, Mode::RawCStr, start, end, 3 + n, 1 + n) // cr##" "##
693                } else {
694                    self.report_raw_str_error(start, 2);
695                }
696            }
697            rustc_lexer::LiteralKind::Int { base, empty_int } => {
698                let mut kind = token::Integer;
699                if empty_int {
700                    let span = self.mk_sp(start, end);
701                    let guar = self.dcx().emit_err(errors::NoDigitsLiteral { span });
702                    kind = token::Err(guar);
703                } else if matches!(base, Base::Binary | Base::Octal) {
704                    let base = base as u32;
705                    let s = self.str_from_to(start + BytePos(2), end);
706                    for (idx, c) in s.char_indices() {
707                        let span = self.mk_sp(
708                            start + BytePos::from_usize(2 + idx),
709                            start + BytePos::from_usize(2 + idx + c.len_utf8()),
710                        );
711                        if c != '_' && c.to_digit(base).is_none() {
712                            let guar =
713                                self.dcx().emit_err(errors::InvalidDigitLiteral { span, base });
714                            kind = token::Err(guar);
715                        }
716                    }
717                }
718                (kind, self.symbol_from_to(start, end))
719            }
720            rustc_lexer::LiteralKind::Float { base, empty_exponent } => {
721                let mut kind = token::Float;
722                if empty_exponent {
723                    let span = self.mk_sp(start, self.pos);
724                    let guar = self.dcx().emit_err(errors::EmptyExponentFloat { span });
725                    kind = token::Err(guar);
726                }
727                let base = match base {
728                    Base::Hexadecimal => Some("hexadecimal"),
729                    Base::Octal => Some("octal"),
730                    Base::Binary => Some("binary"),
731                    _ => None,
732                };
733                if let Some(base) = base {
734                    let span = self.mk_sp(start, end);
735                    let guar =
736                        self.dcx().emit_err(errors::FloatLiteralUnsupportedBase { span, base });
737                    kind = token::Err(guar)
738                }
739                (kind, self.symbol_from_to(start, end))
740            }
741        }
742    }
743
744    #[inline]
745    fn src_index(&self, pos: BytePos) -> usize {
746        (pos - self.start_pos).to_usize()
747    }
748
749    /// Slice of the source text from `start` up to but excluding `self.pos`,
750    /// meaning the slice does not include the character `self.ch`.
751    fn str_from(&self, start: BytePos) -> &'src str {
752        self.str_from_to(start, self.pos)
753    }
754
755    /// As symbol_from, with an explicit endpoint.
756    fn symbol_from_to(&self, start: BytePos, end: BytePos) -> Symbol {
757        debug!("taking an ident from {:?} to {:?}", start, end);
758        Symbol::intern(self.str_from_to(start, end))
759    }
760
761    /// Slice of the source text spanning from `start` up to but excluding `end`.
762    fn str_from_to(&self, start: BytePos, end: BytePos) -> &'src str {
763        &self.src[self.src_index(start)..self.src_index(end)]
764    }
765
766    /// Slice of the source text spanning from `start` until the end
767    fn str_from_to_end(&self, start: BytePos) -> &'src str {
768        &self.src[self.src_index(start)..]
769    }
770
771    fn report_raw_str_error(&self, start: BytePos, prefix_len: u32) -> ! {
772        match rustc_lexer::validate_raw_str(self.str_from(start), prefix_len) {
773            Err(RawStrError::InvalidStarter { bad_char }) => {
774                self.report_non_started_raw_string(start, bad_char)
775            }
776            Err(RawStrError::NoTerminator { expected, found, possible_terminator_offset }) => self
777                .report_unterminated_raw_string(start, expected, possible_terminator_offset, found),
778            Err(RawStrError::TooManyDelimiters { found }) => {
779                self.report_too_many_hashes(start, found)
780            }
781            Ok(()) => panic!("no error found for supposedly invalid raw string literal"),
782        }
783    }
784
785    fn report_non_started_raw_string(&self, start: BytePos, bad_char: char) -> ! {
786        self.dcx()
787            .struct_span_fatal(
788                self.mk_sp(start, self.pos),
789                format!(
790                    "found invalid character; only `#` is allowed in raw string delimitation: {}",
791                    escaped_char(bad_char)
792                ),
793            )
794            .emit()
795    }
796
797    fn report_unterminated_raw_string(
798        &self,
799        start: BytePos,
800        n_hashes: u32,
801        possible_offset: Option<u32>,
802        found_terminators: u32,
803    ) -> ! {
804        let mut err =
805            self.dcx().struct_span_fatal(self.mk_sp(start, start), "unterminated raw string");
806        err.code(E0748);
807        err.span_label(self.mk_sp(start, start), "unterminated raw string");
808
809        if n_hashes > 0 {
810            err.note(format!(
811                "this raw string should be terminated with `\"{}`",
812                "#".repeat(n_hashes as usize)
813            ));
814        }
815
816        if let Some(possible_offset) = possible_offset {
817            let lo = start + BytePos(possible_offset);
818            let hi = lo + BytePos(found_terminators);
819            let span = self.mk_sp(lo, hi);
820            err.span_suggestion(
821                span,
822                "consider terminating the string here",
823                "#".repeat(n_hashes as usize),
824                Applicability::MaybeIncorrect,
825            );
826        }
827
828        err.emit()
829    }
830
831    fn report_unterminated_block_comment(&self, start: BytePos, doc_style: Option<DocStyle>) {
832        let msg = match doc_style {
833            Some(_) => "unterminated block doc-comment",
834            None => "unterminated block comment",
835        };
836        let last_bpos = self.pos;
837        let mut err = self.dcx().struct_span_fatal(self.mk_sp(start, last_bpos), msg);
838        err.code(E0758);
839        let mut nested_block_comment_open_idxs = vec![];
840        let mut last_nested_block_comment_idxs = None;
841        let mut content_chars = self.str_from(start).char_indices().peekable();
842
843        while let Some((idx, current_char)) = content_chars.next() {
844            match content_chars.peek() {
845                Some((_, '*')) if current_char == '/' => {
846                    nested_block_comment_open_idxs.push(idx);
847                }
848                Some((_, '/')) if current_char == '*' => {
849                    last_nested_block_comment_idxs =
850                        nested_block_comment_open_idxs.pop().map(|open_idx| (open_idx, idx));
851                }
852                _ => {}
853            };
854        }
855
856        if let Some((nested_open_idx, nested_close_idx)) = last_nested_block_comment_idxs {
857            err.span_label(self.mk_sp(start, start + BytePos(2)), msg)
858                .span_label(
859                    self.mk_sp(
860                        start + BytePos(nested_open_idx as u32),
861                        start + BytePos(nested_open_idx as u32 + 2),
862                    ),
863                    "...as last nested comment starts here, maybe you want to close this instead?",
864                )
865                .span_label(
866                    self.mk_sp(
867                        start + BytePos(nested_close_idx as u32),
868                        start + BytePos(nested_close_idx as u32 + 2),
869                    ),
870                    "...and last nested comment terminates here.",
871                );
872        }
873
874        err.emit();
875    }
876
877    // RFC 3101 introduced the idea of (reserved) prefixes. As of Rust 2021,
878    // using a (unknown) prefix is an error. In earlier editions, however, they
879    // only result in a (allowed by default) lint, and are treated as regular
880    // identifier tokens.
881    fn report_unknown_prefix(&self, start: BytePos) {
882        let prefix_span = self.mk_sp(start, self.pos);
883        let prefix = self.str_from_to(start, self.pos);
884        let expn_data = prefix_span.ctxt().outer_expn_data();
885
886        if expn_data.edition.at_least_rust_2021() {
887            // In Rust 2021, this is a hard error.
888            let sugg = if prefix == "rb" {
889                Some(errors::UnknownPrefixSugg::UseBr(prefix_span))
890            } else if prefix == "rc" {
891                Some(errors::UnknownPrefixSugg::UseCr(prefix_span))
892            } else if expn_data.is_root() {
893                if self.cursor.first() == '\''
894                    && let Some(start) = self.last_lifetime
895                    && self.cursor.third() != '\''
896                    && let end = self.mk_sp(self.pos, self.pos + BytePos(1))
897                    && !self.psess.source_map().is_multiline(start.until(end))
898                {
899                    // FIXME: An "unclosed `char`" error will be emitted already in some cases,
900                    // but it's hard to silence this error while not also silencing important cases
901                    // too. We should use the error stashing machinery instead.
902                    Some(errors::UnknownPrefixSugg::MeantStr { start, end })
903                } else {
904                    Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi()))
905                }
906            } else {
907                None
908            };
909            self.dcx().emit_err(errors::UnknownPrefix { span: prefix_span, prefix, sugg });
910        } else {
911            // Before Rust 2021, only emit a lint for migration.
912            self.psess.buffer_lint(
913                RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
914                prefix_span,
915                ast::CRATE_NODE_ID,
916                BuiltinLintDiag::ReservedPrefix(prefix_span, prefix.to_string()),
917            );
918        }
919    }
920
921    /// Detect guarded string literal syntax
922    ///
923    /// RFC 3593 reserved this syntax for future use. As of Rust 2024,
924    /// using this syntax produces an error. In earlier editions, however, it
925    /// only results in an (allowed by default) lint, and is treated as
926    /// separate tokens.
927    fn maybe_report_guarded_str(&mut self, start: BytePos, str_before: &'src str) -> TokenKind {
928        let span = self.mk_sp(start, self.pos);
929        let edition2024 = span.edition().at_least_rust_2024();
930
931        let space_pos = start + BytePos(1);
932        let space_span = self.mk_sp(space_pos, space_pos);
933
934        let mut cursor = Cursor::new(str_before, FrontmatterAllowed::No);
935
936        let (is_string, span, unterminated) = match cursor.guarded_double_quoted_string() {
937            Some(rustc_lexer::GuardedStr { n_hashes, terminated, token_len }) => {
938                let end = start + BytePos(token_len);
939                let span = self.mk_sp(start, end);
940                let str_start = start + BytePos(n_hashes);
941
942                if edition2024 {
943                    self.cursor = cursor;
944                    self.pos = end;
945                }
946
947                let unterminated = if terminated { None } else { Some(str_start) };
948
949                (true, span, unterminated)
950            }
951            None => {
952                // We should only get here in the `##+` case.
953                debug_assert_eq!(self.str_from_to(start, start + BytePos(2)), "##");
954
955                (false, span, None)
956            }
957        };
958        if edition2024 {
959            if let Some(str_start) = unterminated {
960                // Only a fatal error if string is unterminated.
961                self.dcx()
962                    .struct_span_fatal(
963                        self.mk_sp(str_start, self.pos),
964                        "unterminated double quote string",
965                    )
966                    .with_code(E0765)
967                    .emit()
968            }
969
970            let sugg = if span.from_expansion() {
971                None
972            } else {
973                Some(errors::GuardedStringSugg(space_span))
974            };
975
976            // In Edition 2024 and later, emit a hard error.
977            let err = if is_string {
978                self.dcx().emit_err(errors::ReservedString { span, sugg })
979            } else {
980                self.dcx().emit_err(errors::ReservedMultihash { span, sugg })
981            };
982
983            token::Literal(token::Lit {
984                kind: token::Err(err),
985                symbol: self.symbol_from_to(start, self.pos),
986                suffix: None,
987            })
988        } else {
989            // Before Rust 2024, only emit a lint for migration.
990            self.psess.buffer_lint(
991                RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX,
992                span,
993                ast::CRATE_NODE_ID,
994                BuiltinLintDiag::ReservedString { is_string, suggestion: space_span },
995            );
996
997            // For backwards compatibility, roll back to after just the first `#`
998            // and return the `Pound` token.
999            self.pos = start + BytePos(1);
1000            self.cursor = Cursor::new(&str_before[1..], FrontmatterAllowed::No);
1001            token::Pound
1002        }
1003    }
1004
1005    fn report_too_many_hashes(&self, start: BytePos, num: u32) -> ! {
1006        self.dcx().emit_fatal(errors::TooManyHashes { span: self.mk_sp(start, self.pos), num });
1007    }
1008
1009    fn cook_common(
1010        &self,
1011        mut kind: token::LitKind,
1012        mode: Mode,
1013        start: BytePos,
1014        end: BytePos,
1015        prefix_len: u32,
1016        postfix_len: u32,
1017        unescape: fn(&str, Mode, &mut dyn FnMut(Range<usize>, Result<(), EscapeError>)),
1018    ) -> (token::LitKind, Symbol) {
1019        let content_start = start + BytePos(prefix_len);
1020        let content_end = end - BytePos(postfix_len);
1021        let lit_content = self.str_from_to(content_start, content_end);
1022        unescape(lit_content, mode, &mut |range, result| {
1023            // Here we only check for errors. The actual unescaping is done later.
1024            if let Err(err) = result {
1025                let span_with_quotes = self.mk_sp(start, end);
1026                let (start, end) = (range.start as u32, range.end as u32);
1027                let lo = content_start + BytePos(start);
1028                let hi = lo + BytePos(end - start);
1029                let span = self.mk_sp(lo, hi);
1030                let is_fatal = err.is_fatal();
1031                if let Some(guar) = emit_unescape_error(
1032                    self.dcx(),
1033                    lit_content,
1034                    span_with_quotes,
1035                    span,
1036                    mode,
1037                    range,
1038                    err,
1039                ) {
1040                    assert!(is_fatal);
1041                    kind = token::Err(guar);
1042                }
1043            }
1044        });
1045
1046        // We normally exclude the quotes for the symbol, but for errors we
1047        // include it because it results in clearer error messages.
1048        let sym = if !matches!(kind, token::Err(_)) {
1049            Symbol::intern(lit_content)
1050        } else {
1051            self.symbol_from_to(start, end)
1052        };
1053        (kind, sym)
1054    }
1055
1056    fn cook_unicode(
1057        &self,
1058        kind: token::LitKind,
1059        mode: Mode,
1060        start: BytePos,
1061        end: BytePos,
1062        prefix_len: u32,
1063        postfix_len: u32,
1064    ) -> (token::LitKind, Symbol) {
1065        self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
1066            unescape_unicode(src, mode, &mut |span, result| callback(span, result.map(drop)))
1067        })
1068    }
1069
1070    fn cook_mixed(
1071        &self,
1072        kind: token::LitKind,
1073        mode: Mode,
1074        start: BytePos,
1075        end: BytePos,
1076        prefix_len: u32,
1077        postfix_len: u32,
1078    ) -> (token::LitKind, Symbol) {
1079        self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
1080            unescape_mixed(src, mode, &mut |span, result| callback(span, result.map(drop)))
1081        })
1082    }
1083}
1084
1085pub fn nfc_normalize(string: &str) -> Symbol {
1086    use unicode_normalization::{IsNormalized, UnicodeNormalization, is_nfc_quick};
1087    match is_nfc_quick(string.chars()) {
1088        IsNormalized::Yes => Symbol::intern(string),
1089        _ => {
1090            let normalized_str: String = string.chars().nfc().collect();
1091            Symbol::intern(&normalized_str)
1092        }
1093    }
1094}