Skip to main content

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