{"record":{"id":"b5f017af5a12c833","repo":"astral-sh/ruff","slug":"parsed-module-should-have-assigned-a-node-index","errorCode":null,"errorMessage":"`parsed_module` should have assigned a node index","messagePattern":"`parsed_module` should have assigned a node index","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/ty_python_semantic/src/types/diagnostic.rs","lineNumber":5115,"sourceCode":"        }\n    }\n\n    if superclass.is_object(db) && matches!(member, \"__eq__\" | \"__ne__\") {\n        // Inspired by mypy's subdiagnostic at <https://github.com/python/mypy/blob/1b6ebb17b7fe64488a7b3c3b4b0187bb14fe331b/mypy/messages.py#L1307-L1318>\n        let eq_subdiagnostics = [\n            format_args!(\n                \"It is recommended for `{member}` to work with arbitrary objects, for example:\",\n            ),\n            format_args!(\"\"),\n            format_args!(\"    def {member}(self, other: object) -> bool:\"),\n            format_args!(\"        if not isinstance(other, {class_name}):\"),\n            format_args!(\"            return False\"),\n            format_args!(\"        return <logic to compare two `{class_name}` instances>\"),\n            format_args!(\"\"),\n        ];\n\n        for subdiag in eq_subdiagnostics {\n            diagnostic.help(subdiag);\n        }\n    }\n}\n\n/// Reports an incompatible pair of source-defined methods in a resolved MRO.\npub(super) fn report_incompatible_base_method<'db>(\n    context: &InferContext<'db, '_>,\n    class: StaticClassLiteral<'db>,\n    member: &str,\n    selected: (ClassType<'db>, Definition<'db>, MethodDecorator),\n    contract: (ClassType<'db>, Definition<'db>, MethodDecorator),\n    error_context: impl FnOnce() -> ErrorContextTree<'db>,\n) {\n    let db = context.db();\n    let Some(builder) = context.report_lint(&INVALID_METHOD_OVERRIDE, class.header_range(db))\n    else {\n        return;\n    };","sourceCodeStart":5097,"sourceCodeEnd":5133,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ty_python_semantic/src/types/diagnostic.rs#L5097-L5133","documentation":"Internal assertion while emitting ty's `override-of-final-method` diagnostic (`report_overridden_final_method`, crates/ty_python_semantic/src/types/diagnostic.rs:4837). When the override of the `@final` member is a set of function overloads, ty builds an unsafe autofix deleting them and groups the edits with `IsolationLevel::Group(class_node.node_index().load().as_u32()...)`. The `expect` asserts that the class AST node — reached via `subclass_literal.body_scope(db).node(db).expect_class().node(context.module())` — has a node index assigned by `parsed_module` for the file being diagnosed. If the class body node does not actually belong to `context.module()`'s parsed tree (for example a definition resolved through a stub or a different module), no index was assigned for this module's numbering and ty panics: an ICE in diagnostic rendering, not a problem with the Python code.","triggerScenarios":"Emitting `override-of-final-method` (subclass overrides a `@typing.final`-decorated method) where the subclass member is a `FunctionDef`/overload set, `should_fix`/isolate logic runs, and the class node obtained from the subclass literal's body scope has an empty `node_index()` in the current module — typically when the diagnostic context's module differs from the file that actually defines the class (stub/implementation pairs, re-exported or aliased class definitions).","commonSituations":"ty contributors hit it after changing how diagnostics resolve the enclosing class node or how `parsed_module` assigns indices; users hit it on pre-release builds checking projects that override `@final` methods across stub/impl boundaries (e.g. typed libraries with `.pyi` files). Not caused by project configuration.","solutions":["As a ty user: report an ICE issue with the class hierarchy reproducer (base class with `@final` method + subclass override in a stub or separate module), `ty --version`, and `RUST_BACKTRACE=1` output; downgrade to the last working version meanwhile.","As a ty developer: `node_index()` is an `Option` — handle the `None` case instead of `expect`, e.g. skip the `IsolationLevel::Group` grouping (fall back to per-edit isolation or omit the fix) when no index exists.","Verify the node/module pairing: index the class node against the `parsed_module` of the file that owns it (`subclass_literal`'s file) rather than assuming `context.module()` owns it.","Add an mdtest covering a `@final` override defined via a stub/implementation pair to lock in the fix."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Embedders (LSP/library): panic isolation per diagnostic request\nuse std::panic::{catch_unwind, AssertUnwindSafe};\n\nlet diags = catch_unwind(AssertUnwindSafe(|| diagnostics(db, file)))\n    .unwrap_or_default(); // degrade to no diagnostics for this file, keep server alive","preventionTips":["ty developers: treat `node_index()` as fallible — skip `IsolationLevel::Group` or the fix when the index is None instead of `expect`ing it.","Derive the class node from the parsed module that actually owns the class definition, not from the diagnostic's ambient `context.module()`.","Add mdtests for `@final` overrides defined across stub/implementation file pairs.","Users: pin a released ty; report the reproducer so the ICE is fixed upstream."],"tags":["ty","diagnostics","autofix","ast-node-index","final-method","panic","ice","rust"],"backgroundTag":"compiler-internal-error","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}