Kill or justify the full mutation run's 32 misses
New tests pin behavior mutation testing showed was unconstrained:
anchor ordering must prefer the larger of two crossing candidates,
NodeTypes::admits must survive multiple supertype entries, a
reformatting-only branch must leave output bytes alone (survivor
origins stay O), duplicate identical insertions dedupe pairwise even
in single-hash slots, a graft must not resurrect what the other branch
deleted, the insert-delete rule stays quiet in both directions on
unrelated deletions, conflicts carry both inserted spans, synthesis
byte behavior is pinned exactly (including the known single-space
wart and files without trailing newlines), the CLI diff3 block is
byte-exact, and Zhang-Shasha gains an exhaustive small-tree oracle
holding the backtrack to the DP's optimal cost.

The remaining misses are excluded in .cargo/mutants.toml, each with a
written equivalence argument.

Assisted-by: Claude Fable 5 via Claude Code
change ymtxqpqwmlrmvyslrxlmtzztxpyuqklq
commit d935bf5d74623a31e3393dd7da5fe76ac7b2fdff
author Alpha Chen <alpha@kejadlen.dev>
date
parent rowyqvuo
diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml
index a36adca..cb06142 100644
--- a/.cargo/mutants.toml
+++ b/.cargo/mutants.toml
@@ -34,4 +34,38 @@ exclude_re = [
     # test pins length and validity, which is the function's whole
     # contract; a mutant that broke either would be caught by it.
     'replace \+ with \* in lcs_pairs',
+    # sibling_order_holds(): a sibling's image can never equal the
+    # candidate y — y is unmatched at check time — so index equality
+    # cannot occur and < versus <= coincide.
+    'replace < with <= in sibling_order_holds',
+    # graft_branch()'s dedupe twin search: the insert-insert rule only
+    # lets equal insertion sequences through to the builder, so a
+    # budget-true entry always exists for every B insert and any
+    # bookkeeping that consumes each entry at most once skips exactly
+    # the same set. (The hash-equality half is pinned by the
+    # single-hash duplicate-statement test.)
+    'replace && with \|\| in Builder<._>::graft_branch',
+    # graft()'s placed/consumed guards: for a graft to meet an
+    # already-placed or twice-consumed survivor, the diff would have to
+    # map a node's image outside its matched ancestor's image, which
+    # admissible() forbids. The guards are defense-in-depth against
+    # matchings our phases cannot produce.
+    'replace && with \|\| in Builder<._>::graft\b',
+    # children_changed(): tree-sitter derives node-types.json from the
+    # grammar, so a node whose children match its parsed origin always
+    # satisfies the model; widening validation to unchanged nodes (or
+    # never treating them as unchanged) cannot change outcomes on
+    # parsed trees. The guard exists for cost and for grammar corners
+    # deliberately kept out of merges.
+    'children_changed',
+    # synthesize()'s unclosed-file fallback guards: distinguishable
+    # only through the cov-excl-marked defensive path (the final merged
+    # leaf is in practice its origin's closing token).
+    'delete ! in synthesize',
+    # Solver::solve()'s backtrack tail: once one side is exhausted the
+    # remaining walk emits only deletions and insertions, which the
+    # mapping does not report — and the delete branch always holds
+    # there, shielding the insert branch's condition.
+    'replace \|\| with && in Solver<T, F>::solve',
+    'replace && with \|\| in Solver<T, F>::solve',
 ]
