astral-sh/ruff · critical
PySourceType always parses into a module
Error message
PySourceType always parses into a module
What it means
Ruff's unchecked parser (`parse_unchecked`) is used for linting when the source type is known; by construction every PySourceType (module, script, notebook, etc.) must yield a parse result that converts into a `Parsed` module. If `try_into_module()` returns None, an internal invariant of the parser/source-type mapping is broken, so the linter panics with this message.
Source
Thrown at crates/ruff_linter/src/linter.rs:806
/// Per-cell modules are merged so definitions remain visible across cells.
pub fn parse_unchecked_source(
source_kind: &SourceKind,
source_type: PySourceType,
target_version: PythonVersion,
) -> Parsed<ModModule> {
let options = ParseOptions::from(source_type).with_target_version(target_version);
// SAFETY: Safe because `PySourceType` always parses to a `ModModule`. See
// `ruff_python_parser::parse_unchecked_source`. We use `parse_unchecked` (and thus
// have to unwrap) in order to pass the `PythonVersion` via `ParseOptions`.
match source_kind.as_ipy_notebook() {
Some(notebook) => ruff_python_parser::parse_cells_unchecked(
source_kind.source_code(),
notebook.cell_offsets().content_ranges(),
&options,
),
None => ruff_python_parser::parse_unchecked(source_kind.source_code(), options)
.try_into_module()
.expect("PySourceType always parses into a module"),
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use ruff_python_ast::{PySourceType, PythonVersion};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_trivia::textwrap::dedent;
use test_case::test_case;
use ruff_db::diagnostic::Diagnostic;
use ruff_notebook::{Notebook, NotebookError};
use crate::linter::{check_path, parse_unchecked_source};View on GitHub (pinned to 26f38c119c)
Solutions
- Report the crash to the Ruff repository with the input file that triggered it — this is an internal invariant violation, not a user error
- Upgrade/downgrade Ruff to a version where the parser regression is fixed
- Work around by linting the file as a plain module (avoid the notebook/Percent format detection, e.g. fix the file extension or cell markers)
- If developing: verify the source_type and options passed to parse_unchecked match (e.g. don't pass notebook options with SourceType::Module)
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure source_kind and options agree before calling the linter let options = source_type_to_options(source_type.source_type()); assert!(matches!(source_type, PySourceType::Python | PySourceType::Ipynb));
Type guard
fn is_supported_source(kind: &PySourceType) -> bool {
matches!(kind, PySourceType::Python | PySourceType::Ipynb)
} Prevention
- Keep source_type and parser options derived from the same value
- Fuzz the parser with notebook/percent-format inputs in CI
- Report any panic here to Ruff with the reproducing file
When it happens
Trigger: Calling `parse_unchecked_source` (directly or via `Linter::run`, `lint_fix`, `into_parsed`, or add-suppressions paths) with a source_type/parser-options combination for which `parse_unchecked` returns tokens that fail `try_into_module` — i.e. only reachable through an internal bug or a mismatched source kind (e.g. feeding IPython-cell options vs a notebook source inconsistently).
Common situations: Encountered by Ruff/ty contributors changing parser APIs or source-kind handling; end users only see it as a crash report when a new parser version regresses notebook or magic-cell handling.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- expected class
- expected function
- expected type alias
- Expected `NamedTuple` definition r.h.s. to be a call express
- i is bounded by INLINE_MAX_SEGMENTS
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/5d7637f46992f20d.
Report an issue: GitHub.