///|
/// Side-specific wrapper; the original parser error and position are preserved.
pub enum ImportsComparisonError {
ImportsBaselineError(WasmError)
ImportsCurrentError(WasmError)
}
///|
/// A multiset key group, not a physical declaration correspondence.
pub struct ImportInventoryGroup {
module_name : String
field : String
kind : String
status : String
baseline_ordinals : Array[Int]
current_ordinals : Array[Int]
baseline_count : Int
current_count : Int
overlap_count : Int
removed_count : Int
added_count : Int
count_delta : Int
baseline_entry_bytes : Int
current_entry_bytes : Int
entry_bytes_delta : Int
}
///|
pub struct ImportInventoryCounts {
baseline_entry_count : Int
current_entry_count : Int
overlap_count : Int
removed_count : Int
added_count : Int
count_delta : Int
} derive(Eq, Debug)
///|
pub struct ImportsComparison {
baseline : ImportsReport
current : ImportsReport
/// Churn descending, absolute byte delta descending, full key ascending.
groups : Array[ImportInventoryGroup]
counts : ImportInventoryCounts
/// Only key multiplicities; never a claim about ABI or descriptor changes.
inventory_changed : Bool
/// Signed current-minus-baseline byte totals, never file ranges.
delta : ImportsLedger
comparison_scope : String
}
///|
priv enum ImportsInventoryKey {
ImportInventoryKey(String, String, String)
} derive(Eq, Hash)
///|
priv struct ImportsInventoryBucket {
module_name : String
field : String
kind : String
baseline_ordinals : Array[Int]
current_ordinals : Array[Int]
mut baseline_bytes : Int
mut current_bytes : Int
}
///|
fn imports_diff_scope() -> String {
"Exact multiset key: (module_name, field, kind), without normalization. Ordinal, kind_index, offsets and type_index are not cross-build identities. " +
"Overlap is a quantity intersection, not physical member matching or equality of bytes/types. same_count does not mean unchanged or compatible. " +
"Type compatibility is not evaluated: equal type_index does not prove equal signatures, different type_index does not prove a signature change; limits, value types and mutability may differ under the same key/count. " +
"Equal inventories do not establish host compatibility or equal encodings; zero byte delta does not establish byte equality. " +
"All deltas are current minus baseline; section header and count overhead are separate from groups. " +
"Declarations do not establish runtime calls, permissions, host implementation/package size, transfer cost or removal savings. " +
"Top limits text groups only; counts, both complete reports, JSON and scope remain complete."
}
///|
pub fn describe_imports_comparison_error(
error : ImportsComparisonError,
) -> String {
match error {
ImportsBaselineError(e) => "baseline: " + describe_error(e)
ImportsCurrentError(e) => "current: " + describe_error(e)
}
}
///|
fn imports_ledger_delta(
before : ImportsLedger,
after : ImportsLedger,
) -> ImportsLedger {
{
import_section_total_bytes: after.import_section_total_bytes -
before.import_section_total_bytes,
section_header_bytes: after.section_header_bytes -
before.section_header_bytes,
count_prefix_bytes: after.count_prefix_bytes - before.count_prefix_bytes,
entry_bytes: after.entry_bytes - before.entry_bytes,
module_name_encoding_bytes: after.module_name_encoding_bytes -
before.module_name_encoding_bytes,
module_length_prefix_bytes: after.module_length_prefix_bytes -
before.module_length_prefix_bytes,
module_utf8_bytes: after.module_utf8_bytes - before.module_utf8_bytes,
field_name_encoding_bytes: after.field_name_encoding_bytes -
before.field_name_encoding_bytes,
field_length_prefix_bytes: after.field_length_prefix_bytes -
before.field_length_prefix_bytes,
field_utf8_bytes: after.field_utf8_bytes - before.field_utf8_bytes,
descriptor_bytes: after.descriptor_bytes - before.descriptor_bytes,
}
}
///|
/// Parse once per side. No analyze, graph, body inventory or type comparison.
pub fn compare_imports(
baseline : Bytes,
current : Bytes,
) -> Result[ImportsComparison, ImportsComparisonError] {
let before = match inspect_imports(baseline) {
Ok(r) => r
Err(e) => return Err(ImportsBaselineError(e))
}
let after = match inspect_imports(current) {
Ok(r) => r
Err(e) => return Err(ImportsCurrentError(e))
}
let buckets : Array[ImportsInventoryBucket] = []
let by_key : Map[ImportsInventoryKey, Int] = Map([])
for (is_baseline, report) in [(true, before), (false, after)] {
for entry in report.entries {
let key = ImportInventoryKey(entry.module_name, entry.field, entry.kind)
let index = match by_key.get(key) {
Some(i) => i
None => {
let i = buckets.length()
buckets.push({
module_name: entry.module_name,
field: entry.field,
kind: entry.kind,
baseline_ordinals: [],
current_ordinals: [],
baseline_bytes: 0,
current_bytes: 0,
})
by_key.set(key, i)
i
}
}
let bucket = buckets[index]
if is_baseline {
bucket.baseline_ordinals.push(entry.ordinal)
bucket.baseline_bytes += entry.encoding.size
} else {
bucket.current_ordinals.push(entry.ordinal)
bucket.current_bytes += entry.encoding.size
}
}
}
let groups : Array[ImportInventoryGroup] = []
let mut total_overlap = 0
let mut total_added = 0
let mut total_removed = 0
let mut changed = false
for bucket in buckets {
let b = bucket.baseline_ordinals.length()
let c = bucket.current_ordinals.length()
let overlap = b.min(c)
let status = if b == 0 {
"added"
} else if c == 0 {
"removed"
} else if c > b {
"count_increased"
} else if c < b {
"count_decreased"
} else {
"same_count"
}
total_overlap += overlap
total_added += c - overlap
total_removed += b - overlap
if b != c {
changed = true
}
groups.push({
module_name: bucket.module_name,
field: bucket.field,
kind: bucket.kind,
status,
baseline_ordinals: bucket.baseline_ordinals,
current_ordinals: bucket.current_ordinals,
baseline_count: b,
current_count: c,
overlap_count: overlap,
removed_count: b - overlap,
added_count: c - overlap,
count_delta: c - b,
baseline_entry_bytes: bucket.baseline_bytes,
current_entry_bytes: bucket.current_bytes,
entry_bytes_delta: bucket.current_bytes - bucket.baseline_bytes,
})
}
groups.sort_by(fn(a, b) {
let churn = (b.added_count + b.removed_count).compare(
a.added_count + a.removed_count,
)
if churn != 0 {
return churn
}
let bytes = delta_magnitude(b.entry_bytes_delta).compare(
delta_magnitude(a.entry_bytes_delta),
)
if bytes != 0 {
return bytes
}
let names = a.module_name.compare(b.module_name)
if names != 0 {
return names
}
let fields = a.field.compare(b.field)
if fields != 0 {
return fields
}
a.kind.compare(b.kind)
})
Ok({
baseline: before,
current: after,
groups,
counts: {
baseline_entry_count: before.entry_count,
current_entry_count: after.entry_count,
overlap_count: total_overlap,
removed_count: total_removed,
added_count: total_added,
count_delta: after.entry_count - before.entry_count,
},
inventory_changed: changed,
delta: imports_ledger_delta(before.ledger, after.ledger),
comparison_scope: imports_diff_scope(),
})
}