rust-lang/rust-analyzer · error · SsrError

Expected ident, found {:?} while parsing kind constraint

Error message

Expected ident, found {:?} while parsing kind constraint

What it means

Inside a kind(...) constraint, SSR expects exactly one identifier naming the node kind, then a closing ')'. If the token found where the identifier should be is not a SyntaxKind::IDENT, this error reports the unexpected token kind via {:?}. It comes from parse_constraint, which is reachable both directly from parse_placeholder and recursively via the 'not(...)' constraint.

Source

Thrown at crates/ide-ssr/src/parsing.rs:318

    }
    let name = name.ok_or_else(|| SsrError::new("Placeholder ($) with no name"))?;
    Ok(Placeholder::new(name, constraints))
}

fn parse_constraint(tokens: &mut std::vec::IntoIter<Token>) -> Result<Constraint, SsrError> {
    let constraint_type = tokens
        .next()
        .ok_or_else(|| SsrError::new("Found end of placeholder while looking for a constraint"))?
        .text
        .to_string();
    match constraint_type.as_str() {
        "kind" => {
            expect_token(tokens, "(")?;
            let t = tokens.next().ok_or_else(|| {
                SsrError::new("Unexpected end of constraint while looking for kind")
            })?;
            if t.kind != SyntaxKind::IDENT {
                bail!("Expected ident, found {:?} while parsing kind constraint", t.kind);
            }
            expect_token(tokens, ")")?;
            Ok(Constraint::Kind(NodeKind::from(&t.text)?))
        }
        "not" => {
            expect_token(tokens, "(")?;
            let sub = parse_constraint(tokens)?;
            expect_token(tokens, ")")?;
            Ok(Constraint::Not(Box::new(sub)))
        }
        x => bail!("Unsupported constraint type '{}'", x),
    }
}

fn expect_token(tokens: &mut std::vec::IntoIter<Token>, expected: &str) -> Result<(), SsrError> {
    if let Some(t) = tokens.next() {
        if t.text == expected {
            return Ok(());

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Pass the kind name as a bare unquoted identifier: kind(literal)
  2. Ensure exactly one identifier appears between kind( and )
  3. Check the reported token kind in the message to see what the parser actually found

Example fix

// before
let pattern = "$x:kind(\"literal\")";
// after
let pattern = "$x:kind(literal)";
Defensive patterns

Strategy: validation

Validate before calling

fn valid_kind_constraint(p: &str) -> bool {
    if let Some(idx) = p.find("kind(") {
        let rest = &p[idx + 5..];
        let mut chars = rest.chars();
        match chars.next() {
            Some(c) if c.is_alphabetic() || c == '_' => {
                // next non-ident char should be ')'
                let ident_end = rest.find(|c: char| !(c.is_alphanumeric() || c == '_'));
                matches!(ident_end.map(|e| rest[e..].starts_with(')')), Some(true))
            }
            _ => false, // missing or non-ident argument (e.g. quoted)
        }
    } else {
        true
    }
}

Try / catch

match parse_pattern(pattern) {
    Ok(p) => use_pattern(p),
    Err(e) if e.message.contains("while parsing kind constraint") => {
        eprintln!("kind() takes one unquoted identifier: kind(literal)");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Writing constraints like kind(42), kind("literal"), kind() where the next token is a literal/paren/EOF rather than an ident, or kind(kind) with a keyword-ish token that does not lex as IDENT.

Common situations: Quoting the kind name out of habit (kind("literal")); forgetting the argument entirely (kind()); typos that leave punctuation inside the parentheses.

Related errors


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