Skip to main content

wdl_ast/
lib.rs

1//! An abstract syntax tree for Workflow Description Language (WDL) documents.
2//!
3//! The AST implementation is effectively a facade over the concrete syntax tree
4//! (CST) implemented by [SyntaxTree] from `wdl-grammar`.
5//!
6//! An AST is cheap to construct and may be cheaply cloned at any level.
7//!
8//! However, an AST (and the underlying CST) are immutable; updating the tree
9//! requires replacing a node in the tree to produce a new tree. The unaffected
10//! nodes of the replacement are reused from the old tree to the new tree.
11//!
12//! # Examples
13//!
14//! An example of parsing a WDL document into an AST and validating it:
15//!
16//! ```rust
17//! # let source = "version 1.1\nworkflow test {}";
18//! use wdl_ast::Document;
19//!
20//! let (document, diagnostics) = Document::parse(source);
21//! if !diagnostics.is_empty() {
22//!     // Handle the failure to parse
23//! }
24//! ```
25
26#![warn(missing_docs)]
27#![warn(rust_2018_idioms)]
28#![warn(rust_2021_compatibility)]
29#![warn(missing_debug_implementations)]
30#![warn(clippy::missing_docs_in_private_items)]
31#![warn(rustdoc::broken_intra_doc_links)]
32
33use std::collections::HashSet;
34use std::fmt;
35use std::str::FromStr;
36
37pub use rowan::Direction;
38use rowan::NodeOrToken;
39use v1::CloseBrace;
40use v1::CloseHeredoc;
41use v1::OpenBrace;
42use v1::OpenHeredoc;
43pub use wdl_grammar::Diagnostic;
44pub use wdl_grammar::Label;
45pub use wdl_grammar::Severity;
46pub use wdl_grammar::Span;
47pub use wdl_grammar::SupportedVersion;
48pub use wdl_grammar::SyntaxElement;
49pub use wdl_grammar::SyntaxKind;
50pub use wdl_grammar::SyntaxNode;
51pub use wdl_grammar::SyntaxToken;
52pub use wdl_grammar::SyntaxTokenExt;
53pub use wdl_grammar::SyntaxTree;
54pub use wdl_grammar::WorkflowDescriptionLanguage;
55pub use wdl_grammar::lexer;
56pub use wdl_grammar::version;
57
58pub mod v1;
59
60mod element;
61
62pub use element::*;
63
64/// An [`AstNode`] that may have documentation comments attached to it.
65pub trait Documented<N: TreeNode>: AstNode<N> {
66    /// Get all comment nodes preceding this node that start with
67    /// [`DOC_COMMENT_PREFIX`].
68    ///
69    /// If doc comments don't apply to this node, `None` will be returned.
70    ///
71    /// The comments returned are ordered top to bottom.
72    fn doc_comments(&self) -> Option<Vec<Comment<N::Token>>>;
73}
74
75/// Shared doc comment extraction logic.
76pub fn doc_comments<N: TreeNode>(
77    preceding_trivia: impl IntoIterator<Item = N::Token>,
78) -> impl Iterator<Item = Comment<N::Token>> {
79    preceding_trivia
80        .into_iter()
81        .take_while(|token| {
82            token.kind() == SyntaxKind::Whitespace || token.kind() == SyntaxKind::Comment
83        })
84        .filter_map(|token| {
85            if token.kind() == SyntaxKind::Comment && token.text().starts_with(DOC_COMMENT_PREFIX) {
86                Some(Comment::<N::Token>::cast(token).expect("should be a comment"))
87            } else {
88                None
89            }
90        })
91}
92
93/// A trait that abstracts the underlying representation of a syntax tree node.
94///
95/// The default node type is `SyntaxNode` for all AST nodes.
96pub trait TreeNode: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
97    /// The associated token type for the tree node.
98    type Token: TreeToken;
99
100    /// Gets the parent node of the node.
101    ///
102    /// Returns `None` if the node is a root.
103    fn parent(&self) -> Option<Self>;
104
105    /// Gets the syntax kind of the node.
106    fn kind(&self) -> SyntaxKind;
107
108    /// Gets the text of the node.
109    ///
110    /// Node text is not contiguous, so the returned value implements `Display`.
111    fn text(&self) -> impl fmt::Display;
112
113    /// Gets the span of the node.
114    fn span(&self) -> Span;
115
116    /// Gets the children nodes of the node.
117    fn children(&self) -> impl Iterator<Item = Self>;
118
119    /// Gets all the children of the node, including tokens.
120    fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>>;
121
122    /// Gets the first token of the node.
123    fn first_token(&self) -> Option<Self::Token>;
124
125    /// Gets the last token of the node.
126    fn last_token(&self) -> Option<Self::Token>;
127
128    /// Gets the node descendants of the node.
129    fn descendants(&self) -> impl Iterator<Item = Self>;
130
131    /// Gets the ancestors of the node.
132    fn ancestors(&self) -> impl Iterator<Item = Self>;
133}
134
135/// A trait that abstracts the underlying representation of a syntax token.
136pub trait TreeToken: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
137    /// The associated node type for the token.
138    type Node: TreeNode;
139
140    /// Gets the parent node of the token.
141    fn parent(&self) -> Self::Node;
142
143    /// Gets the syntax kind for the token.
144    fn kind(&self) -> SyntaxKind;
145
146    /// Gets the text of the token.
147    fn text(&self) -> &str;
148
149    /// Gets the span of the token.
150    fn span(&self) -> Span;
151}
152
153/// A trait implemented by AST nodes.
154pub trait AstNode<N: TreeNode>: Sized {
155    /// Determines if the kind can be cast to this representation.
156    fn can_cast(kind: SyntaxKind) -> bool;
157
158    /// Casts the given inner type to the this representation.
159    fn cast(inner: N) -> Option<Self>;
160
161    /// Gets the inner type from this representation.
162    fn inner(&self) -> &N;
163
164    /// Gets the syntax kind of the node.
165    fn kind(&self) -> SyntaxKind {
166        self.inner().kind()
167    }
168
169    /// Gets the text of the node.
170    ///
171    /// As node text is not contiguous, this returns a type that implements
172    /// `Display`.
173    fn text<'a>(&'a self) -> impl fmt::Display
174    where
175        N: 'a,
176    {
177        self.inner().text()
178    }
179
180    /// Gets the span of the node.
181    fn span(&self) -> Span {
182        self.inner().span()
183    }
184
185    /// Gets the first token child that can cast to an expected type.
186    fn token<C>(&self) -> Option<C>
187    where
188        C: AstToken<N::Token>,
189    {
190        self.inner()
191            .children_with_tokens()
192            .filter_map(|e| e.into_token())
193            .find_map(|t| C::cast(t))
194    }
195
196    /// Gets all the token children that can cast to an expected type.
197    fn tokens<'a, C>(&'a self) -> impl Iterator<Item = C>
198    where
199        C: AstToken<N::Token>,
200        N: 'a,
201    {
202        self.inner()
203            .children_with_tokens()
204            .filter_map(|e| e.into_token().and_then(C::cast))
205    }
206
207    /// Gets the last token of the node and attempts to cast it to an expected
208    /// type.
209    ///
210    /// Returns `None` if there is no last token or if it cannot be casted to
211    /// the expected type.
212    fn last_token<C>(&self) -> Option<C>
213    where
214        C: AstToken<N::Token>,
215    {
216        self.inner().last_token().and_then(C::cast)
217    }
218
219    /// Gets the first node child that can cast to an expected type.
220    fn child<C>(&self) -> Option<C>
221    where
222        C: AstNode<N>,
223    {
224        self.inner().children().find_map(C::cast)
225    }
226
227    /// Gets all node children that can cast to an expected type.
228    fn children<'a, C>(&'a self) -> impl Iterator<Item = C>
229    where
230        C: AstNode<N>,
231        N: 'a,
232    {
233        self.inner().children().filter_map(C::cast)
234    }
235
236    /// Gets the parent of the node if the underlying tree node has a parent.
237    ///
238    /// Returns `None` if the node has no parent or if the parent node is not of
239    /// the expected type.
240    fn parent<'a, P>(&self) -> Option<P>
241    where
242        P: AstNode<N>,
243        N: 'a,
244    {
245        P::cast(self.inner().parent()?)
246    }
247
248    /// Calculates the span of a scope given the node where the scope is
249    /// visible.
250    ///
251    /// Returns `None` if the node does not contain the open and close tokens as
252    /// children.
253    fn scope_span<O, C>(&self) -> Option<Span>
254    where
255        O: AstToken<N::Token>,
256        C: AstToken<N::Token>,
257    {
258        let open = self.token::<O>()?.span();
259        let close = self.last_token::<C>()?.span();
260
261        // The span starts after the opening brace and after the closing brace
262        Some(Span::new(open.end(), close.end() - open.end()))
263    }
264
265    /// Gets the interior span of child opening and closing brace tokens for the
266    /// node.
267    ///
268    /// The span starts from immediately after the opening brace token and ends
269    /// immediately before the closing brace token.
270    ///
271    /// Returns `None` if the node does not contain child brace tokens.
272    fn braced_scope_span(&self) -> Option<Span> {
273        self.scope_span::<OpenBrace<N::Token>, CloseBrace<N::Token>>()
274    }
275
276    /// Gets the interior span of child opening and closing heredoc tokens for
277    /// the node.
278    ///
279    /// The span starts from immediately after the opening heredoc token and
280    /// ends immediately before the closing heredoc token.
281    ///
282    /// Returns `None` if the node does not contain child heredoc tokens.
283    fn heredoc_scope_span(&self) -> Option<Span> {
284        self.scope_span::<OpenHeredoc<N::Token>, CloseHeredoc<N::Token>>()
285    }
286
287    /// Gets the node descendants (including self) from this node that can be
288    /// cast to the expected type.
289    fn descendants<'a, D>(&'a self) -> impl Iterator<Item = D>
290    where
291        D: AstNode<N>,
292        N: 'a,
293    {
294        self.inner().descendants().filter_map(|d| D::cast(d))
295    }
296}
297
298/// A trait implemented by AST tokens.
299pub trait AstToken<T: TreeToken>: Sized {
300    /// Determines if the kind can be cast to this representation.
301    fn can_cast(kind: SyntaxKind) -> bool;
302
303    /// Casts the given inner type to the this representation.
304    fn cast(inner: T) -> Option<Self>;
305
306    /// Gets the inner type from this representation.
307    fn inner(&self) -> &T;
308
309    /// Gets the syntax kind of the token.
310    fn kind(&self) -> SyntaxKind {
311        self.inner().kind()
312    }
313
314    /// Gets the text of the token.
315    fn text<'a>(&'a self) -> &'a str
316    where
317        T: 'a,
318    {
319        self.inner().text()
320    }
321
322    /// Gets the span of the token.
323    fn span(&self) -> Span {
324        self.inner().span()
325    }
326
327    /// Gets the parent of the token.
328    ///
329    /// Returns `None` if the parent node cannot be cast to the expected type.
330    fn parent<'a, P>(&self) -> Option<P>
331    where
332        P: AstNode<T::Node>,
333        T: 'a,
334    {
335        P::cast(self.inner().parent())
336    }
337}
338
339/// Implemented by nodes that can create a new root from a different tree node
340/// type.
341pub trait NewRoot<N: TreeNode>: Sized {
342    /// Constructs a new root node from the give root node of a different tree
343    /// node type.
344    fn new_root(root: N) -> Self;
345}
346
347impl TreeNode for SyntaxNode {
348    type Token = SyntaxToken;
349
350    fn parent(&self) -> Option<SyntaxNode> {
351        self.parent()
352    }
353
354    fn kind(&self) -> SyntaxKind {
355        self.kind()
356    }
357
358    fn children(&self) -> impl Iterator<Item = Self> {
359        self.children()
360    }
361
362    fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>> {
363        self.children_with_tokens()
364    }
365
366    fn text(&self) -> impl fmt::Display {
367        self.text()
368    }
369
370    fn span(&self) -> Span {
371        let range = self.text_range();
372        let start = usize::from(range.start());
373        Span::new(start, usize::from(range.end()) - start)
374    }
375
376    fn first_token(&self) -> Option<Self::Token> {
377        self.first_token()
378    }
379
380    fn last_token(&self) -> Option<Self::Token> {
381        self.last_token()
382    }
383
384    fn descendants(&self) -> impl Iterator<Item = Self> {
385        self.descendants()
386    }
387
388    fn ancestors(&self) -> impl Iterator<Item = Self> {
389        self.ancestors()
390    }
391}
392
393impl TreeToken for SyntaxToken {
394    type Node = SyntaxNode;
395
396    fn parent(&self) -> SyntaxNode {
397        self.parent().expect("token should have a parent")
398    }
399
400    fn kind(&self) -> SyntaxKind {
401        self.kind()
402    }
403
404    fn text(&self) -> &str {
405        self.text()
406    }
407
408    fn span(&self) -> Span {
409        let range = self.text_range();
410        let start = usize::from(range.start());
411        Span::new(start, usize::from(range.end()) - start)
412    }
413}
414
415/// Represents the AST of a [Document].
416///
417/// See [Document::ast].
418#[derive(Clone, Debug, PartialEq, Eq)]
419pub enum Ast<N: TreeNode = SyntaxNode> {
420    /// The WDL document specifies an unsupported version.
421    Unsupported,
422    /// The WDL document is V1.
423    V1(v1::Ast<N>),
424}
425
426impl<N: TreeNode> Ast<N> {
427    /// Gets the AST as a V1 AST.
428    ///
429    /// Returns `None` if the AST is not a V1 AST.
430    pub fn as_v1(&self) -> Option<&v1::Ast<N>> {
431        match self {
432            Self::V1(ast) => Some(ast),
433            _ => None,
434        }
435    }
436
437    /// Consumes `self` and attempts to return the V1 AST.
438    pub fn into_v1(self) -> Option<v1::Ast<N>> {
439        match self {
440            Self::V1(ast) => Some(ast),
441            _ => None,
442        }
443    }
444
445    /// Consumes `self` and attempts to return the V1 AST.
446    ///
447    /// # Panics
448    ///
449    /// Panics if the AST is not a V1 AST.
450    pub fn unwrap_v1(self) -> v1::Ast<N> {
451        self.into_v1().expect("the AST is not a V1 AST")
452    }
453}
454
455/// Represents a single WDL document.
456///
457/// See [Document::ast] for getting a version-specific Abstract
458/// Syntax Tree.
459#[derive(Clone, PartialEq, Eq, Hash)]
460pub struct Document<N: TreeNode = SyntaxNode>(N);
461
462impl<N: TreeNode> AstNode<N> for Document<N> {
463    fn can_cast(kind: SyntaxKind) -> bool {
464        kind == SyntaxKind::RootNode
465    }
466
467    fn cast(inner: N) -> Option<Self> {
468        if Self::can_cast(inner.kind()) {
469            Some(Self(inner))
470        } else {
471            None
472        }
473    }
474
475    fn inner(&self) -> &N {
476        &self.0
477    }
478}
479
480impl Document {
481    /// Parses a document from the given source.
482    ///
483    /// A document and its AST elements are trivially cloned.
484    ///
485    /// # Example
486    ///
487    /// ```rust
488    /// # use wdl_ast::{Document, AstToken, Ast};
489    /// let (document, diagnostics) = Document::parse("version 1.1");
490    /// assert!(diagnostics.is_empty());
491    ///
492    /// assert_eq!(
493    ///     document
494    ///         .version_statement()
495    ///         .expect("should have version statement")
496    ///         .version()
497    ///         .text(),
498    ///     "1.1"
499    /// );
500    ///
501    /// match document.ast() {
502    ///     Ast::V1(ast) => {
503    ///         assert_eq!(ast.items().count(), 0);
504    ///     }
505    ///     Ast::Unsupported => panic!("should be a V1 AST"),
506    /// }
507    /// ```
508    pub fn parse(source: &str) -> (Self, Vec<Diagnostic>) {
509        let (tree, diagnostics) = SyntaxTree::parse(source);
510        (
511            Document::cast(tree.into_syntax()).expect("document should cast"),
512            diagnostics,
513        )
514    }
515}
516
517impl<N: TreeNode> Document<N> {
518    /// Gets the version statement of the document.
519    ///
520    /// This can be used to determine the version of the document that was
521    /// parsed.
522    ///
523    /// A return value of `None` signifies a missing version statement.
524    pub fn version_statement(&self) -> Option<VersionStatement<N>> {
525        self.child()
526    }
527
528    /// Gets the AST representation of the document.
529    pub fn ast(&self) -> Ast<N> {
530        self.ast_with_version_fallback(None)
531    }
532
533    /// Gets the AST representation of the document, falling back to the
534    /// specified WDL version if the document's version statement contains
535    /// an unrecognized version.
536    ///
537    /// A fallback version of `None` does not have any fallback behavior, and is
538    /// equivalent to calling [`Document::ast()`].
539    ///
540    /// <div class="warning">
541    ///
542    /// It is the caller's responsibility to ensure that falling back to the
543    /// given version does not introduce unwanted behavior. For applications
544    /// where correctness is essential, the caller should only provide a
545    /// version that is known to be compatible with the version declared in
546    /// the document.
547    ///
548    /// </div>
549    pub fn ast_with_version_fallback(&self, fallback_version: Option<SupportedVersion>) -> Ast<N> {
550        let Some(stmt) = self.version_statement() else {
551            return Ast::Unsupported;
552        };
553        // Parse the version statement, fall back to the fallback, and finally give up
554        // if neither of those works.
555        let Some(version) = stmt
556            .version()
557            .text()
558            .parse::<SupportedVersion>()
559            .ok()
560            .or(fallback_version)
561        else {
562            return Ast::Unsupported;
563        };
564        match version {
565            SupportedVersion::V1(_) => Ast::V1(v1::Ast(self.0.clone())),
566            _ => Ast::Unsupported,
567        }
568    }
569
570    /// Morphs a document of one node type to a document of a different node
571    /// type.
572    pub fn morph<U: TreeNode + NewRoot<N>>(self) -> Document<U> {
573        Document(U::new_root(self.0))
574    }
575}
576
577impl fmt::Debug for Document {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        self.0.fmt(f)
580    }
581}
582
583/// Represents a whitespace token in the AST.
584#[derive(Clone, Debug, PartialEq, Eq, Hash)]
585pub struct Whitespace<T: TreeToken = SyntaxToken>(T);
586
587impl<T: TreeToken> AstToken<T> for Whitespace<T> {
588    fn can_cast(kind: SyntaxKind) -> bool {
589        kind == SyntaxKind::Whitespace
590    }
591
592    fn cast(inner: T) -> Option<Self> {
593        match inner.kind() {
594            SyntaxKind::Whitespace => Some(Self(inner)),
595            _ => None,
596        }
597    }
598
599    fn inner(&self) -> &T {
600        &self.0
601    }
602}
603
604/// The prefix for directive comments.
605pub const DIRECTIVE_COMMENT_PREFIX: &str = "#@";
606/// The delimiter between a directive and its contents
607pub const DIRECTIVE_DELIMITER: &str = ":";
608
609/// A comment directive for WDL tools to respect.
610#[derive(Debug, PartialEq, Eq)]
611pub enum Directive {
612    /// Ignore any rules contained in the set.
613    Except(HashSet<String>),
614}
615
616impl FromStr for Directive {
617    type Err = ();
618
619    fn from_str(s: &str) -> Result<Self, Self::Err> {
620        let s = s.strip_prefix(DIRECTIVE_COMMENT_PREFIX).ok_or(())?;
621        let (directive, contents) = s.trim().split_once(DIRECTIVE_DELIMITER).ok_or(())?;
622        match directive.trim_end() {
623            "except" => Ok(Self::Except(HashSet::from_iter(
624                contents.split(',').map(|id| id.trim().to_string()),
625            ))),
626            _ => Err(()),
627        }
628    }
629}
630
631/// The prefix for doc comments.
632pub const DOC_COMMENT_PREFIX: &str = "##";
633
634/// Represents a comment token in the AST.
635#[derive(Debug, Clone, PartialEq, Eq, Hash)]
636pub struct Comment<T: TreeToken = SyntaxToken>(T);
637
638impl<T: TreeToken> AstToken<T> for Comment<T> {
639    fn can_cast(kind: SyntaxKind) -> bool {
640        kind == SyntaxKind::Comment
641    }
642
643    fn cast(inner: T) -> Option<Self> {
644        match inner.kind() {
645            SyntaxKind::Comment => Some(Self(inner)),
646            _ => None,
647        }
648    }
649
650    fn inner(&self) -> &T {
651        &self.0
652    }
653}
654
655impl Comment {
656    /// Gets whether the comment starts with DIRECIVE_COMMENT_PREFIX.
657    pub fn is_directive(&self) -> bool {
658        self.text().starts_with(DIRECTIVE_COMMENT_PREFIX)
659    }
660
661    /// Try to parse the comment as a directive.
662    pub fn directive(&self) -> Option<Directive> {
663        self.text().parse::<Directive>().ok()
664    }
665
666    /// Gets whether comment starts with [`DOC_COMMENT_PREFIX`].
667    pub fn is_doc_comment(&self) -> bool {
668        self.text().starts_with(DOC_COMMENT_PREFIX)
669    }
670
671    /// Gets whether the comment is an inline comment or not.
672    pub fn is_inline_comment(&self) -> bool {
673        // If there is a preceding token that isn't whitespace with a newline, then
674        // the comment is not alone on this line.
675        if let Some(prev) = self.inner().prev_sibling_or_token() {
676            if prev.kind() == SyntaxKind::Whitespace {
677                !prev
678                    .into_token()
679                    .expect("SyntaxKind::Whitespace is a token")
680                    .text()
681                    .contains('\n')
682            } else {
683                true
684            }
685        } else {
686            false
687        }
688    }
689}
690
691/// Represents a version statement in a WDL AST.
692#[derive(Debug, Clone, PartialEq, Eq, Hash)]
693pub struct VersionStatement<N: TreeNode = SyntaxNode>(N);
694
695impl<N: TreeNode> VersionStatement<N> {
696    /// Gets the version of the version statement.
697    pub fn version(&self) -> Version<N::Token> {
698        self.token()
699            .expect("version statement must have a version token")
700    }
701
702    /// Gets the version keyword of the version statement.
703    pub fn keyword(&self) -> v1::VersionKeyword<N::Token> {
704        self.token()
705            .expect("version statement must have a version keyword")
706    }
707}
708
709impl<N: TreeNode> AstNode<N> for VersionStatement<N> {
710    fn can_cast(kind: SyntaxKind) -> bool {
711        kind == SyntaxKind::VersionStatementNode
712    }
713
714    fn cast(inner: N) -> Option<Self> {
715        match inner.kind() {
716            SyntaxKind::VersionStatementNode => Some(Self(inner)),
717            _ => None,
718        }
719    }
720
721    fn inner(&self) -> &N {
722        &self.0
723    }
724}
725
726/// Represents a version in the AST.
727#[derive(Clone, Debug, PartialEq, Eq, Hash)]
728pub struct Version<T: TreeToken = SyntaxToken>(T);
729
730impl<T: TreeToken> AstToken<T> for Version<T> {
731    fn can_cast(kind: SyntaxKind) -> bool {
732        kind == SyntaxKind::Version
733    }
734
735    fn cast(inner: T) -> Option<Self> {
736        match inner.kind() {
737            SyntaxKind::Version => Some(Self(inner)),
738            _ => None,
739        }
740    }
741
742    fn inner(&self) -> &T {
743        &self.0
744    }
745}
746
747/// Represents an identifier token.
748#[derive(Debug, Clone, PartialEq, Eq, Hash)]
749pub struct Ident<T: TreeToken = SyntaxToken>(T);
750
751impl<T: TreeToken> Ident<T> {
752    /// Gets a hashable representation of the identifier.
753    pub fn hashable(&self) -> TokenText<T> {
754        TokenText(self.0.clone())
755    }
756}
757
758impl<T: TreeToken> AstToken<T> for Ident<T> {
759    fn can_cast(kind: SyntaxKind) -> bool {
760        kind == SyntaxKind::Ident
761    }
762
763    fn cast(inner: T) -> Option<Self> {
764        match inner.kind() {
765            SyntaxKind::Ident => Some(Self(inner)),
766            _ => None,
767        }
768    }
769
770    fn inner(&self) -> &T {
771        &self.0
772    }
773}
774
775/// Helper for hashing tokens by their text.
776///
777/// Normally a token's equality and hash implementation work by comparing
778/// the token's element in the tree; thus, two tokens with the same text
779/// but different positions in the tree will compare and hash differently.
780///
781/// With this hash implementation, two tokens compare and hash identically if
782/// their text is identical.
783#[derive(Debug, Clone)]
784pub struct TokenText<T: TreeToken = SyntaxToken>(T);
785
786impl TokenText {
787    /// Gets the text of the underlying token.
788    pub fn text(&self) -> &str {
789        self.0.text()
790    }
791
792    /// Gets the span of the underlying token.
793    pub fn span(&self) -> Span {
794        self.0.span()
795    }
796}
797
798impl<T: TreeToken> PartialEq for TokenText<T> {
799    fn eq(&self, other: &Self) -> bool {
800        self.0.text() == other.0.text()
801    }
802}
803
804impl<T: TreeToken> Eq for TokenText<T> {}
805
806impl<T: TreeToken> std::hash::Hash for TokenText<T> {
807    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
808        self.0.text().hash(state);
809    }
810}
811
812impl<T: TreeToken> std::borrow::Borrow<str> for TokenText<T> {
813    fn borrow(&self) -> &str {
814        self.0.text()
815    }
816}