{"record":{"id":"6580cda51f4b296c","repo":"Wilfred/difftastic","slug":"incompatible-tree-sitter-version","errorCode":null,"errorMessage":"Incompatible tree-sitter version","messagePattern":"Incompatible tree-sitter version","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/parse/tree_sitter_parser.rs","lineNumber":1301,"sourceCode":"                atom_nodes: [\"string\"].into_iter().collect(),\n                delimiter_tokens: vec![(\"{\", \"}\"), (\"[\", \"]\"), (\"(\", \")\")]\n                    .into_iter()\n                    .collect(),\n                ignore_trailing_tokens: vec![],\n                highlight_query: ts::Query::new(&language, tree_sitter_zig::HIGHLIGHTS_QUERY)\n                    .unwrap(),\n                sub_languages: vec![],\n            }\n        }\n    }\n}\n\n/// Parse `src` with tree-sitter.\npub(crate) fn to_tree(src: &str, config: &TreeSitterConfig) -> tree_sitter::Tree {\n    let mut parser = ts::Parser::new();\n    parser\n        .set_language(&config.language)\n        .expect(\"Incompatible tree-sitter version\");\n\n    parser.parse(src, None).unwrap()\n}\n\n#[derive(Debug)]\npub(crate) struct ExceededByteLimit(pub(crate) usize);\n\npub(crate) fn to_tree_with_limit(\n    diff_options: &DiffOptions,\n    config: &TreeSitterConfig,\n    lhs_src: &str,\n    rhs_src: &str,\n) -> Result<(tree_sitter::Tree, tree_sitter::Tree), ExceededByteLimit> {\n    if lhs_src.len() > diff_options.byte_limit || rhs_src.len() > diff_options.byte_limit {\n        let num_bytes = std::cmp::max(lhs_src.len(), rhs_src.len());\n        return Err(ExceededByteLimit(num_bytes));\n    }\n","sourceCodeStart":1283,"sourceCodeEnd":1319,"githubUrl":"https://github.com/Wilfred/difftastic/blob/d8fe43b3ef8ebf55a411af9da708f28048f071cd/src/parse/tree_sitter_parser.rs#L1283-L1319","documentation":"tree_sitter::Parser::set_language returns Err(LanguageError) when the grammar's compiled ABI version (the LANGUAGE_VERSION constant baked into the grammar crate's generated parser.c) is newer than the tree-sitter runtime supports, or older than MIN_COMPATIBLE_LANGUAGE_VERSION. difftastic calls .expect(\"Incompatible tree-sitter version\") in to_tree (src/parse/tree_sitter_parser.rs:1301), so instead of a Result the process panics the first time it tries to parse a file of that language. The root cause is always a version skew between the `tree-sitter` runtime crate and one of the `tree-sitter-*` grammar crates resolved in Cargo.lock (this repo pins `tree-sitter = \"0.26.10\"` precisely because newer runtimes broke tree-sitter-solidity).","triggerScenarios":"Calling to_tree(src, config) after difftastic builds a TreeSitterConfig via from_language: ts::Parser::new() then parser.set_language(&config.language) fails when the resolved grammar crate (e.g. tree-sitter-javascript 0.25.0) was generated with a `tree-sitter generate` CLI whose ABI exceeds what the resolved runtime tree-sitter 0.26.10 can load. Concretely: a `cargo update` that advances a grammar crate past the pinned runtime (or downgrades the runtime), or a vendored/forked grammar regenerated with a different tree-sitter CLI.","commonSituations":"Running `cargo update` in difftastic or a project embedding it, which moves grammar crates and runtime out of lockstep. Pinning an older `tree-sitter` runtime while grammar crates drift newer. Using a forked grammar crate rebuilt with the latest tree-sitter CLI. Major tree-sitter releases that bump LANGUAGE_VERSION (e.g. the ABI 14-to-15 transitions) make previously working pairs fail. Installing a difftastic binary built against mismatched dependency versions.","solutions":["Realign the dependency pair in Cargo.lock: run `cargo update -p tree-sitter --precise 0.26.10` (or bump the `tree-sitter = \"0.26.10\"` requirement to the version the grammar crates were generated for), then rebuild.","If one grammar crate floated too far ahead, pin it back to the version difftastic's Cargo.toml expects, e.g. `cargo update -p tree-sitter-javascript --precise 0.25.0`.","Inspect the resolved graph with `cargo tree -i tree-sitter` to find which grammar pulls an incompatible ABI, and check the grammar's `#define LANGUAGE_VERSION` in its generated parser.c against `tree_sitter::LANGUAGE_VERSION` and `MIN_COMPATIBLE_LANGUAGE_VERSION`.","For vendored/custom grammars, regenerate them with `tree-sitter generate` using the CLI version matching the 0.26.10 runtime and rebuild.","If you are using difftastic as a binary (not embedding it), upgrade to the latest release, which ships a Cargo.lock with a tested, consistent set."],"exampleFix":"# before (Cargo.toml / Cargo.lock out of sync — runtime too old for grammar ABI)\ntree-sitter = \"0.25.0\"\ntree-sitter-javascript = \"0.25.0\"   # grammar generated with newer CLI, ABI too new for 0.25 runtime\n\n# after (runtime supports the grammar's ABI version)\ntree-sitter = \"0.26.10\"\ntree-sitter-javascript = \"0.25.0\"","handlingStrategy":"validation","validationCode":"use tree_sitter::{Language, LANGUAGE_VERSION, MIN_COMPATIBLE_LANGUAGE_VERSION};\n\nfn language_loads(lang: &Language) -> bool {\n    // tree-sitter >= 0.22 exposes lang.version(); older runtimes: tree_sitter::language_version(lang)\n    let abi = lang.version();\n    (MIN_COMPATIBLE_LANGUAGE_VERSION..=LANGUAGE_VERSION).contains(&abi)\n}\n\n// run before calling to_tree / difftastic's parse entry points\nfor config in all_language_configs() {\n    assert!(\n        language_loads(&config.language),\n        \"grammar ABI {} outside supported range {}..={}\",\n        config.language.version(),\n        MIN_COMPATIBLE_LANGUAGE_VERSION,\n        LANGUAGE_VERSION\n    );\n}","typeGuard":"fn is_abi_compatible(lang: &tree_sitter::Language) -> bool {\n    (tree_sitter::MIN_COMPATIBLE_LANGUAGE_VERSION..=tree_sitter::LANGUAGE_VERSION)\n        .contains(&lang.version())\n}","tryCatchPattern":"// set_language failure surfaces as a panic via .expect, not a Result;\n// pre-validate instead. As a last-resort containment when embedding difftastic:\nlet parsed = std::panic::catch_unwind(|| to_tree(src, config));\nmatch parsed {\n    Ok(tree) => tree,\n    Err(payload) => {\n        let msg = payload\n            .downcast_ref::<&str>()\n            .copied()\n            .or_else(|| payload.downcast_ref::<String>().map(|s| s.as_str()));\n        if msg == Some(\"Incompatible tree-sitter version\") {\n            // fall back to a text diff / skip this language, and log the ABI mismatch\n        } else {\n            std::panic::resume_unwind(payload);\n        }\n    }\n}","preventionTips":["Commit Cargo.lock and never run blanket `cargo update` on the tree-sitter dependency cluster; update runtime and grammar crates together in one reviewed change.","Add a startup check that walks every language config and asserts lang.version() is within MIN_COMPATIBLE_LANGUAGE_VERSION..=LANGUAGE_VERSION before any parsing.","Keep a CI smoke test that parses one sample file per supported language so ABI breaks fail the build, not users.","Run `cargo tree -i tree-sitter` after any dependency change to confirm a single consistent runtime version across all grammar crates.","Regenerate vendored grammars with the same tree-sitter CLI version as the runtime crate, and record that version in the repo."],"tags":["tree-sitter","abi","version-mismatch","rust","cargo","panic","difftastic"],"backgroundTag":"tree-sitter-abi-version-mismatch","analyzedSha":"d8fe43b3ef8ebf55a411af9da708f28048f071cd","analyzedAt":"2026-08-16T22:08:50.691Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}