Header template copies are not regressions while the header is unchanged

A template or inline function defined in a header is emitted by every unit
that uses it; main records its best copy at one of them. Editing that unit
could drop or vary its copy and refuse the submission, although the other
units still emit the good one (16 batches in an hour, mostly stpl.h).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
This commit is contained in:
Saty 2026-10-18 22:00:01 +02:00
commit abba0f5f06
2 changed files with 58 additions and 3 deletions

View file

@ -239,7 +239,7 @@ export class Verifier {
}
/** Functions that match on main but would no longer match with this change. */
regressions(check) {
regressions(check, files = {}) {
const results = new Map([...bestResults(check)].map(([id, b]) => [id, b.f]));
const compiledFiles = new Set(check.units.map((u) => u.file));
const out = [];
@ -252,7 +252,35 @@ export class Verifier {
out.push({ id: row.symbol_id, file: row.file, was: 'peer-confirmed', now: `changed (score ${(row.score * 100).toFixed(1)}% -> ${(now_.score * 100).toFixed(1)}%)` });
}
}
return out;
return this.withoutHeaderCopies(out, results, files);
}
// A template or inline function defined in a header is emitted by every unit that uses it, and
// main keeps its best copy, recorded at one of those units. Editing that unit can drop or vary its
// copy while the others stay as they were: no regression unless the header itself changed (and
// then "removed" only when its annotation is gone from the header).
withoutHeaderCopies(found, results, files) {
if (!found.length) return found;
const defined = new Map();
for (const r of found) if (results.get(r.id)?.file) defined.set(r.id, results.get(r.id).file);
const unknown = found.filter((r) => !defined.has(r.id)).map((r) => r.id);
if (unknown.length) {
try {
const out = this.repo.git(['grep', '-o', '-w', '-F', ...unknown.flatMap((id) => ['-e', `FUNCTION: ${id}`]), 'HEAD', '--']);
for (const line of out.split('\n')) {
const m = /^HEAD:(.+?):FUNCTION: (\S+)$/.exec(line.trim());
if (m && !defined.has(m[2])) defined.set(m[2], m[1]);
}
} catch {
// no annotation on main for any of them (git grep exits 1): judged as before
}
}
return found.filter((r) => {
const file = defined.get(r.id);
if (!file || isUnit(file)) return true;
if (typeof files[file] !== 'string') return false;
return r.now !== 'removed' || !new RegExp(`FUNCTION: ${r.id}\\b`).test(files[file]);
});
}
/**
@ -393,7 +421,7 @@ export class Verifier {
for (const w of annWarnings) feedback.warnings.push(`${w.file}:${w.line} ${w.message}`);
const failed = check.units.filter((u) => !u.compiled);
const target = check.units.flatMap((u) => u.functions.map((f) => ({ ...f, file: u.file }))).find((f) => f.id === targetId);
const regressions = this.regressions(check);
const regressions = this.regressions(check, files);
feedback.target = target ?? null;
feedback.annotationErrors = annErrors;

27
test/regressions.test.js Normal file
View file

@ -0,0 +1,27 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { Verifier } from '../src/server/verify.js';
// A header's template copy that one unit stops emitting (or emits differently) is not a regression
// unless the header changed; a unit's own function is.
test('header-defined copies are not regressions while the header is unchanged', () => {
const grep = 'HEAD:Engine/H/stpl.h:FUNCTION: fn_00002\nHEAD:Engine/H/stpl.h:FUNCTION: fn_00003\n';
const v = Object.create(Verifier.prototype);
v.repo = { git: () => grep };
const found = [
{ id: 'fn_00001', file: 'Engine/A.cpp', was: 'exact', now: 'mismatch' },
{ id: 'fn_00002', file: 'Engine/A.cpp', was: 'exact', now: 'removed' },
{ id: 'fn_00003', file: 'Engine/A.cpp', was: 'exact', now: 'removed' },
{ id: 'fn_00004', file: 'Engine/A.cpp', was: 'exact', now: 'mismatch' },
];
const results = new Map([
['fn_00001', { file: 'Engine/A.cpp' }],
['fn_00004', { file: 'Engine/H/other.h' }],
]);
assert.deepEqual(v.withoutHeaderCopies(found, results, { 'Engine/A.cpp': '' }).map((r) => r.id), ['fn_00001']);
// Header changed: a removed annotation counts, a still-annotated copy not compiled here does not.
const header = '// FUNCTION: fn_00002 <A>\ntemplate<class T> void f() {}\n';
const files = { 'Engine/A.cpp': '', 'Engine/H/stpl.h': header, 'Engine/H/other.h': '' };
assert.deepEqual(v.withoutHeaderCopies(found, results, files).map((r) => r.id), ['fn_00001', 'fn_00003', 'fn_00004']);
});