rust-lang/rust-analyzer · error

`ast::DocComment` must have a comment token

Error message

`ast::DocComment` must have a comment token

What it means

ast::DocComment::token() asserts the structural invariant that a DocComment node always wraps a comment token: it takes the node's first token and casts it to AnyComment, panicking with this message if the cast fails. Since DocComment is only produced from comment tokens, failure indicates a corrupted or wrongly-constructed node.

Source

Thrown at crates/syntax/src/ast/node_ext.rs:289

    }
}

impl ast::DocComment {
    // `///` or `/**` or `//!` or `/*!`, all are 3 chars.
    pub const PREFIX_LEN: TextSize = TextSize::new(3);

    pub fn kind(&self) -> AttrKind {
        match self.inner_doc_comment_token() {
            Some(_) => AttrKind::Inner,
            None => AttrKind::Outer,
        }
    }

    pub fn token(&self) -> AnyComment {
        self.syntax
            .first_token()
            .and_then(ast::AnyComment::cast)
            .expect("`ast::DocComment` must have a comment token")
    }

    pub fn shape(&self) -> CommentShape {
        CommentShape::from_text(self.text_with_markers())
    }

    /// Returns the text with the `/**...*/` or `/*!...*/` or `///...` or `//!...` markers.
    pub fn text_with_markers(&self) -> &str {
        text_of_first_token(&self.syntax)
    }

    /// Returns the textual content of a doc comment node as a single string with prefix and suffix removed.
    pub fn text(&self) -> &str {
        let shape = self.shape();
        let text = &self.text_with_markers()[Self::PREFIX_LEN.into()..];
        if shape == CommentShape::Block {
            // The `*/` may not exist because of recovery.
            text.strip_suffix("*/").unwrap_or(text)

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Verify the node actually came from a cast of a comment token; only construct DocComment from COMMENT tokens.
  2. If doing tree surgery, re-validate the subtree after mutation instead of assuming invariants.
  3. If hit on ordinary parsed code, reduce to a minimal file and report a rust-analyzer parser bug.

Example fix

// before
let comment = doc_comment.token();
// after
let comment = doc_comment.syntax().first_token().and_then(ast::AnyComment::cast);
if let Some(comment) = comment { /* ... */ }
Defensive patterns

Strategy: type-guard

Validate before calling

// Only treat nodes as DocComment when their first token is a comment
fn is_valid_doc_comment(node: &SyntaxNode) -> bool {
    node.first_token().map_or(false, |t| t.kind().is_comment())
}

Type guard

fn safe_doc_comment(node: &SyntaxNode) -> Option<ast::DocComment> {
    ast::DocComment::cast(node.clone())
        .filter(|dc| dc.syntax().first_token().map_or(false, |t| t.kind().is_comment()))
}

Try / catch

let comment = std::panic::catch_unwind(AssertUnwindSafe(|| doc_comment.token()))
    .ok()
    .or_else(|| doc_comment.syntax().first_token().and_then(ast::AnyComment::cast));

Prevention

When it happens

Trigger: Calling .token() on an ast::DocComment whose syntax node's first token is not castable to AnyComment — practically only when the node was constructed from a non-comment token, or the tree was mutated (edits, token grafting) leaving the DocComment without its comment token.

Common situations: Custom syntax-tree surgery in tests or tooling that reparents tokens; synthetic DocComment nodes built in fixtures with wrong tokens; IDE incremental reparse bugs (should be reported upstream).

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/b3751720dc9f32a6. Report an issue: GitHub.