Repository navigation
Description
This was generated by AI during triage.
Problem
lsc regen --backend=dafny merges a new generation into the proof file with git merge-file <dfy> <base> <gen> (tools/src/dafny-commands.ts:200). That call uses Git's default Myers diff. When a large handwritten proof block sits between two generated declarations, the default diff stops searching for the shortest edit script and treats some unchanged generated lines after the block as deleted from the .dfy and reinserted. If the new generation changes one of those lines, regen reports a conflict, restores the .dfy, and exits 1, even though no handwritten line touches the changed line.
The additions-only check in the same file already runs git diff --minimal (tools/src/dafny-commands.ts:54, added in 08fdfee), so the check and the merge disagree about which generated lines a .dfy kept. In the case below the check accepts the .dfy, and the merge then fails on it.
In that case the .dfy is the old generation plus 1,184 inserted proof lines, and it verifies. The only generation change is one line inside preempts. From the old generation to the .dfy, git diff --no-index reports 2,396 insertions and 95 deletions with the default algorithm, and 2,301 insertions and no deletions with --minimal, --histogram or --patience. With Git 2.50.1, git merge-file on the same three files conflicts with the default and with --diff-algorithm=myers. It merges cleanly with --diff-algorithm=minimal, histogram or patience, and the merged file differs from the .dfy only in the preempts line. The conflict hunk pairs the new preempts line with the body of an unrelated handwritten predicate, ChildrenActive.
Deleting lines from the three files until no single deletion preserves the conflict left 31 generated lines and 513 proof lines. A small hand-written example therefore does not reproduce it; the proof block has to be large.
Reproduced with LemmaScript 0.6.8 (36eb31d) and 0.6.6. The git merge-file call is unchanged on main at 6b18f23.
Reproduction
The model is public in robertpenner/xstate at e9042e7. That commit keeps all generated declarations contiguous. The script moves a 1,184-line proof block back between them, which is the layout that conflicted.
git clone https://github.com/robertpenner/xstate && cd xstate && git checkout e9042e7
cd packages/core/test/machine-model
f=formal/machineModel.dfy
{ sed -n '1,1181p' $f; sed -n '1379,2562p' $f; sed -n '1182,1378p' $f; sed -n '2563,$p' $f; } > tmp && mv tmp $f
lsc check --backend=dafny machineModel.ts # 454 verified, 0 errors
sed -i.bak 's/!below(candidate.node.path, kept.node.path)/!below(kept.node.path, candidate.node.path)/' machineModel.ts
lsc regen --backend=dafny --no-verify machineModel.tsGen changed. Three-way merging...
CONFLICT: machineModel.dfy — merge had conflicts, dfy restored. See machineModel.dfy.merged
On Git 2.44 or later, git merge-file --diff-algorithm=histogram formal/machineModel.dfy formal/machineModel.dfy.base formal/machineModel.dfy.gen on the files regen leaves behind exits 0.
Expected
When the .dfy passes the additions-only check, a generation change merges without conflict unless a handwritten line actually overlaps the changed generated lines. Today the only workaround is to keep every generated declaration in one contiguous prefix of the .dfy, which forces proof text away from the code it is about.
Possible fixes, for the maintainers to choose:
- Pass
--diff-algorithm=minimalorhistogramtogit merge-file. Git added the option in 2.44; Git 2.39 rejects it with exit 129, which the currente.status > 0branch would report as a conflict, so this needs a version check or a fallback. - Merge from the additions-only structure: a
.dfythat passed the check contains the old generation as a subsequence, soregencould locate the old generated lines from that alignment instead of from a fresh text diff.
Acceptance criteria
- The reproduction above merges without conflict, and the merged
.dfydiffers from the original only in thepreemptsline. - A regression test changes one generated line that follows a large inserted proof block and expects a clean merge.
- A real conflict, where a handwritten edit overlaps the changed generated lines, still exits 1 and writes
.dfy.merged. - If the fix uses a Git option,
regenon a Git version without it either falls back to today's behavior or reports the version requirement. It must not report a conflict caused by the rejected option.
Activity
Sequela02 commentedon Nov 13, 2026
I reproduced this on
mainat553fdff(0.6.8) with Dafny 4.11.0 and Git 2.56.1, with LF files. After the script,lsc checkreports 454 verified, 0 errors, andregenconflicts. On the files thatregenleaves behind,git merge-fileconflicts with the default algorithm and with--diff-algorithm=myers. It merges cleanly withminimal,histogramandpatience, and the merged file differs from the.dfyonly in thepreemptsline. My insertion counts differ from the ones in the issue: from.dfy.baseto.dfy, I get 2,470 with--minimaland 2,565 with the default. The 95 deletions match.I suggest
--diff-algorithm=minimal. The additions-only check requires the old generation to be a subsequence of the.dfy. In that case, a minimal diff from.dfy.baseto.dfycontains only insertions.histogramandpatiencedon't guarantee that, even though both merge this case.minimalis also the algorithm the check already uses (#214).Two related points:
git merge-fileexits with the number of conflicts, capped at 127, and with other values on errors. Today,e.status > 0reports all of them as a conflict. With a missing file (exit 255),regenprintsCONFLICTand copies the unmerged.dfyto.dfy.merged. Ifregentreats only 1 to 127 as a conflict and prints Git's error otherwise, it reports errors correctly. That also covers the issue's last acceptance criterion.--diff-algorithmwith exit 129. Ubuntu 24.04 ships Git 2.43, Ubuntu 22.04 ships 2.34, and Debian 12 ships 2.39. There are two options. On exit 129,regencan merge once without the option, as it does today, and print that Git 2.44 or later avoids these conflicts. Orregencan require Git 2.44 and say so. The first option keepsregenworking on those systems.For the regression test, small synthetic cases didn't conflict. The test would probably need the reduction described in the issue (31 generated lines and 513 proof lines). Is a fixture derived from the xstate model acceptable?
I can open a pull request with either option.
namin commentedon Nov 13, 2026
Thank you both. There's a PR in progress for it: #296
Please do chime in on the PR. @Sequela02 I didn't see your message only after pushing my attempt. I think it essentially follows your suggestions.
namin commentedon Nov 13, 2026
Only tangentially related, I am curious about your thoughts on this change on top of the fix: #298