FA-75646 / Text diff and three-way merge / Open access
Line mapping through hunks: hunks after a line are still examined · case 01
Lines before a later hunk are reported as removed.
ROOT CAUSE
The loop continues past hunks that start after the line, and the range test then matches them.
VERIFIED REPAIR
Stop examining hunks once a hunk starts after the line.
Unsuccessful approach: Stopping one line late treats the line just before a hunk as part of it.
Case contract
Hunks [old_start, old_len, new_start, new_len] use unified conventions (start is the line before when the length is 0) and are sorted. An old line maps to None if it lies in a hunk's old range; otherwise it moves by the sum of new_len - old_len over hunks entirely before it. A zero-length hunk after line s affects lines from s+1 on. Return the mapped line numbers.
Why this case matters
Review tools re-anchor comments on changed files by mapping old line numbers through the diff.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(hunks, lines):
res = []
for ln in lines:
shift = 0
out = ln
for os_, ol, ns, nl in hunks:
s = os_ if ol > 0 else os_ + 1
if ln < s + ol:
out = None
break
shift += nl - ol
res.append(None if out is None else ln + shift)
return res
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = {
1: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 1]], [5, 6, 7]], [5, 7, 8]), ('pure deletion', [[[3, 1, 2, 0]], [2, 3, 3, 4, 10]], [2, None, None, 3, 9]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 1]], [1, 1])],
2: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 2]], [5, 6, 7]], [5, 8, 9]), ('pure deletion', [[[3, 2, 2, 0]], [2, 3, 4, 5, 10]], [2, None, None, 3, 8]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 2]], [1, 2])],
3: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 3]], [5, 6, 7]], [5, 9, 10]), ('pure deletion', [[[3, 3, 2, 0]], [2, 3, 5, 6, 10]], [2, None, None, 3, 7]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 3]], [1, 3])],
4: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 4]], [5, 6, 7]], [5, 10, 11]), ('pure deletion', [[[3, 4, 2, 0]], [2, 3, 6, 7, 10]], [2, None, None, 3, 6]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 4]], [1, 4])],
5: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 5]], [5, 6, 7]], [5, 11, 12]), ('pure deletion', [[[3, 5, 2, 0]], [2, 3, 7, 8, 10]], [2, None, None, 3, 5]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 5]], [1, 5])],
}[N]
for label, args, expected in cases:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| lines before and after a replacement | [None, None, None, 8, 9] | [4, None, None, 8, 9] | Failed |
| pure insertion after line five | [None, 7, 8] | [5, 7, 8] | Failed |
| pure deletion | [None, None, None, 3, 9] | [2, None, None, 3, 9] | Failed |
| insertion at the top of the file | [3, 4] | [3, 4] | Passed |
| several hunks accumulate | [None, None, None, None, None, None, None, 24] | [1, None, 4, 10, None, 12, 19, 24] | Failed |
| line just after a hunk | [None, 12, 13] | [None, 12, 13] | Passed |
| last line of a deletion | [None, 4] | [None, 4] | Passed |
| no hunks | [1, 1] | [1, 1] | Passed |
SHA-256 / 1750a88c4c8e6f16fb4e1cce1fdf7bb6e321d28138c1012ae720d78b725839a2
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(hunks, lines):
res = []
for ln in lines:
shift = 0
out = ln
for os_, ol, ns, nl in hunks:
s = os_ if ol > 0 else os_ + 1
if ln < s - 1:
break
if ln < s + ol:
out = None
break
shift += nl - ol
res.append(None if out is None else ln + shift)
return res
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = {
1: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 1]], [5, 6, 7]], [5, 7, 8]), ('pure deletion', [[[3, 1, 2, 0]], [2, 3, 3, 4, 10]], [2, None, None, 3, 9]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 1]], [1, 1])],
2: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 2]], [5, 6, 7]], [5, 8, 9]), ('pure deletion', [[[3, 2, 2, 0]], [2, 3, 4, 5, 10]], [2, None, None, 3, 8]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 2]], [1, 2])],
3: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 3]], [5, 6, 7]], [5, 9, 10]), ('pure deletion', [[[3, 3, 2, 0]], [2, 3, 5, 6, 10]], [2, None, None, 3, 7]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 3]], [1, 3])],
4: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 4]], [5, 6, 7]], [5, 10, 11]), ('pure deletion', [[[3, 4, 2, 0]], [2, 3, 6, 7, 10]], [2, None, None, 3, 6]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 4]], [1, 4])],
5: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 5]], [5, 6, 7]], [5, 11, 12]), ('pure deletion', [[[3, 5, 2, 0]], [2, 3, 7, 8, 10]], [2, None, None, 3, 5]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 5]], [1, 5])],
}[N]
for label, args, expected in cases:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| lines before and after a replacement | [None, None, None, 8, 9] | [4, None, None, 8, 9] | Failed |
| pure insertion after line five | [None, 7, 8] | [5, 7, 8] | Failed |
| pure deletion | [None, None, None, 3, 9] | [2, None, None, 3, 9] | Failed |
| insertion at the top of the file | [3, 4] | [3, 4] | Passed |
| several hunks accumulate | [None, None, 4, None, None, 12, None, 24] | [1, None, 4, 10, None, 12, 19, 24] | Failed |
| line just after a hunk | [None, 12, 13] | [None, 12, 13] | Passed |
| last line of a deletion | [None, 4] | [None, 4] | Passed |
| no hunks | [1, 1] | [1, 1] | Passed |
SHA-256 / a3c7f1b8a4ecfafeefd54e6eb245dae0f44056680f8e7dfed12a50eba671f14e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(hunks, lines):
res = []
for ln in lines:
shift = 0
out = ln
for os_, ol, ns, nl in hunks:
s = os_ if ol > 0 else os_ + 1
if ln < s:
break
if ln < s + ol:
out = None
break
shift += nl - ol
res.append(None if out is None else ln + shift)
return res
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = {
1: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 1]], [5, 6, 7]], [5, 7, 8]), ('pure deletion', [[[3, 1, 2, 0]], [2, 3, 3, 4, 10]], [2, None, None, 3, 9]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 1]], [1, 1])],
2: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 2]], [5, 6, 7]], [5, 8, 9]), ('pure deletion', [[[3, 2, 2, 0]], [2, 3, 4, 5, 10]], [2, None, None, 3, 8]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 2]], [1, 2])],
3: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 3]], [5, 6, 7]], [5, 9, 10]), ('pure deletion', [[[3, 3, 2, 0]], [2, 3, 5, 6, 10]], [2, None, None, 3, 7]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 3]], [1, 3])],
4: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 4]], [5, 6, 7]], [5, 10, 11]), ('pure deletion', [[[3, 4, 2, 0]], [2, 3, 6, 7, 10]], [2, None, None, 3, 6]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 4]], [1, 4])],
5: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 5]], [5, 6, 7]], [5, 11, 12]), ('pure deletion', [[[3, 5, 2, 0]], [2, 3, 7, 8, 10]], [2, None, None, 3, 5]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 5]], [1, 5])],
}[N]
for label, args, expected in cases:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| lines before and after a replacement | [4, None, None, 8, 9] | [4, None, None, 8, 9] | Passed |
| pure insertion after line five | [5, 7, 8] | [5, 7, 8] | Passed |
| pure deletion | [2, None, None, 3, 9] | [2, None, None, 3, 9] | Passed |
| insertion at the top of the file | [3, 4] | [3, 4] | Passed |
| several hunks accumulate | [1, None, 4, 10, None, 12, 19, 24] | [1, None, 4, 10, None, 12, 19, 24] | Passed |
| line just after a hunk | [None, 12, 13] | [None, 12, 13] | Passed |
| last line of a deletion | [None, 4] | [None, 4] | Passed |
| no hunks | [1, 1] | [1, 1] | Passed |
SHA-256 / aab39124f240e0d5b7f21f67c12b75c82c38c65ca3b9144132e6026cc1d781f7
Verification & scope
A deterministic, bounded teaching model of one diff, patch or merge rule with stipulated conventions; it is not a production diff or version-control implementation and makes no claim of conformance to any specific tool. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:49:08.940912+00:00.
Case digest / d1cd352e0dc60fbb25a5f7ae33378f030b09938702454b9c94c43128f949678b