astral-sh/ruff · critical
`parsed_module` should have assigned a node index
Error message
`parsed_module` should have assigned a node index
What it means
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.
Source
Thrown at crates/ty_python_semantic/src/types/diagnostic.rs:5115
}
}
if superclass.is_object(db) && matches!(member, "__eq__" | "__ne__") {
// Inspired by mypy's subdiagnostic at <https://github.com/python/mypy/blob/1b6ebb17b7fe64488a7b3c3b4b0187bb14fe331b/mypy/messages.py#L1307-L1318>
let eq_subdiagnostics = [
format_args!(
"It is recommended for `{member}` to work with arbitrary objects, for example:",
),
format_args!(""),
format_args!(" def {member}(self, other: object) -> bool:"),
format_args!(" if not isinstance(other, {class_name}):"),
format_args!(" return False"),
format_args!(" return <logic to compare two `{class_name}` instances>"),
format_args!(""),
];
for subdiag in eq_subdiagnostics {
diagnostic.help(subdiag);
}
}
}
/// Reports an incompatible pair of source-defined methods in a resolved MRO.
pub(super) fn report_incompatible_base_method<'db>(
context: &InferContext<'db, '_>,
class: StaticClassLiteral<'db>,
member: &str,
selected: (ClassType<'db>, Definition<'db>, MethodDecorator),
contract: (ClassType<'db>, Definition<'db>, MethodDecorator),
error_context: impl FnOnce() -> ErrorContextTree<'db>,
) {
let db = context.db();
let Some(builder) = context.report_lint(&INVALID_METHOD_OVERRIDE, class.header_range(db))
else {
return;
};View on GitHub (pinned to 15f3fe6b15)
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.
Defensive patterns
Strategy: try-catch
Try / catch
// Embedders (LSP/library): panic isolation per diagnostic request
use std::panic::{catch_unwind, AssertUnwindSafe};
let diags = catch_unwind(AssertUnwindSafe(|| diagnostics(db, file)))
.unwrap_or_default(); // degrade to no diagnostics for this file, keep server alive Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- should be set because `extract_if` only yields elements with
- argument index should not be out of range
- every BDD constraint should have a source-order entry
- clause vector should not be empty
- extra use-def data should have been retained
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/b5f017af5a12c833.
Report an issue: GitHub.