solar_ast/
token.rs

1//! Solidity source code token.
2
3use crate::{
4    DocComment, StrKind,
5    ast::{BinOp, BinOpKind, UnOp, UnOpKind},
6};
7use solar_interface::{Ident, Span, Symbol, diagnostics::ErrorGuaranteed};
8use std::{borrow::Cow, fmt, mem::MaybeUninit};
9
10/// The type of a comment.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub enum CommentKind {
13    /// `// ...`, `/// ...`
14    Line,
15    /// `/* ... */`, `/** ... */`
16    Block,
17}
18
19/// A binary operation token.
20///
21/// Note that this enum contains only binary operators that can also be used in assignments.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum BinOpToken {
24    /// `+`
25    Plus,
26    /// `-`
27    Minus,
28    /// `*`
29    Star,
30    /// `/`
31    Slash,
32    /// `%`
33    Percent,
34    /// `^`
35    Caret,
36    /// `&`
37    And,
38    /// `|`
39    Or,
40    /// `<<`
41    Shl,
42    /// `>>`
43    Shr,
44    /// `>>>`
45    Sar,
46}
47
48impl fmt::Display for BinOpToken {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        f.write_str(self.to_str())
51    }
52}
53
54impl BinOpToken {
55    /// Returns the string representation of the binary operator token.
56    pub const fn to_str(self) -> &'static str {
57        match self {
58            Self::Plus => "+",
59            Self::Minus => "-",
60            Self::Star => "*",
61            Self::Slash => "/",
62            Self::Percent => "%",
63            Self::Caret => "^",
64            Self::And => "&",
65            Self::Or => "|",
66            Self::Shl => "<<",
67            Self::Shr => ">>",
68            Self::Sar => ">>>",
69        }
70    }
71
72    /// Returns the string representation of the binary operator token followed by an equals sign
73    /// (`=`).
74    pub const fn to_str_with_eq(self) -> &'static str {
75        match self {
76            Self::Plus => "+=",
77            Self::Minus => "-=",
78            Self::Star => "*=",
79            Self::Slash => "/=",
80            Self::Percent => "%=",
81            Self::Caret => "^=",
82            Self::And => "&=",
83            Self::Or => "|=",
84            Self::Shl => "<<=",
85            Self::Shr => ">>=",
86            Self::Sar => ">>>=",
87        }
88    }
89
90    /// Returns the binary operator kind.
91    #[inline]
92    pub const fn as_binop(self) -> BinOpKind {
93        match self {
94            Self::Plus => BinOpKind::Add,
95            Self::Minus => BinOpKind::Sub,
96            Self::Star => BinOpKind::Mul,
97            Self::Slash => BinOpKind::Div,
98            Self::Percent => BinOpKind::Rem,
99            Self::Caret => BinOpKind::BitXor,
100            Self::And => BinOpKind::BitAnd,
101            Self::Or => BinOpKind::BitOr,
102            Self::Shl => BinOpKind::Shl,
103            Self::Shr => BinOpKind::Shr,
104            Self::Sar => BinOpKind::Sar,
105        }
106    }
107}
108
109/// Describes how a sequence of token trees is delimited.
110#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
111pub enum Delimiter {
112    /// `( ... )`
113    Parenthesis,
114    /// `{ ... }`
115    Brace,
116    /// `[ ... ]`
117    Bracket,
118}
119
120impl Delimiter {
121    /// Returns the string representation of the opening delimiter.
122    pub const fn to_open_str(self) -> &'static str {
123        match self {
124            Self::Parenthesis => "(",
125            Self::Brace => "{",
126            Self::Bracket => "[",
127        }
128    }
129
130    /// Returns the string representation of the closing delimiter.
131    pub const fn to_close_str(self) -> &'static str {
132        match self {
133            Self::Parenthesis => ")",
134            Self::Brace => "}",
135            Self::Bracket => "]",
136        }
137    }
138}
139
140/// A literal token. Different from an AST literal as this is unparsed and only contains the raw
141/// contents.
142#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
143pub struct TokenLit {
144    /// The symbol of the literal token, excluding any quotes.
145    pub symbol: Symbol,
146    /// The literal kind.
147    pub kind: TokenLitKind,
148}
149
150impl fmt::Display for TokenLit {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        let &Self { kind, symbol } = self;
153        match kind {
154            TokenLitKind::Str => write!(f, "\"{symbol}\""),
155            TokenLitKind::UnicodeStr => write!(f, "unicode\"{symbol}\""),
156            TokenLitKind::HexStr => write!(f, "hex\"{symbol}\""),
157            TokenLitKind::Integer | TokenLitKind::Rational | TokenLitKind::Err(_) => {
158                write!(f, "{symbol}")
159            }
160        }
161    }
162}
163
164impl TokenLit {
165    /// Creates a new literal token.
166    #[inline]
167    pub const fn new(kind: TokenLitKind, symbol: Symbol) -> Self {
168        Self { kind, symbol }
169    }
170
171    /// Returns a description of the literal.
172    pub const fn description(self) -> &'static str {
173        self.kind.description()
174    }
175}
176
177/// A kind of literal token.
178#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
179pub enum TokenLitKind {
180    /// An integer literal token.
181    Integer,
182    /// A rational literal token.
183    Rational,
184    /// A string literal token.
185    Str,
186    /// A unicode string literal token.
187    UnicodeStr,
188    /// A hex string literal token.
189    HexStr,
190    /// An error occurred while lexing the literal token.
191    Err(ErrorGuaranteed),
192}
193
194impl From<StrKind> for TokenLitKind {
195    fn from(str_kind: StrKind) -> Self {
196        match str_kind {
197            StrKind::Str => Self::Str,
198            StrKind::Unicode => Self::UnicodeStr,
199            StrKind::Hex => Self::HexStr,
200        }
201    }
202}
203
204impl TokenLitKind {
205    /// Returns the description of the literal kind.
206    pub const fn description(self) -> &'static str {
207        match self {
208            Self::Integer => "integer",
209            Self::Rational => "rational",
210            Self::Str => "string",
211            Self::UnicodeStr => "unicode string",
212            Self::HexStr => "hex string",
213            Self::Err(_) => "error",
214        }
215    }
216}
217
218/// A kind of token.
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
220pub enum TokenKind {
221    // Expression-operator symbols.
222    /// `=`
223    Eq,
224    /// `<`
225    Lt,
226    /// `<=`
227    Le,
228    /// `==`
229    EqEq,
230    /// `!=`
231    Ne,
232    /// `>=`
233    Ge,
234    /// `>`
235    Gt,
236    /// `&&`
237    AndAnd,
238    /// `||`
239    OrOr,
240    /// `!`
241    Not,
242    /// `~`
243    Tilde,
244    /// `:=`
245    Walrus,
246    /// `++`
247    PlusPlus,
248    /// `--`
249    MinusMinus,
250    /// `**`
251    StarStar,
252    /// A binary operator token.
253    BinOp(BinOpToken),
254    /// A binary operator token, followed by an equals sign (`=`).
255    BinOpEq(BinOpToken),
256
257    // Structural symbols.
258    /// `@`
259    At,
260    /// `.`
261    Dot,
262    /// `,`
263    Comma,
264    /// `;`
265    Semi,
266    /// `:`
267    Colon,
268    /// `->`
269    Arrow,
270    /// `=>`
271    FatArrow,
272    /// `?`
273    Question,
274    /// An opening delimiter (e.g., `{`).
275    OpenDelim(Delimiter),
276    /// A closing delimiter (e.g., `}`).
277    CloseDelim(Delimiter),
278
279    // Literals.
280    /// A literal token.
281    ///
282    /// Note that this does not include boolean literals.
283    ///
284    /// `Symbol` is the literal's parsed data. In string literals, this is the unescaped value, and
285    /// excludes its quotes (`"`, `'`) and prefix (`hex`, `unicode`).
286    Literal(TokenLitKind, Symbol),
287
288    /// Identifier token.
289    Ident(Symbol),
290
291    /// A comment or doc-comment token.
292    ///
293    /// `Symbol` is the comment's data excluding its "quotes" (`//`, `/**`)
294    /// similarly to symbols in string literal tokens.
295    Comment(bool /* is_doc */, CommentKind, Symbol),
296
297    /// End of file marker.
298    Eof,
299}
300
301impl fmt::Display for TokenKind {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        f.write_str(&self.description())
304    }
305}
306
307impl TokenKind {
308    /// Creates a new literal token kind.
309    pub fn lit(kind: TokenLitKind, symbol: Symbol) -> Self {
310        Self::Literal(kind, symbol)
311    }
312
313    /// Returns the string representation of the token kind.
314    pub fn as_str(&self) -> &str {
315        match self {
316            Self::Eq => "=",
317            Self::Lt => "<",
318            Self::Le => "<=",
319            Self::EqEq => "==",
320            Self::Ne => "!=",
321            Self::Ge => ">=",
322            Self::Gt => ">",
323            Self::AndAnd => "&&",
324            Self::OrOr => "||",
325            Self::Not => "!",
326            Self::Tilde => "~",
327            Self::Walrus => ":=",
328            Self::PlusPlus => "++",
329            Self::MinusMinus => "--",
330            Self::StarStar => "**",
331            Self::BinOp(op) => op.to_str(),
332            Self::BinOpEq(op) => op.to_str_with_eq(),
333
334            Self::At => "@",
335            Self::Dot => ".",
336            Self::Comma => ",",
337            Self::Semi => ";",
338            Self::Colon => ":",
339            Self::Arrow => "->",
340            Self::FatArrow => "=>",
341            Self::Question => "?",
342            Self::OpenDelim(d) => d.to_open_str(),
343            Self::CloseDelim(d) => d.to_close_str(),
344
345            Self::Literal(.., symbol) | Self::Ident(.., symbol) | Self::Comment(.., symbol) => {
346                symbol.as_str()
347            }
348
349            Self::Eof => "<eof>",
350        }
351    }
352
353    /// Returns the description of the token kind.
354    pub fn description(&self) -> Cow<'_, str> {
355        match self {
356            Self::Literal(kind, _) => return format!("<{}>", kind.description()).into(),
357            Self::Ident(symbol) => return symbol.to_string().into(),
358            Self::Comment(false, CommentKind::Block, _) => "<block comment>",
359            Self::Comment(true, CommentKind::Block, _) => "<block doc-comment>",
360            Self::Comment(false, CommentKind::Line, _) => "<line comment>",
361            Self::Comment(true, CommentKind::Line, _) => "<line doc-comment>",
362            _ => self.as_str(),
363        }
364        .into()
365    }
366
367    /// Returns `true` if the token kind is an operator.
368    pub const fn is_op(&self) -> bool {
369        use TokenKind::*;
370        match self {
371            Eq | Lt | Le | EqEq | Ne | Ge | Gt | AndAnd | OrOr | Not | Tilde | Walrus
372            | PlusPlus | MinusMinus | StarStar | BinOp(_) | BinOpEq(_) | At | Dot | Comma
373            | Colon | Arrow | FatArrow | Question => true,
374
375            OpenDelim(..) | CloseDelim(..) | Literal(..) | Comment(..) | Ident(..) | Semi | Eof => {
376                false
377            }
378        }
379    }
380
381    /// Returns the token kind as a unary operator, if any.
382    pub fn as_unop(&self, is_postfix: bool) -> Option<UnOpKind> {
383        let kind = if is_postfix {
384            match self {
385                Self::PlusPlus => UnOpKind::PostInc,
386                Self::MinusMinus => UnOpKind::PostDec,
387                _ => return None,
388            }
389        } else {
390            match self {
391                Self::PlusPlus => UnOpKind::PreInc,
392                Self::MinusMinus => UnOpKind::PreDec,
393                Self::Not => UnOpKind::Not,
394                Self::Tilde => UnOpKind::BitNot,
395                Self::BinOp(BinOpToken::Minus) => UnOpKind::Neg,
396                _ => return None,
397            }
398        };
399        debug_assert_eq!(kind.is_postfix(), is_postfix);
400        Some(kind)
401    }
402
403    /// Returns the token kind as a binary operator, if any.
404    #[inline]
405    pub fn as_binop(&self) -> Option<BinOpKind> {
406        match self {
407            Self::Eq => Some(BinOpKind::Eq),
408            Self::Lt => Some(BinOpKind::Lt),
409            Self::Le => Some(BinOpKind::Le),
410            Self::EqEq => Some(BinOpKind::Eq),
411            Self::Ne => Some(BinOpKind::Ne),
412            Self::Ge => Some(BinOpKind::Ge),
413            Self::Gt => Some(BinOpKind::Gt),
414            Self::AndAnd => Some(BinOpKind::And),
415            Self::OrOr => Some(BinOpKind::Or),
416            Self::StarStar => Some(BinOpKind::Pow),
417            Self::BinOp(op) => Some(op.as_binop()),
418            _ => None,
419        }
420    }
421
422    /// Returns the token kind as a binary operator, if any.
423    #[inline]
424    pub fn as_binop_eq(&self) -> Option<BinOpKind> {
425        match self {
426            Self::BinOpEq(op) => Some(op.as_binop()),
427            _ => None,
428        }
429    }
430
431    /// Returns `true` if the token kind is a normal comment (not a doc-comment).
432    #[inline]
433    pub const fn is_comment(&self) -> bool {
434        matches!(self, Self::Comment(false, ..))
435    }
436
437    /// Returns `true` if the token kind is a comment or doc-comment.
438    #[inline]
439    pub const fn is_comment_or_doc(&self) -> bool {
440        matches!(self, Self::Comment(..))
441    }
442}
443
444/// A single token.
445///
446/// This struct is written in such a way that it can be passed in registers.
447/// The actual representation is [`TokenRepr`], but it should not be accessed directly.
448#[derive(Clone, Copy)]
449#[repr(C)]
450pub struct Token {
451    _kind: MaybeUninit<u64>,
452    _span: u64,
453}
454
455/// Actual representation of [`Token`].
456///
457/// Do not use this struct directly. Use [`Token`] instead.
458#[derive(Clone, Copy, PartialEq, Eq)]
459pub struct TokenRepr {
460    /// The kind of the token.
461    pub kind: TokenKind,
462    /// The full span of the token.
463    pub span: Span,
464}
465
466const _: () = {
467    assert!(size_of::<Token>() == size_of::<TokenRepr>());
468    assert!(align_of::<Token>() >= align_of::<TokenRepr>());
469    assert!(std::mem::offset_of!(Token, _kind) == std::mem::offset_of!(TokenRepr, kind));
470    assert!(std::mem::offset_of!(Token, _span) == std::mem::offset_of!(TokenRepr, span));
471};
472
473impl std::ops::Deref for Token {
474    type Target = TokenRepr;
475
476    #[inline(always)]
477    fn deref(&self) -> &Self::Target {
478        // SAFETY: transparent wrapper.
479        unsafe { std::mem::transmute(self) }
480    }
481}
482
483impl std::ops::DerefMut for Token {
484    #[inline(always)]
485    fn deref_mut(&mut self) -> &mut Self::Target {
486        // SAFETY: transparent wrapper.
487        unsafe { std::mem::transmute(self) }
488    }
489}
490
491impl fmt::Debug for Token {
492    #[inline]
493    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494        fmt::Debug::fmt(&**self, f)
495    }
496}
497
498impl fmt::Debug for TokenRepr {
499    #[inline]
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        f.debug_struct("Token").field("kind", &self.kind).field("span", &self.span).finish()
502    }
503}
504
505impl PartialEq for Token {
506    #[inline]
507    fn eq(&self, other: &Self) -> bool {
508        **self == **other
509    }
510}
511
512impl Eq for Token {}
513
514impl From<Ident> for Token {
515    #[inline]
516    fn from(ident: Ident) -> Self {
517        Self::from_ast_ident(ident)
518    }
519}
520
521impl Token {
522    /// The [EOF](TokenKind::Eof) token.
523    pub const EOF: Self = Self::new(TokenKind::Eof, Span::DUMMY);
524
525    /// A dummy token that will be thrown away later.
526    pub const DUMMY: Self = Self::new(TokenKind::Question, Span::DUMMY);
527
528    /// Creates a new token.
529    #[inline]
530    pub const fn new(kind: TokenKind, span: Span) -> Self {
531        // SAFETY: transparent wrapper.
532        unsafe { std::mem::transmute(TokenRepr { kind, span }) }
533    }
534
535    /// Recovers a `Token` from an `Ident`.
536    #[inline]
537    pub fn from_ast_ident(ident: Ident) -> Self {
538        Self::new(TokenKind::Ident(ident.name), ident.span)
539    }
540
541    /// Creates a new identifier if the kind is [`TokenKind::Ident`].
542    #[inline]
543    pub fn ident(&self) -> Option<Ident> {
544        match self.kind {
545            TokenKind::Ident(ident) => Some(Ident::new(ident, self.span)),
546            _ => None,
547        }
548    }
549
550    /// Returns the literal if the kind is [`TokenKind::Literal`].
551    #[inline]
552    pub fn lit(&self) -> Option<TokenLit> {
553        match self.kind {
554            TokenKind::Literal(kind, symbol) => Some(TokenLit::new(kind, symbol)),
555            _ => None,
556        }
557    }
558
559    /// Returns this token's literal kind, if any.
560    #[inline]
561    pub fn lit_kind(&self) -> Option<TokenLitKind> {
562        match self.kind {
563            TokenKind::Literal(kind, _) => Some(kind),
564            _ => None,
565        }
566    }
567
568    /// Returns the comment if the kind is [`TokenKind::Comment`], and whether it's a doc-comment.
569    #[inline]
570    pub fn comment(&self) -> Option<(bool, DocComment)> {
571        match self.kind {
572            TokenKind::Comment(is_doc, kind, symbol) => {
573                Some((is_doc, DocComment { span: self.span, kind, symbol }))
574            }
575            _ => None,
576        }
577    }
578
579    /// Returns the comment if the kind is [`TokenKind::Comment`].
580    ///
581    /// Does not check that `is_doc` is `true`.
582    #[inline]
583    pub fn doc(&self) -> Option<DocComment> {
584        match self.kind {
585            TokenKind::Comment(_, kind, symbol) => {
586                Some(DocComment { span: self.span, kind, symbol })
587            }
588            _ => None,
589        }
590    }
591
592    /// Returns `true` if the token is an operator.
593    #[inline]
594    pub fn is_op(&self) -> bool {
595        self.kind.is_op()
596    }
597
598    /// Returns the token as a unary operator, if any.
599    #[inline]
600    pub fn as_unop(&self, is_postfix: bool) -> Option<UnOp> {
601        self.kind.as_unop(is_postfix).map(|kind| UnOp { span: self.span, kind })
602    }
603
604    /// Returns the token as a binary operator, if any.
605    #[inline]
606    pub fn as_binop(&self) -> Option<BinOp> {
607        self.kind.as_binop().map(|kind| BinOp { span: self.span, kind })
608    }
609
610    /// Returns the token as a binary operator, if any.
611    #[inline]
612    pub fn as_binop_eq(&self) -> Option<BinOp> {
613        self.kind.as_binop_eq().map(|kind| BinOp { span: self.span, kind })
614    }
615
616    /// Returns `true` if the token is an identifier.
617    #[inline]
618    pub fn is_ident(&self) -> bool {
619        matches!(self.kind, TokenKind::Ident(_))
620    }
621
622    /// Returns `true` if the token is a literal. Includes `bool` literals.
623    #[inline]
624    pub fn is_lit(&self) -> bool {
625        matches!(self.kind, TokenKind::Literal(..)) || self.is_bool_lit()
626    }
627
628    /// Returns `true` if the token is a given keyword, `kw`.
629    #[inline]
630    pub fn is_keyword(&self, kw: Symbol) -> bool {
631        self.is_ident_where(|id| id.name == kw)
632    }
633
634    /// Returns `true` if the token is any of the given keywords.
635    #[inline]
636    pub fn is_keyword_any(&self, kws: &[Symbol]) -> bool {
637        self.is_ident_where(|id| kws.contains(&id.name))
638    }
639
640    /// Returns `true` if the token is a keyword used in the language.
641    #[inline]
642    pub fn is_used_keyword(&self) -> bool {
643        self.is_ident_where(Ident::is_used_keyword)
644    }
645
646    /// Returns `true` if the token is a keyword reserved for possible future use.
647    #[inline]
648    pub fn is_unused_keyword(&self) -> bool {
649        self.is_ident_where(Ident::is_unused_keyword)
650    }
651
652    /// Returns `true` if the token is a keyword.
653    #[inline]
654    pub fn is_reserved_ident(&self, yul: bool) -> bool {
655        self.is_ident_where(|i| i.is_reserved(yul))
656    }
657
658    /// Returns `true` if the token is an identifier, but not a keyword.
659    #[inline]
660    pub fn is_non_reserved_ident(&self, yul: bool) -> bool {
661        self.is_ident_where(|i| i.is_non_reserved(yul))
662    }
663
664    /// Returns `true` if the token is an elementary type name.
665    ///
666    /// Note that this does not include `[u]fixedMxN` types.
667    #[inline]
668    pub fn is_elementary_type(&self) -> bool {
669        self.is_ident_where(Ident::is_elementary_type)
670    }
671
672    /// Returns `true` if the token is the identifier `true` or `false`.
673    #[inline]
674    pub fn is_bool_lit(&self) -> bool {
675        self.is_ident_where(|id| id.name.is_bool_lit())
676    }
677
678    /// Returns `true` if the token is a numeric literal.
679    #[inline]
680    pub fn is_numeric_lit(&self) -> bool {
681        matches!(self.kind, TokenKind::Literal(TokenLitKind::Integer | TokenLitKind::Rational, _))
682    }
683
684    /// Returns `true` if the token is the integer literal.
685    #[inline]
686    pub fn is_integer_lit(&self) -> bool {
687        matches!(self.kind, TokenKind::Literal(TokenLitKind::Integer, _))
688    }
689
690    /// Returns `true` if the token is the rational literal.
691    #[inline]
692    pub fn is_rational_lit(&self) -> bool {
693        matches!(self.kind, TokenKind::Literal(TokenLitKind::Rational, _))
694    }
695
696    /// Returns `true` if the token is a string literal.
697    #[inline]
698    pub fn is_str_lit(&self) -> bool {
699        matches!(self.kind, TokenKind::Literal(TokenLitKind::Str, _))
700    }
701
702    /// Returns `true` if the token is an identifier for which `pred` holds.
703    #[inline]
704    pub fn is_ident_where(&self, pred: impl FnOnce(Ident) -> bool) -> bool {
705        self.ident().is_some_and(pred)
706    }
707
708    /// Returns `true` if the token is an end-of-file marker.
709    #[inline]
710    pub fn is_eof(&self) -> bool {
711        matches!(self.kind, TokenKind::Eof)
712    }
713
714    /// Returns `true` if the token is the given open delimiter.
715    #[inline]
716    pub fn is_open_delim(&self, d: Delimiter) -> bool {
717        self.kind == TokenKind::OpenDelim(d)
718    }
719
720    /// Returns `true` if the token is the given close delimiter.
721    #[inline]
722    pub fn is_close_delim(&self, d: Delimiter) -> bool {
723        self.kind == TokenKind::CloseDelim(d)
724    }
725
726    /// Returns `true` if the token is a normal comment (not a doc-comment).
727    #[inline]
728    pub fn is_comment(&self) -> bool {
729        self.kind.is_comment()
730    }
731
732    /// Returns `true` if the token is a comment or doc-comment.
733    #[inline]
734    pub fn is_comment_or_doc(&self) -> bool {
735        self.kind.is_comment_or_doc()
736    }
737
738    /// Returns `true` if the token is a location specifier.
739    #[inline]
740    pub fn is_location_specifier(&self) -> bool {
741        self.is_ident_where(Ident::is_location_specifier)
742    }
743
744    /// Returns `true` if the token is a mutability specifier.
745    #[inline]
746    pub fn is_mutability_specifier(&self) -> bool {
747        self.is_ident_where(Ident::is_mutability_specifier)
748    }
749
750    /// Returns `true` if the token is a visibility specifier.
751    #[inline]
752    pub fn is_visibility_specifier(&self) -> bool {
753        self.is_ident_where(Ident::is_visibility_specifier)
754    }
755
756    /// Returns this token's full description: `{self.description()} '{self.kind}'`.
757    pub fn full_description(&self) -> impl fmt::Display + '_ {
758        // https://github.com/rust-lang/rust/blob/44bf2a32a52467c45582c3355a893400e620d010/compiler/rustc_parse/src/parser/mod.rs#L378
759        if let Some(description) = self.description() {
760            format!("{description} `{}`", self.kind)
761        } else {
762            format!("`{}`", self.kind)
763        }
764    }
765
766    /// Returns the string representation of the token.
767    pub fn as_str(&self) -> &str {
768        self.kind.as_str()
769    }
770
771    /// Returns this token's description, if any.
772    #[inline]
773    pub fn description(self) -> Option<TokenDescription> {
774        TokenDescription::from_token(self)
775    }
776}
777
778/// A description of a token.
779///
780/// Precedes the token string in error messages like `keyword 'for'` in `expected identifier, found
781/// keyword 'for'`. See [`full_description`](Token::full_description).
782#[derive(Clone, Copy, Debug, PartialEq, Eq)]
783pub enum TokenDescription {
784    /// A keyword.
785    Keyword,
786    /// A reserved keyword.
787    ReservedKeyword,
788    /// A Yul keyword.
789    YulKeyword,
790    /// A Yul EVM builtin.
791    YulEvmBuiltin,
792}
793
794impl fmt::Display for TokenDescription {
795    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796        f.write_str(self.to_str())
797    }
798}
799
800impl TokenDescription {
801    /// Returns the description of the given token.
802    pub fn from_token(token: Token) -> Option<Self> {
803        match token.kind {
804            _ if token.is_used_keyword() => Some(Self::Keyword),
805            _ if token.is_unused_keyword() => Some(Self::ReservedKeyword),
806            _ if token.is_ident_where(|id| id.is_yul_keyword()) => Some(Self::YulKeyword),
807            _ if token.is_ident_where(|id| id.is_yul_evm_builtin()) => Some(Self::YulEvmBuiltin),
808            _ => None,
809        }
810    }
811
812    /// Returns the string representation of the token description.
813    pub const fn to_str(self) -> &'static str {
814        match self {
815            Self::Keyword => "keyword",
816            Self::ReservedKeyword => "reserved keyword",
817            Self::YulKeyword => "Yul keyword",
818            Self::YulEvmBuiltin => "Yul EVM builtin keyword",
819        }
820    }
821}