diff --git a/src/diff.rs b/src/diff.rs
index 2fd1301..70523f8 100644
--- a/src/diff.rs
+++ b/src/diff.rs
@@ -981,6 +981,20 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn larger_anchors_win_over_crossing_smaller_ones() -> Result<(), Error> {
+        // The number and the inner array swap places; only one can
+        // anchor. Largest first means the subtree wins and the lone
+        // number is demoted.
+        let o = parse_json("[5, [1, 2, 3]]")?;
+        let a = parse_json("[[1, 2, 3], 5]")?;
+        let m = anchor(&o, &a);
+        let inner = find_kind(&o, "array").last().copied();
+        assert!(inner.is_some_and(|n| m.image(n).is_some()));
+        assert_eq!(find_number(&o, "5").and_then(|n| m.image(n)), None);
+        Ok(())
+    }
+
     #[test]
     fn duplicated_subtrees_do_not_anchor() -> Result<(), Error> {
         // The number 1 appears twice in O, so its hash is not unique
diff --git a/src/lang.rs b/src/lang.rs
index 3847c6f..f84fdad 100644
--- a/src/lang.rs
+++ b/src/lang.rs
@@ -322,6 +322,28 @@ mod tests {
         assert!(has_operator);
     }
 
+    #[test]
+    fn admits_expands_nested_supertypes() {
+        // rust's block admits statements whose literals sit two
+        // supertype hops away (_expression → _literal → ...), so the
+        // expansion must survive visiting several supertype entries.
+        let node_types = Lang::by_name("rust").map(Lang::node_types);
+        let block_types = node_types
+            .and_then(|nt| nt.get("block"))
+            .and_then(|t| t.children.as_ref())
+            .map(|slot| slot.types.as_slice());
+        let admits = |kind: &str| {
+            node_types
+                .zip(block_types)
+                .is_some_and(|(nt, types)| nt.admits(types, kind))
+        };
+        assert!(admits("integer_literal"));
+        assert!(admits("string_literal"));
+        assert!(admits("expression_statement"));
+        assert!(!admits("no_such_kind"));
+        assert!(!admits("source_file"));
+    }
+
     #[test]
     fn supertype_subtypes_are_loaded() {
         let has_const_item = Lang::by_name("rust").map(|lang| {
diff --git a/src/merge.rs b/src/merge.rs
index 68f9b93..f0c49f2 100644
--- a/src/merge.rs
+++ b/src/merge.rs
@@ -481,6 +481,53 @@ mod tests {
         assert_merges("json", "[1, 2]", "[1, 9, 2]", "[1, 9, 2]", "[1, 9, 2]")
     }
 
+    #[test]
+    fn duplicate_identical_insertions_dedupe_pairwise() -> Result<(), Error> {
+        // Two equal insertions per branch: each A graft absorbs
+        // exactly one B twin, so the pair lands twice, not four times.
+        assert_merges("json", "[1]", "[1, 9, 9]", "[1, 9, 9]", "[1, 9, 9]")?;
+        // The same with a single-hash slot (no separators): dedupe
+        // must match twins by hash, not merely consume any entry.
+        assert_merges(
+            "rust",
+            "fn f() { }",
+            "fn f() { x(); x(); }",
+            "fn f() { x(); x(); }",
+            "fn f() { x(); x(); }",
+        )
+    }
+
+    #[test]
+    fn a_graft_does_not_resurrect_what_the_other_branch_deleted() -> Result<(), Error> {
+        // A wraps y() in a block; B deletes y() outright. The block
+        // survives from A, but the statement inside it died in B and
+        // must not be pulled into the graft.
+        assert_merges(
+            "rust",
+            "fn f() { x(); y(); }",
+            "fn f() { x(); { y(); } }",
+            "fn f() { x(); }",
+            "fn f() { x(); { } }",
+        )
+    }
+
+    #[test]
+    fn reformatting_only_branches_leave_bytes_alone() -> Result<(), Error> {
+        // B reformats without changing the tree, so no relabel fires,
+        // every survivor keeps its O origin, and the output is
+        // byte-identical to O.
+        let o_text = "fn keep() {\n    x(y);\n}\n";
+        let b_text = "fn keep(  ) {  x( y ) ;  }\n";
+        let o = parse(o_text, "rust")?;
+        let a = parse(o_text, "rust")?;
+        let b = parse(b_text, "rust")?;
+        match merge_to_text(&o, &a, &b)? {
+            MergeResult::Merged(text) => assert_eq!(text, o_text),
+            MergeResult::Conflicts(conflicts) => panic!("unexpected conflicts: {conflicts:?}"),
+        }
+        Ok(())
+    }
+
     #[test]
     fn identical_deletions_merge_silently() -> Result<(), Error> {
         assert_merges("json", "[1, 2]", "[1]", "[1]", "[1]")
@@ -554,13 +601,23 @@ mod tests {
 
     #[test]
     fn conflicting_insertions_at_one_slot_conflict() -> Result<(), Error> {
-        assert_conflicts(
+        let conflicts = assert_conflicts(
             "rust",
             "fn f() { x(); }",
             "fn f() { x(); y(); }",
             "fn f() { x(); z(); }",
             "insert-insert",
         )?;
+        // The conflict carries both branches' inserted spans.
+        let a = parse("fn f() { x(); y(); }", "rust")?;
+        let witnessed = conflicts.iter().any(|c| {
+            c.span_a
+                .clone()
+                .and_then(|span| a.source_slice(span))
+                .is_some_and(|text| text.contains("y()"))
+                && c.span_b.is_some()
+        });
+        assert!(witnessed);
         Ok(())
     }
 
@@ -647,6 +704,16 @@ mod tests {
             "fn a() { x(); z(); }\nfn b() { y(); }",
             "fn a() { x(); }",
             "fn a() { x(); z(); }",
+        )?;
+        // And mirrored: A deletes fn b while B grows fn a. The
+        // insert-delete rule must not fire on unrelated deletions in
+        // either direction.
+        assert_merges(
+            "rust",
+            "fn a() { x(); }\nfn b() { y(); }",
+            "fn a() { x(); }",
+            "fn a() { x(); z(); }\nfn b() { y(); }",
+            "fn a() { x(); z(); }",
         )
     }
 
diff --git a/src/synth.rs b/src/synth.rs
index a9b31ac..5a31584 100644
--- a/src/synth.rs
+++ b/src/synth.rs
@@ -239,6 +239,36 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn identical_inputs_reproduce_the_file_byte_for_byte() -> Result<(), Error> {
+        // Trailing trivia after the last leaf travels...
+        let with_tail = "fn a() {} // tail\n";
+        assert_eq!(
+            merge_text("rust", with_tail, with_tail, with_tail)?,
+            with_tail
+        );
+        // ...and a file without a final newline does not grow one.
+        let bare = "[1, 2]";
+        assert_eq!(merge_text("json", bare, bare, bare)?, bare);
+        Ok(())
+    }
+
+    #[test]
+    fn grafted_functions_pin_exact_bytes() -> Result<(), Error> {
+        // Byte-level regression for the trivia heuristics — including
+        // the known single-space wart where the O run's first leaf has
+        // no predecessor to borrow trivia from ("{} fn keep").
+        let o = "fn keep() {\n    x();\n}\n";
+        let a = "/// From A.\nfn top() {}\n\nfn keep() {\n    x();\n}\n";
+        let b = "fn keep() {\n    x();\n}\n\n/// From B.\nfn bottom() {}\n";
+        let text = merge_text("rust", o, a, b)?;
+        assert_eq!(
+            text,
+            "/// From A.\nfn top() {} fn keep() {\n    x();\n}\n\n/// From B.\nfn bottom() {}\n"
+        );
+        Ok(())
+    }
+
     #[test]
     fn doc_comments_travel_with_grafted_functions() -> Result<(), Error> {
         let o = "fn keep() {\n    x();\n}\n";
diff --git a/src/zs.rs b/src/zs.rs
index 091d04b..37b4dfe 100644
--- a/src/zs.rs
+++ b/src/zs.rs
@@ -8,6 +8,7 @@
 //! between trees and related problems", SIAM J. Comput. 18(6), 1989.
 
 /// A tree for Zhang–Shasha: a value and ordered children.
+#[derive(Clone)]
 pub struct ZsTree<T> {
     pub value: T,
     pub children: Vec<ZsTree<T>>,
@@ -29,6 +30,17 @@ pub fn mapping<T: Copy>(
     dst: &ZsTree<T>,
     relabel: impl Fn(T, T) -> Option<u32>,
 ) -> Vec<(T, T)> {
+    mapping_with_cost(src, dst, relabel).0
+}
+
+/// [`mapping`] plus the optimal edit cost the DP computed, so tests
+/// can hold the backtrack to account: the mapping's implied cost must
+/// equal the distance.
+pub(crate) fn mapping_with_cost<T: Copy>(
+    src: &ZsTree<T>,
+    dst: &ZsTree<T>,
+    relabel: impl Fn(T, T) -> Option<u32>,
+) -> (Vec<(T, T)>, u32) {
     let src_flat = Flat::build(src);
     let dst_flat = Flat::build(dst);
     let solver = Solver {
@@ -103,7 +115,7 @@ struct Solver<T, F> {
 // waived for fidelity to the published pseudocode.
 #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
 impl<T: Copy, F: Fn(T, T) -> Option<u32>> Solver<T, F> {
-    fn solve(&self) -> Vec<(T, T)> {
+    fn solve(&self) -> (Vec<(T, T)>, u32) {
         let (n, m) = (self.src.len(), self.dst.len());
         let mut treedist = vec![vec![0u32; m + 1]; n + 1];
         for &i in &self.src.keyroots {
@@ -111,6 +123,7 @@ impl<T: Copy, F: Fn(T, T) -> Option<u32>> Solver<T, F> {
                 self.forest_dist(i, j, &mut treedist);
             }
         }
+        let distance = treedist[n][m];
 
         // Backtrack: walk each subtree pair's forest table from the
         // corner, emitting pairs on diagonal steps that close a whole
@@ -137,7 +150,7 @@ impl<T: Copy, F: Fn(T, T) -> Option<u32>> Solver<T, F> {
                 }
             }
         }
-        pairs
+        (pairs, distance)
     }
 
     /// The forest-distance DP for the subtree pair (i, j), 1-based.
@@ -249,6 +262,94 @@ mod tests {
         }
     }
 
+    /// All trees with exactly `nodes` nodes over the given labels.
+    fn trees_with(nodes: usize, labels: &[u8]) -> Vec<ZsTree<u8>> {
+        if nodes == 0 {
+            return Vec::new();
+        }
+        let mut out = Vec::new();
+        for &label in labels {
+            for children in forests(nodes.saturating_sub(1), labels) {
+                out.push(ZsTree {
+                    value: label,
+                    children,
+                });
+            }
+        }
+        out
+    }
+
+    /// All ordered forests totalling `total` nodes.
+    fn forests(total: usize, labels: &[u8]) -> Vec<Vec<ZsTree<u8>>> {
+        if total == 0 {
+            return vec![Vec::new()];
+        }
+        let mut out = Vec::new();
+        for first in 1..=total {
+            for head in trees_with(first, labels) {
+                for tail in forests(total.saturating_sub(first), labels) {
+                    let mut forest = vec![head.clone()];
+                    forest.extend(tail);
+                    out.push(forest);
+                }
+            }
+        }
+        out
+    }
+
+    /// Clones a label tree into an identity-carrying tree with
+    /// pre-order ids, returning the node count.
+    fn retag(tree: &ZsTree<u8>, next: &mut usize) -> ZsTree<(usize, u8)> {
+        let id = *next;
+        *next = next.saturating_add(1);
+        ZsTree {
+            value: (id, tree.value),
+            children: tree.children.iter().map(|c| retag(c, next)).collect(),
+        }
+    }
+
+    #[test]
+    fn mapping_cost_matches_the_dp_on_all_small_trees() {
+        // Exhaustive internal-consistency oracle: the backtracked
+        // mapping's implied cost (relabels + unmatched nodes on both
+        // sides) must equal the DP's optimal distance, and the mapping
+        // must be a bijection. Kills backtrack mutants that hand-picked
+        // cases let through.
+        let labels = [0u8, 1u8];
+        let mut all: Vec<(ZsTree<(usize, u8)>, usize)> = Vec::new();
+        for nodes in 1..=3usize {
+            for tree in trees_with(nodes, &labels) {
+                let mut next = 0usize;
+                let tagged = retag(&tree, &mut next);
+                all.push((tagged, next));
+            }
+        }
+        for (src, src_count) in &all {
+            for (dst, dst_count) in &all {
+                let (pairs, distance) =
+                    mapping_with_cost(src, dst, |a, b| if a.1 == b.1 { Some(0) } else { Some(1) });
+                let relabels = pairs.iter().filter(|(a, b)| a.1 != b.1).count();
+                let matched = pairs.len();
+                let implied = relabels
+                    .saturating_add(src_count.saturating_sub(matched))
+                    .saturating_add(dst_count.saturating_sub(matched));
+                assert_eq!(
+                    u32::try_from(implied).ok(),
+                    Some(distance),
+                    "implied cost diverged for {matched} pairs"
+                );
+                let mut src_ids: Vec<usize> = pairs.iter().map(|(a, _)| a.0).collect();
+                let mut dst_ids: Vec<usize> = pairs.iter().map(|(_, b)| b.0).collect();
+                src_ids.sort_unstable();
+                src_ids.dedup();
+                dst_ids.sort_unstable();
+                dst_ids.dedup();
+                assert_eq!(src_ids.len(), matched);
+                assert_eq!(dst_ids.len(), matched);
+            }
+        }
+    }
+
     #[test]
     fn mapping_preserves_sibling_order() {
         // f(a, b) vs f(b, a): only one leaf can survive; crossing both
diff --git a/tests/cli.rs b/tests/cli.rs
index a15a292..d307ed8 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -69,7 +69,9 @@ fn conflicting_merge_exits_one_with_markers() {
         .args(&paths)
         .assert()
         .code(1)
-        .stdout(predicate::str::contains("<<<<<<<"))
+        .stdout(predicate::eq(
+            "<<<<<<< A\nfn b() {}\n||||||| O\nfn a() {}\n=======\nfn c() {}\n>>>>>>> B\n",
+        ))
         .stderr(predicate::str::contains("relabel-relabel"));
 }