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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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


AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20). Data as JSON: /api/errors/b5f017af5a12c833. Report an issue: GitHub.