solar_ast/
token.rs

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