{"record":{"id":"b4d9d821ec5e4e74","repo":"astral-sh/ruff","slug":"expression-not-found","errorCode":null,"errorMessage":"Expression not found","messagePattern":"Expression not found","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs","lineNumber":324,"sourceCode":"}\n\n/// Wrap a type annotation in quotes.\n///\n/// This requires more than just wrapping the reference itself in quotes. For example:\n/// - When quoting `Series` in `Series[pd.Timestamp]`, we want `\"Series[pd.Timestamp]\"`.\n/// - When quoting `kubernetes` in `kubernetes.SecurityContext`, we want `\"kubernetes.SecurityContext\"`.\n/// - When quoting `Series` in `Series[\"pd.Timestamp\"]`, we want `\"Series[pd.Timestamp]\"`.\n/// - When quoting `Series` in `Series[Literal[\"pd.Timestamp\"]]`, we want `\"Series[Literal['pd.Timestamp']]\"`.\n///\n/// In general, when expanding a component of a call chain, we want to quote the entire call chain.\npub(crate) fn quote_annotation(\n    node_id: NodeId,\n    semantic: &SemanticModel,\n    stylist: &Stylist,\n    locator: &Locator,\n    flags: StringLiteralFlags,\n) -> Edit {\n    let expr = semantic.expression(node_id).expect(\"Expression not found\");\n    if let Some(parent_id) = semantic.parent_expression_id(node_id) {\n        match semantic.expression(parent_id) {\n            Some(Expr::Subscript(parent)) if expr == parent.value.as_ref() => {\n                // If we're quoting the value of a subscript, we need to quote the entire\n                // expression. For example, when quoting `DataFrame` in `DataFrame[int]`, we\n                // should generate `\"DataFrame[int]\"`.\n                return quote_annotation(parent_id, semantic, stylist, locator, flags);\n            }\n            Some(Expr::Attribute(parent)) if expr == parent.value.as_ref() => {\n                // If we're quoting the value of an attribute, we need to quote the entire\n                // expression. For example, when quoting `DataFrame` in `pd.DataFrame`, we\n                // should generate `\"pd.DataFrame\"`.\n                return quote_annotation(parent_id, semantic, stylist, locator, flags);\n            }\n            Some(Expr::Call(parent)) if expr == parent.func.as_ref() => {\n                // If we're quoting the function of a call, we need to quote the entire\n                // expression. For example, when quoting `DataFrame` in `DataFrame()`, we\n                // should generate `\"DataFrame()\"`.","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/astral-sh/ruff/blob/26f38c119cac42e4d320ba08f09224fdec74af2c/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs#L306-L342","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet expr = semantic.expression(node_id).expect(\"Expression not found\");\n// after\nlet Some(expr) = semantic.expression(node_id) else {\n    return Edit::noop();\n};","handlingStrategy":"validation","validationCode":"if semantic.expression(node_id).is_none() { return Edit::noop(); }","typeGuard":"fn resolvable(semantic: &SemanticModel, id: NodeId) -> bool { semantic.expression(id).is_some() }","tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","panic","semantic-model","flake8-type-checking"],"backgroundTag":"stale-node-id-panic","analyzedSha":"26f38c119cac42e4d320ba08f09224fdec74af2c","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}