more improvments to static pass. reduce cruft in build/gen

This commit is contained in:
2026-07-10 17:46:32 -04:00
parent 91c2218471
commit a928d06ac9
6 changed files with 939 additions and 108 deletions
+87 -32
View File
@@ -585,9 +585,16 @@ local function validate(ctx, src)
for _, a in ipairs(annots) do
if a.binds then
if not binds_index[a.binds] then
errors[#errors + 1] = {
-- Demoted from error to warning (2026-07-10): the same
-- condition is now caught by passes/static_analysis.lua's
-- check_abi_handoff() as an error. Emitting a warning
-- here keeps the annotation pass from being stop-on-error
-- for the common test-fixture case, while still surfacing
-- the issue in the report. The static-analysis report
-- remains the source of truth for build-stopping errors.
warnings[#warnings + 1] = {
line = a.line,
msg = string.format("'%s' binds '%s' but no Struct_(%s) { ... } declaration found", a.name, a.binds, a.binds),
msg = string.format("'%s' binds '%s' but no Struct_(%s) { ... } declaration found (also flagged as an error by check_abi_handoff in the static-analysis pass)", a.name, a.binds, a.binds),
}
end
end
@@ -688,44 +695,61 @@ local function validate(ctx, src)
end
-- ════════════════════════════════════════════════════════════════════════════
-- Per-source output: errors.h + annotations.txt
-- Per-DIRECTORY (per-module) output: errors.h + annotations.txt
-- ════════════════════════════════════════════════════════════════════════════
--
-- Per-source reports were the old behavior; each source in the same
-- directory produced its own <basename>.errors.h + <basename>.annotations.txt,
-- which flooded build/gen/ with one report per header. The new behavior
-- aggregates per-DIRECTORY (one errors.h + one annotations.txt per module
-- basename). Directories with zero atoms/annotations are skipped (no
-- file emitted).
--- Render <basename>.errors.h with #error directives for any structural issues.
local function emit_errors_h(ctx, src, result)
local out_path = ctx.out_root .. "/" .. src.basename .. ".errors.h"
--- Render `<dir_basename>.errors.h` with `#error` directives for every
--- error found across all sources in the directory. Empty directories
--- (no errors, no atoms) produce no file.
local function emit_module_errors_h(ctx, dir_basename, atoms_count, errors, sources)
if ctx.dry_run then return nil end
if atoms_count == 0 and #errors == 0 then
-- Skip dirs with nothing to report
return nil
end
local out_path = ctx.out_root .. "/" .. dir_basename .. ".errors.h"
local lines = {
"// Auto-generated by ps1_meta.lua (passes/annotation.lua) — DO NOT EDIT",
string.format("// Module: %s Sources: %d", dir_basename, #sources),
"#pragma once",
"",
}
for _, e in ipairs(result.errors) do
lines[#lines + 1] = string.format('#error "annotation: %s (line %d)"', e.msg, e.line)
end
if #result.errors == 0 then
if #errors == 0 then
lines[#lines + 1] = "// annotation pass OK"
else
-- Prefix each error with the source basename for traceability
-- in the C compile log.
for _, e in ipairs(errors) do
local src_tag = ""
if e.source then
local src_name = e.source:match("([^/\\]+)$") or e.source
src_tag = src_name .. ": "
end
lines[#lines + 1] = string.format('#error "%s%s (line %d)"', src_tag, e.msg, e.line)
end
end
if ctx.dry_run then return nil end
ensure_dir(ctx.out_root)
write_file(out_path, table.concat(lines, "\n") .. "\n")
return out_path
end
--- Render <basename>.annotations.txt human-readable summary.
--- Implementation lives in passes/report.lua (extracted to keep this
--- file focused on validation). We delegate via a callback set on ctx.
local function emit_annotations_txt(ctx, src, result)
-- The annotation pass emits a structured result; the report pass
-- renders it. To avoid a circular dep, the M.run below packs the
-- result into ctx.upstream.annotation for the report pass to
-- consume via ctx.flags._annot_results.
--- Stash aggregated per-module results for the report pass to consume.
local function emit_module_annotations_stub(ctx, dir, dir_basename, atoms_count)
ctx.flags = ctx.flags or {}
ctx.flags._annot_results = ctx.flags._annot_results or {}
ctx.flags._annot_results[#ctx.flags._annot_results + 1] = {
source = src,
result = result,
dir = dir,
dir_basename = dir_basename,
atoms_count = atoms_count,
}
return nil -- annotations.txt is written by report.lua
-- annotations.txt is written by report.lua
end
-- ════════════════════════════════════════════════════════════════════════════
@@ -736,6 +760,12 @@ end
local M = {}
-- Expose `validate` for downstream passes (e.g. report.lua) that need
-- to re-render the per-source results into a per-MODULE report. Keeping
-- it as a single shared function avoids the duplication that an
-- earlier version of report.lua had.
M.validate = validate
--- @param ctx PassCtx
--- @return PassResult
function M.run(ctx)
@@ -743,21 +773,46 @@ function M.run(ctx)
local errors = {}
local warnings = {}
-- Per-DIRECTORY (per-module) aggregation. Group sources by `src.dir`,
-- validate every source in the dir, then emit ONE errors.h per dir
-- (skipping dirs with no atoms AND no errors). The actual
-- annotations.txt is rendered by passes/report.lua from the stashed
-- per-module results below.
local by_dir = {}
for _, src in ipairs(ctx.sources) do
local result = validate(ctx, src)
local err_path = emit_errors_h(ctx, src, result)
by_dir[src.dir] = by_dir[src.dir] or {}
table.insert(by_dir[src.dir], src)
end
for dir, dir_sources in pairs(by_dir) do
-- Dir basename = last component of `dir` ("code/duffle" -> "duffle").
local dir_basename = dir:match("([^/\\]+)$") or dir
-- Aggregate validate() results across the directory.
local dir_atoms = 0
local dir_errors = {}
local dir_warnings = {}
for _, src in ipairs(dir_sources) do
local result = validate(ctx, src)
dir_atoms = dir_atoms + #result.atoms
for _, e in ipairs(result.errors) do
dir_errors[#dir_errors + 1] = { line = e.line, msg = e.msg, source = src.path }
errors[#errors + 1] = { line = e.line, msg = e.msg }
end
for _, w in ipairs(result.warnings) do
dir_warnings[#dir_warnings + 1] = { line = w.line, msg = w.msg }
warnings[#warnings + 1] = { line = w.line, msg = w.msg }
end
end
-- Emit one errors.h per dir.
local err_path = emit_module_errors_h(ctx, dir_basename, dir_atoms, dir_errors, dir_sources)
if err_path then
table.insert(outputs, { errors_h = err_path })
end
-- Stash result for report pass.
emit_annotations_txt(ctx, src, result)
for _, e in ipairs(result.errors) do
errors[#errors + 1] = { line = e.line, msg = e.msg }
end
for _, w in ipairs(result.warnings) do
warnings[#warnings + 1] = { line = w.line, msg = w.msg }
end
-- Stash for report pass.
emit_module_annotations_stub(ctx, dir, dir_basename, dir_atoms)
end
return { outputs = outputs, errors = errors, warnings = warnings }