{"record":{"id":"5d7637f46992f20d","repo":"astral-sh/ruff","slug":"pysourcetype-always-parses-into-a-module","errorCode":null,"errorMessage":"PySourceType always parses into a module","messagePattern":"PySourceType always parses into a module","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/ruff_linter/src/linter.rs","lineNumber":806,"sourceCode":"/// Per-cell modules are merged so definitions remain visible across cells.\npub fn parse_unchecked_source(\n    source_kind: &SourceKind,\n    source_type: PySourceType,\n    target_version: PythonVersion,\n) -> Parsed<ModModule> {\n    let options = ParseOptions::from(source_type).with_target_version(target_version);\n    // SAFETY: Safe because `PySourceType` always parses to a `ModModule`. See\n    // `ruff_python_parser::parse_unchecked_source`. We use `parse_unchecked` (and thus\n    // have to unwrap) in order to pass the `PythonVersion` via `ParseOptions`.\n    match source_kind.as_ipy_notebook() {\n        Some(notebook) => ruff_python_parser::parse_cells_unchecked(\n            source_kind.source_code(),\n            notebook.cell_offsets().content_ranges(),\n            &options,\n        ),\n        None => ruff_python_parser::parse_unchecked(source_kind.source_code(), options)\n            .try_into_module()\n            .expect(\"PySourceType always parses into a module\"),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::Path;\n\n    use anyhow::Result;\n    use ruff_python_ast::{PySourceType, PythonVersion};\n    use ruff_python_codegen::Stylist;\n    use ruff_python_index::Indexer;\n    use ruff_python_trivia::textwrap::dedent;\n    use test_case::test_case;\n\n    use ruff_db::diagnostic::Diagnostic;\n    use ruff_notebook::{Notebook, NotebookError};\n\n    use crate::linter::{check_path, parse_unchecked_source};","sourceCodeStart":788,"sourceCodeEnd":824,"githubUrl":"https://github.com/astral-sh/ruff/blob/26f38c119cac42e4d320ba08f09224fdec74af2c/crates/ruff_linter/src/linter.rs#L788-L824","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// ensure source_kind and options agree before calling the linter\nlet options = source_type_to_options(source_type.source_type());\nassert!(matches!(source_type, PySourceType::Python | PySourceType::Ipynb));","typeGuard":"fn is_supported_source(kind: &PySourceType) -> bool {\n    matches!(kind, PySourceType::Python | PySourceType::Ipynb)\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","panic","parser","invariant","internal"],"backgroundTag":"internal-invariant-violation","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"}