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
- Verify the NodeId originates from the same semantic model / parse revision passed as `semantic`
- Use `semantic.expression(node_id)` with `let Some(expr) = ... else { return Edit::noop() }` to skip unquotable nodes safely
- 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
- Always derive NodeIds from the same semantic model/revision used at lookup
- Never cache node ids across checker passes
- Return noop edits for unresolvable nodes in fix builders
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
- Expected at least one reference
- extra use-def data should have been retained
- should be set because `extract_if` only yields elements with
- Should only ever pass a positive integer to `from_nonnegativ
- argument index should be valid
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/b4d9d821ec5e4e74.
Report an issue: GitHub.