Skip to main content

macroonz_compiler/token/capture/text/
read.rs

1//! The callable text route into the normalized captured tree.
2//!
3//! `ra-ap-rustc_lexer` owns low-level Rust token boundaries and lengths.
4//! This file slices the original declared source by those byte lengths, translates every lexer row into this crate's typed capture vocabulary, and sends every retained token through the checked builder.
5//! Text never synthesizes an invisible group; only a compiler producer can carry that distinction.
6
7use super::{
8    CaptureBound, CaptureBuildRefusal, CaptureBuilder, CaptureLevel, CapturedAtom,
9    CapturedDelimiter, CapturedInput, LiteralReadCause, SpanHandle, SpanTable,
10    TEXT_SOURCE_BYTE_LIMIT, TextCapture, TextLexicalCause, TextReadCause, TextReadRefusal,
11    TokenPath, capture_literal,
12};
13use crate::bounded::Bounded;
14use crate::token::bank::raw_identifier_is_reserved;
15use ra_ap_rustc_lexer::{DocStyle, FrontmatterAllowed, LiteralKind, TokenKind, tokenize};
16
17impl TextCapture {
18    /// Read one declared Rust token input from source text.
19    ///
20    /// # Errors
21    ///
22    /// Returns [`TextReadRefusal`] naming the established cause and source byte.
23    /// The source-byte magnitude is checked before tokenization and is independent of every structural capture magnitude.
24    pub fn read(source: &str) -> Result<Self, TextReadRefusal> {
25        if source.len() > TEXT_SOURCE_BYTE_LIMIT {
26            return Err(TextReadRefusal {
27                cause: TextReadCause::SourceBytesUnbounded,
28                at: source_offset(TEXT_SOURCE_BYTE_LIMIT),
29            });
30        }
31        let lexemes = lex(source)?;
32        let mut builder = CaptureBuilder::declared();
33        let mut cursor = 0;
34        let level = builder.open();
35        let level = capture_sequence(&lexemes, &mut cursor, None, level)
36            .map_err(|refusal| text_refusal(&refusal))?;
37        let input = level.finish();
38        let offsets = Bounded::new(builder.positions().to_vec()).map_err(|_| TextReadRefusal {
39            cause: TextReadCause::Unbounded(CaptureBound::Tree),
40            at: source_offset(source.len()),
41        })?;
42        Ok(Self {
43            input,
44            spans: SpanTable::ByteOffsets(offsets),
45        })
46    }
47
48    /// The normalized captured input shared with the compiler-token producer.
49    #[must_use]
50    pub const fn input(&self) -> &CapturedInput {
51        &self.input
52    }
53
54    /// The byte-offset table that resolves this read's handles.
55    #[must_use]
56    pub const fn spans(&self) -> &SpanTable {
57        &self.spans
58    }
59}
60
61/// One low-level token together with its exact original source slice.
62#[derive(Debug, Clone, Copy)]
63struct Lexeme<'source> {
64    kind: TokenKind,
65    spelling: &'source str,
66    at: u64,
67}
68
69/// One delimiter expected by an open checked-builder group.
70struct Closing {
71    kind: ClosingKind,
72    at: u64,
73    path: TokenPath,
74    span: SpanHandle,
75}
76
77/// The written delimiter that closes an open group.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79enum ClosingKind {
80    Parenthesis,
81    Brace,
82    Bracket,
83}
84
85/// Whether one retained lifetime identifier was written in its ordinary or raw form.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87enum LifetimeForm {
88    Ordinary,
89    Raw,
90}
91
92/// Whether one doc comment occupies a line or block spelling.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94enum DocForm {
95    Line,
96    Block,
97}
98
99/// Slice the declared source by the exact byte lengths produced by the low-level lexer.
100fn lex(source: &str) -> Result<Vec<Lexeme<'_>>, TextReadRefusal> {
101    let mut lexemes = Vec::new();
102    let mut offset = 0usize;
103    for token in tokenize(source, FrontmatterAllowed::No) {
104        let length = usize::try_from(token.len).map_err(|_| TextReadRefusal {
105            cause: TextReadCause::SourceBytesUnbounded,
106            at: source_offset(offset),
107        })?;
108        let end = offset.checked_add(length).ok_or(TextReadRefusal {
109            cause: TextReadCause::SourceBytesUnbounded,
110            at: source_offset(offset),
111        })?;
112        let spelling = source.get(offset..end).ok_or(TextReadRefusal {
113            cause: TextReadCause::Lexical(TextLexicalCause::UnknownToken),
114            at: source_offset(offset),
115        })?;
116        lexemes.push(Lexeme {
117            kind: token.kind,
118            spelling,
119            at: source_offset(offset),
120        });
121        offset = end;
122    }
123    if offset != source.len() {
124        return Err(TextReadRefusal {
125            cause: TextReadCause::Lexical(TextLexicalCause::UnknownToken),
126            at: source_offset(offset),
127        });
128    }
129    Ok(lexemes)
130}
131
132/// Capture one root or delimited sequence.
133fn capture_sequence<'capture>(
134    lexemes: &[Lexeme<'_>],
135    cursor: &mut usize,
136    closing: Option<Closing>,
137    mut level: CaptureLevel<'capture, u64>,
138) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
139    loop {
140        let Some(lexeme) = lexemes.get(*cursor) else {
141            return match closing {
142                Some(held) => Err(CaptureBuildRefusal::ProducerRefused {
143                    cause: refusal(TextReadCause::NotBalanced, held.at),
144                    path: held.path,
145                    at: held.span,
146                }),
147                None => Ok(level),
148            };
149        };
150        if closes(lexeme.kind, closing.as_ref()) {
151            advance(cursor);
152            return Ok(level);
153        }
154        if is_closing(lexeme.kind) {
155            return refuse(level, lexeme.at, TextReadCause::NotOpened);
156        }
157        if let Some((delimiter, expected)) = opening(lexeme.kind) {
158            advance(cursor);
159            level = level.group(lexeme.at, delimiter, |span, inner| {
160                let path = inner.path().clone();
161                capture_sequence(
162                    lexemes,
163                    cursor,
164                    Some(Closing {
165                        kind: expected,
166                        at: lexeme.at,
167                        path,
168                        span,
169                    }),
170                    inner,
171                )
172            })?;
173            continue;
174        }
175        level = capture_lexeme(lexemes, cursor, level)?;
176    }
177}
178
179/// Translate one non-delimiter lexeme through the checked capture builder.
180fn capture_lexeme<'capture>(
181    lexemes: &[Lexeme<'_>],
182    cursor: &mut usize,
183    level: CaptureLevel<'capture, u64>,
184) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
185    let Some(lexeme) = lexemes.get(*cursor) else {
186        return Ok(level);
187    };
188    let index = *cursor;
189    advance(cursor);
190    match lexeme.kind {
191        TokenKind::Whitespace | TokenKind::LineComment { doc_style: None } => {
192            level.examined(lexeme.at)
193        }
194        TokenKind::BlockComment {
195            doc_style: None,
196            terminated: true,
197        } => level.examined(lexeme.at),
198        TokenKind::BlockComment {
199            doc_style: _,
200            terminated: false,
201        } => refuse(
202            level,
203            lexeme.at,
204            TextReadCause::Lexical(TextLexicalCause::BlockCommentNotTerminated),
205        ),
206        TokenKind::LineComment {
207            doc_style: Some(style),
208        } => capture_doc(level, lexeme, style, DocForm::Line),
209        TokenKind::BlockComment {
210            doc_style: Some(style),
211            terminated: true,
212        } => capture_doc(level, lexeme, style, DocForm::Block),
213        TokenKind::Ident => capture_atom(
214            level,
215            lexeme.at,
216            CapturedAtom::Word(lexeme.spelling.to_owned()),
217        ),
218        TokenKind::RawIdent => capture_prefixed_identifier(level, lexeme, "r#"),
219        TokenKind::Lifetime {
220            starts_with_number: false,
221        } => capture_lifetime(level, lexeme, LifetimeForm::Ordinary),
222        TokenKind::RawLifetime => capture_lifetime(level, lexeme, LifetimeForm::Raw),
223        TokenKind::Literal { kind, suffix_start } => {
224            capture_lexed_literal(level, lexeme, kind, suffix_start)
225        }
226        TokenKind::Semi => capture_punctuation(level, lexemes, index, ';'),
227        TokenKind::Comma => capture_punctuation(level, lexemes, index, ','),
228        TokenKind::Dot => capture_punctuation(level, lexemes, index, '.'),
229        TokenKind::At => capture_punctuation(level, lexemes, index, '@'),
230        TokenKind::Pound => capture_punctuation(level, lexemes, index, '#'),
231        TokenKind::Tilde => capture_punctuation(level, lexemes, index, '~'),
232        TokenKind::Question => capture_punctuation(level, lexemes, index, '?'),
233        TokenKind::Colon => capture_punctuation(level, lexemes, index, ':'),
234        TokenKind::Dollar => capture_punctuation(level, lexemes, index, '$'),
235        TokenKind::Eq => capture_punctuation(level, lexemes, index, '='),
236        TokenKind::Bang => capture_punctuation(level, lexemes, index, '!'),
237        TokenKind::Lt => capture_punctuation(level, lexemes, index, '<'),
238        TokenKind::Gt => capture_punctuation(level, lexemes, index, '>'),
239        TokenKind::Minus => capture_punctuation(level, lexemes, index, '-'),
240        TokenKind::And => capture_punctuation(level, lexemes, index, '&'),
241        TokenKind::Or => capture_punctuation(level, lexemes, index, '|'),
242        TokenKind::Plus => capture_punctuation(level, lexemes, index, '+'),
243        TokenKind::Star => capture_punctuation(level, lexemes, index, '*'),
244        TokenKind::Slash => capture_punctuation(level, lexemes, index, '/'),
245        TokenKind::Caret => capture_punctuation(level, lexemes, index, '^'),
246        TokenKind::Percent => capture_punctuation(level, lexemes, index, '%'),
247        TokenKind::InvalidIdent => {
248            lexical_refusal(level, lexeme, TextLexicalCause::InvalidIdentifier)
249        }
250        TokenKind::UnknownPrefix => lexical_refusal(level, lexeme, TextLexicalCause::UnknownPrefix),
251        TokenKind::UnknownPrefixLifetime => {
252            lexical_refusal(level, lexeme, TextLexicalCause::UnknownLifetimePrefix)
253        }
254        TokenKind::GuardedStrPrefix => {
255            lexical_refusal(level, lexeme, TextLexicalCause::GuardedStringPrefix)
256        }
257        TokenKind::Lifetime {
258            starts_with_number: true,
259        } => lexical_refusal(level, lexeme, TextLexicalCause::LifetimeStartsWithNumber),
260        TokenKind::Frontmatter {
261            has_invalid_preceding_whitespace: _,
262            invalid_infostring: _,
263        } => lexical_refusal(level, lexeme, TextLexicalCause::Frontmatter),
264        TokenKind::Unknown
265        | TokenKind::OpenParen
266        | TokenKind::CloseParen
267        | TokenKind::OpenBrace
268        | TokenKind::CloseBrace
269        | TokenKind::OpenBracket
270        | TokenKind::CloseBracket => lexical_refusal(level, lexeme, TextLexicalCause::UnknownToken),
271        TokenKind::Eof => Ok(level),
272    }
273}
274
275/// Capture a literal after checking every malformed flag the low-level lexer exposes.
276fn capture_lexed_literal<'capture>(
277    level: CaptureLevel<'capture, u64>,
278    lexeme: &Lexeme<'_>,
279    kind: LiteralKind,
280    suffix_start: u32,
281) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
282    let suffix = usize::try_from(suffix_start)
283        .ok()
284        .and_then(|start| lexeme.spelling.get(start..));
285    if let Some(cause) = malformed_literal(kind) {
286        return refuse(level, lexeme.at, cause);
287    }
288    if suffix == Some("_") {
289        return lexical_refusal(level, lexeme, TextLexicalCause::MalformedLiteral);
290    }
291    level.atom(lexeme.at, |_| {
292        capture_literal(lexeme.spelling).map_err(|cause| match cause {
293            LiteralReadCause::NotReadable if lexeme.spelling.contains('\\') => {
294                refusal(TextReadCause::NotEscapeFree, lexeme.at)
295            }
296            LiteralReadCause::NotAKnownForm | LiteralReadCause::NotReadable => refusal(
297                TextReadCause::Lexical(TextLexicalCause::MalformedLiteral),
298                lexeme.at,
299            ),
300        })
301    })
302}
303
304/// The text refusal established by one malformed low-level literal flag.
305const fn malformed_literal(kind: LiteralKind) -> Option<TextReadCause> {
306    match kind {
307        LiteralKind::Int { base: _, empty_int } => {
308            if empty_int {
309                Some(TextReadCause::Lexical(TextLexicalCause::MalformedLiteral))
310            } else {
311                None
312            }
313        }
314        LiteralKind::Float {
315            base: _,
316            empty_exponent,
317        } => {
318            if empty_exponent {
319                Some(TextReadCause::Lexical(TextLexicalCause::MalformedLiteral))
320            } else {
321                None
322            }
323        }
324        LiteralKind::Char { terminated }
325        | LiteralKind::Byte { terminated }
326        | LiteralKind::Str { terminated }
327        | LiteralKind::ByteStr { terminated }
328        | LiteralKind::CStr { terminated } => {
329            if terminated {
330                None
331            } else {
332                Some(TextReadCause::NotTerminated)
333            }
334        }
335        LiteralKind::RawStr { n_hashes }
336        | LiteralKind::RawByteStr { n_hashes }
337        | LiteralKind::RawCStr { n_hashes } => {
338            if n_hashes.is_none() {
339                Some(TextReadCause::Lexical(TextLexicalCause::MalformedLiteral))
340            } else {
341                None
342            }
343        }
344    }
345}
346
347/// Capture an ordinary or raw lifetime as the punctuation and identifier tokens a proc macro receives.
348fn capture_lifetime<'capture>(
349    level: CaptureLevel<'capture, u64>,
350    lexeme: &Lexeme<'_>,
351    form: LifetimeForm,
352) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
353    let prefix = match form {
354        LifetimeForm::Ordinary => "'",
355        LifetimeForm::Raw => "'r#",
356    };
357    let Some(name) = lexeme.spelling.strip_prefix(prefix) else {
358        return lexical_refusal(level, lexeme, TextLexicalCause::UnknownLifetimePrefix);
359    };
360    if form == LifetimeForm::Raw && raw_identifier_is_reserved(name) {
361        return lexical_refusal(level, lexeme, TextLexicalCause::InvalidIdentifier);
362    }
363    let level = capture_atom(level, lexeme.at, CapturedAtom::JointPunct('\''))?;
364    let atom = match form {
365        LifetimeForm::Ordinary => CapturedAtom::Word(name.to_owned()),
366        LifetimeForm::Raw => CapturedAtom::RawIdentifier(name.to_owned()),
367    };
368    capture_atom(level, lexeme.at, atom)
369}
370
371/// Capture a raw identifier after removing its syntax-only prefix.
372fn capture_prefixed_identifier<'capture>(
373    level: CaptureLevel<'capture, u64>,
374    lexeme: &Lexeme<'_>,
375    prefix: &str,
376) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
377    let Some(name) = lexeme.spelling.strip_prefix(prefix) else {
378        return lexical_refusal(level, lexeme, TextLexicalCause::InvalidIdentifier);
379    };
380    if raw_identifier_is_reserved(name) {
381        return lexical_refusal(level, lexeme, TextLexicalCause::InvalidIdentifier);
382    }
383    capture_atom(
384        level,
385        lexeme.at,
386        CapturedAtom::RawIdentifier(name.to_owned()),
387    )
388}
389
390/// Lower one doc comment into the attribute tokens a proc macro receives.
391fn capture_doc<'capture>(
392    level: CaptureLevel<'capture, u64>,
393    lexeme: &Lexeme<'_>,
394    style: DocStyle,
395    form: DocForm,
396) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
397    let Some(body) = doc_body(lexeme.spelling, style, form) else {
398        return lexical_refusal(level, lexeme, TextLexicalCause::UnknownToken);
399    };
400    let level = capture_atom(level, lexeme.at, CapturedAtom::Punct('#'))?;
401    let level = match style {
402        DocStyle::Outer => level,
403        DocStyle::Inner => capture_atom(level, lexeme.at, CapturedAtom::Punct('!'))?,
404    };
405    level.group(lexeme.at, CapturedDelimiter::Bracket, |_span, inner| {
406        let inner = capture_atom(inner, lexeme.at, CapturedAtom::Word("doc".to_owned()))?;
407        let inner = capture_atom(inner, lexeme.at, CapturedAtom::Punct('='))?;
408        capture_atom(inner, lexeme.at, CapturedAtom::Text(body.to_owned()))
409    })
410}
411
412/// Extract the exact doc-comment body the compiler's implicit `doc` attribute carries.
413fn doc_body(spelling: &str, style: DocStyle, form: DocForm) -> Option<&str> {
414    let prefix = match (style, form) {
415        (DocStyle::Outer, DocForm::Line) => "///",
416        (DocStyle::Inner, DocForm::Line) => "//!",
417        (DocStyle::Outer, DocForm::Block) => "/**",
418        (DocStyle::Inner, DocForm::Block) => "/*!",
419    };
420    let body = spelling.strip_prefix(prefix)?;
421    match form {
422        DocForm::Line => Some(body),
423        DocForm::Block => body.strip_suffix("*/"),
424    }
425}
426
427/// Capture one punctuation token with the same adjacency a proc macro observes.
428fn capture_punctuation<'capture>(
429    level: CaptureLevel<'capture, u64>,
430    lexemes: &[Lexeme<'_>],
431    index: usize,
432    mark: char,
433) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
434    let at = lexemes.get(index).map_or(u64::MAX, |lexeme| lexeme.at);
435    let atom = if punctuation_is_joint(lexemes, index) {
436        CapturedAtom::JointPunct(mark)
437    } else {
438        CapturedAtom::Punct(mark)
439    };
440    capture_atom(level, at, atom)
441}
442
443/// Whether one punctuation character is immediately followed by another punctuation token without whitespace.
444fn punctuation_is_joint(lexemes: &[Lexeme<'_>], index: usize) -> bool {
445    lexemes
446        .get(index.saturating_add(1))
447        .is_some_and(|lexeme| is_punctuation(lexeme.kind))
448}
449
450/// Whether one low-level token becomes a procedural-macro punctuation token.
451const fn is_punctuation(kind: TokenKind) -> bool {
452    matches!(
453        kind,
454        TokenKind::Semi
455            | TokenKind::Comma
456            | TokenKind::Dot
457            | TokenKind::At
458            | TokenKind::Pound
459            | TokenKind::Tilde
460            | TokenKind::Question
461            | TokenKind::Colon
462            | TokenKind::Dollar
463            | TokenKind::Eq
464            | TokenKind::Bang
465            | TokenKind::Lt
466            | TokenKind::Gt
467            | TokenKind::Minus
468            | TokenKind::And
469            | TokenKind::Or
470            | TokenKind::Plus
471            | TokenKind::Star
472            | TokenKind::Slash
473            | TokenKind::Caret
474            | TokenKind::Percent
475    )
476}
477
478/// Capture one already-read atom.
479fn capture_atom(
480    level: CaptureLevel<'_, u64>,
481    at: u64,
482    atom: CapturedAtom,
483) -> Result<CaptureLevel<'_, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
484    level.atom(at, |_| Ok(atom))
485}
486
487/// Establish one lexical refusal at a lexeme's first byte.
488fn lexical_refusal<'capture>(
489    level: CaptureLevel<'capture, u64>,
490    lexeme: &Lexeme<'_>,
491    cause: TextLexicalCause,
492) -> Result<CaptureLevel<'capture, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
493    refuse(level, lexeme.at, TextReadCause::Lexical(cause))
494}
495
496/// Establish one producer refusal through the checked builder's refusal seat.
497fn refuse(
498    level: CaptureLevel<'_, u64>,
499    at: u64,
500    cause: TextReadCause,
501) -> Result<CaptureLevel<'_, u64>, CaptureBuildRefusal<u64, TextReadRefusal>> {
502    level.atom(at, |_| Err(refusal(cause, at)))
503}
504
505/// One byte-positioned text refusal.
506const fn refusal(cause: TextReadCause, at: u64) -> TextReadRefusal {
507    TextReadRefusal { cause, at }
508}
509
510/// Lower the checked builder's refusal into the text route's own byte-positioned refusal.
511fn text_refusal(refusal: &CaptureBuildRefusal<u64, TextReadRefusal>) -> TextReadRefusal {
512    match refusal {
513        CaptureBuildRefusal::Unbounded { bound, at } => TextReadRefusal {
514            cause: TextReadCause::Unbounded(*bound),
515            at: *at,
516        },
517        CaptureBuildRefusal::ProducerRefused {
518            cause,
519            path: _,
520            at: _,
521        } => *cause,
522    }
523}
524
525/// Advance one bounded lexeme cursor without arithmetic overflow.
526fn advance(cursor: &mut usize) {
527    *cursor = cursor.saturating_add(1);
528}
529
530/// Convert one source index into the public coordinate width without a panic path.
531fn source_offset(offset: usize) -> u64 {
532    u64::try_from(offset).unwrap_or(u64::MAX)
533}
534
535/// The captured delimiter and closer written by one opening token.
536const fn opening(kind: TokenKind) -> Option<(CapturedDelimiter, ClosingKind)> {
537    match kind {
538        TokenKind::OpenParen => Some((CapturedDelimiter::Parenthesis, ClosingKind::Parenthesis)),
539        TokenKind::OpenBrace => Some((CapturedDelimiter::Brace, ClosingKind::Brace)),
540        TokenKind::OpenBracket => Some((CapturedDelimiter::Bracket, ClosingKind::Bracket)),
541        TokenKind::LineComment { .. }
542        | TokenKind::BlockComment { .. }
543        | TokenKind::Whitespace
544        | TokenKind::Frontmatter { .. }
545        | TokenKind::Ident
546        | TokenKind::InvalidIdent
547        | TokenKind::RawIdent
548        | TokenKind::UnknownPrefix
549        | TokenKind::UnknownPrefixLifetime
550        | TokenKind::RawLifetime
551        | TokenKind::GuardedStrPrefix
552        | TokenKind::Literal { .. }
553        | TokenKind::Lifetime { .. }
554        | TokenKind::Semi
555        | TokenKind::Comma
556        | TokenKind::Dot
557        | TokenKind::CloseParen
558        | TokenKind::CloseBrace
559        | TokenKind::CloseBracket
560        | TokenKind::At
561        | TokenKind::Pound
562        | TokenKind::Tilde
563        | TokenKind::Question
564        | TokenKind::Colon
565        | TokenKind::Dollar
566        | TokenKind::Eq
567        | TokenKind::Bang
568        | TokenKind::Lt
569        | TokenKind::Gt
570        | TokenKind::Minus
571        | TokenKind::And
572        | TokenKind::Or
573        | TokenKind::Plus
574        | TokenKind::Star
575        | TokenKind::Slash
576        | TokenKind::Caret
577        | TokenKind::Percent
578        | TokenKind::Unknown
579        | TokenKind::Eof => None,
580    }
581}
582
583/// Whether one token is any written group closer.
584const fn is_closing(kind: TokenKind) -> bool {
585    matches!(
586        kind,
587        TokenKind::CloseParen | TokenKind::CloseBrace | TokenKind::CloseBracket
588    )
589}
590
591/// Whether one token closes the group currently being read.
592fn closes(kind: TokenKind, closing: Option<&Closing>) -> bool {
593    match (kind, closing.map(|held| held.kind)) {
594        (TokenKind::CloseParen, Some(ClosingKind::Parenthesis))
595        | (TokenKind::CloseBrace, Some(ClosingKind::Brace))
596        | (TokenKind::CloseBracket, Some(ClosingKind::Bracket)) => true,
597        (
598            TokenKind::LineComment { .. }
599            | TokenKind::BlockComment { .. }
600            | TokenKind::Whitespace
601            | TokenKind::Frontmatter { .. }
602            | TokenKind::Ident
603            | TokenKind::InvalidIdent
604            | TokenKind::RawIdent
605            | TokenKind::UnknownPrefix
606            | TokenKind::UnknownPrefixLifetime
607            | TokenKind::RawLifetime
608            | TokenKind::GuardedStrPrefix
609            | TokenKind::Literal { .. }
610            | TokenKind::Lifetime { .. }
611            | TokenKind::Semi
612            | TokenKind::Comma
613            | TokenKind::Dot
614            | TokenKind::OpenParen
615            | TokenKind::OpenBrace
616            | TokenKind::OpenBracket
617            | TokenKind::CloseParen
618            | TokenKind::CloseBrace
619            | TokenKind::CloseBracket
620            | TokenKind::At
621            | TokenKind::Pound
622            | TokenKind::Tilde
623            | TokenKind::Question
624            | TokenKind::Colon
625            | TokenKind::Dollar
626            | TokenKind::Eq
627            | TokenKind::Bang
628            | TokenKind::Lt
629            | TokenKind::Gt
630            | TokenKind::Minus
631            | TokenKind::And
632            | TokenKind::Or
633            | TokenKind::Plus
634            | TokenKind::Star
635            | TokenKind::Slash
636            | TokenKind::Caret
637            | TokenKind::Percent
638            | TokenKind::Unknown
639            | TokenKind::Eof,
640            None | Some(ClosingKind::Parenthesis | ClosingKind::Brace | ClosingKind::Bracket),
641        ) => false,
642    }
643}