astral-sh/ruff · error

Expression not found

Error message

Expression not found

What it means

`quote_annotation` in flake8_type_checking helpers looks up an expression by NodeId in the semantic model to produce a quoting Edit. The `.expect("Expression not found")` asserts that every NodeId handed to this function resolves to a live expression in the semantic model. It fires when the function is called with a stale or out-of-model node id.

Source

Thrown at crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs:324

}

/// Wrap a type annotation in quotes.
///
/// This requires more than just wrapping the reference itself in quotes. For example:
/// - When quoting `Series` in `Series[pd.Timestamp]`, we want `"Series[pd.Timestamp]"`.
/// - When quoting `kubernetes` in `kubernetes.SecurityContext`, we want `"kubernetes.SecurityContext"`.
/// - When quoting `Series` in `Series["pd.Timestamp"]`, we want `"Series[pd.Timestamp]"`.
/// - When quoting `Series` in `Series[Literal["pd.Timestamp"]]`, we want `"Series[Literal['pd.Timestamp']]"`.
///
/// In general, when expanding a component of a call chain, we want to quote the entire call chain.
pub(crate) fn quote_annotation(
    node_id: NodeId,
    semantic: &SemanticModel,
    stylist: &Stylist,
    locator: &Locator,
    flags: StringLiteralFlags,
) -> Edit {
    let expr = semantic.expression(node_id).expect("Expression not found");
    if let Some(parent_id) = semantic.parent_expression_id(node_id) {
        match semantic.expression(parent_id) {
            Some(Expr::Subscript(parent)) if expr == parent.value.as_ref() => {
                // If we're quoting the value of a subscript, we need to quote the entire
                // expression. For example, when quoting `DataFrame` in `DataFrame[int]`, we
                // should generate `"DataFrame[int]"`.
                return quote_annotation(parent_id, semantic, stylist, locator, flags);
            }
            Some(Expr::Attribute(parent)) if expr == parent.value.as_ref() => {
                // If we're quoting the value of an attribute, we need to quote the entire
                // expression. For example, when quoting `DataFrame` in `pd.DataFrame`, we
                // should generate `"pd.DataFrame"`.
                return quote_annotation(parent_id, semantic, stylist, locator, flags);
            }
            Some(Expr::Call(parent)) if expr == parent.func.as_ref() => {
                // If we're quoting the function of a call, we need to quote the entire
                // expression. For example, when quoting `DataFrame` in `DataFrame()`, we
                // should generate `"DataFrame()"`.

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Verify the NodeId originates from the same semantic model / parse revision passed as `semantic`
  2. Use `semantic.expression(node_id)` with `let Some(expr) = ... else { return Edit::noop() }` to skip unquotable nodes safely
  3. Ensure ids are collected during the same checker pass in which the fix is generated, not cached across passes

Example fix

// before
let expr = semantic.expression(node_id).expect("Expression not found");
// after
let Some(expr) = semantic.expression(node_id) else {
    return Edit::noop();
};
Defensive patterns

Strategy: validation

Validate before calling

if semantic.expression(node_id).is_none() { return Edit::noop(); }

Type guard

fn resolvable(semantic: &SemanticModel, id: NodeId) -> bool { semantic.expression(id).is_some() }

Prevention

When it happens

Trigger: Calling quote_annotation (directly or via quote_imports/fix_imports) with a NodeId that the SemanticModel does not contain — e.g. an id from a different module/revision, or from a node dropped during AST pruning before the fix runs.

Common situations: Contributors hit this when refactoring how annotation node ids are collected (e.g. collecting ids before the semantic model is built for the current file), or when ids survive across salsa revisions and become stale.

Related errors


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/b4d9d821ec5e4e74. Report an issue: GitHub.