e809f45e67
Six-task post-fieldtest documentary tidy. No production-code behaviour
changes; 559 tests green at every per-task gate.
T1-T3: form_a.md additions
- §Definitions intro "Three kinds" -> "Five kinds".
- New `### Class — (class ...)` subsection: EBNF carries optional
superclass (0 or 1 per ClassDef.superclass schema), method
signatures with optional defaults; anchored to
examples/test_22c_user_class_e2e.ail (ok 24/2).
- New `### Instance — (instance ...)` subsection: EBNF carries the
(method NAME (body LAM-TERM))* shape; canonical-form CLASS-REF
rule explicitly stated (bare same-module / qualified cross-module);
two examples — same-module abbreviated from
mq3_class_eq_vs_fn_eq_classmod.ail and cross-module qualified
from show_user_adt.ail; bare-cross-module-class-ref diagnostic
named inline.
- §Types `(forall ...)` line extended with optional `(constraints
(constraint CLASS-REF TYPE)+)?` clause; explanatory paragraph
added with no-instance diagnostic anchor; fifth example added
to the Examples block anchored to cmp_max_smoke.ail.
T4: docs/specs/2026-05-13-form-a-default-authoring.md "seven
carve-outs" → "eight carve-outs" at 6 sites contradicting the
§C4(b) compile-time-embed amendment (commit 9fcda8b). Sites:
preamble line 11, §C1 line 170, §C2 line 191, §C3 line 218,
§"Data flow" lines 363 + 374. Post-edit grep returns 4 surviving
"seven" lines (233, 238, 463, 469), all correctly §C4(a)-scope
or arithmetic/future-state.
T5: crates/ailang-core/src/hash.rs:50-57 — delete empty
`#[cfg(test)] mod tests {}` placeholder + 6-line relocation
comment. Tests live in crates/ailang-core/tests/hash_pin.rs
since form-a.1 T5; placeholder served no purpose. hash_pin.rs
still 10/10 passing.
T6: crates/ailang-surface/tests/round_trip.rs — module-level
`//!` and inner `///` docstrings rewritten to the post-T10
four-property framing (parse-determinism / idempotency /
CLI-pipeline-idempotency / carve-out-anchor) instead of the
retired Direction-1/Direction-2 language. Sibling-crate
breadcrumbs added pointing at the other two enforcement points
(roundtrip_cli.rs, carve_out_inventory.rs).
Drift item B2 (audit-form-a "plan file two sites") dropped per
recon verification: docs/plans/2026-05-13-iter-form-a.1.md
contains zero defective "seven" sites; all four hits are
internally scoped to §C4(a), arithmetic, or future-state.
Decision recorded in the journal.
Closes 2 of 3 fieldtest-form-a spec_gap findings (#2 form_a.md
typeclass surface + #3 form_a.md class-qualifier rule for
instance) and 3 of 4 audit-form-a drift items.
49 lines
1.7 KiB
Rust
49 lines
1.7 KiB
Rust
//! Content-addressed hashing for definitions.
|
|
//!
|
|
//! Hash = BLAKE3 over the canonical JSON bytes of a [`Def`] (see
|
|
//! [`crate::canonical`]). The hash function takes a [`Def`] by
|
|
//! reference, so there is no `hash` field to strip — the in-memory
|
|
//! struct does not carry one.
|
|
//!
|
|
//! The single entry point at this level is [`def_hash`]. The parallel
|
|
//! entry point at module granularity is
|
|
//! [`crate::workspace::module_hash`].
|
|
|
|
use crate::ast::Def;
|
|
use crate::canonical;
|
|
|
|
/// Content hash of a single [`Def`] — the 16-hex-char (64-bit) prefix
|
|
/// of its BLAKE3 hash over canonical JSON bytes.
|
|
///
|
|
/// 64 bits is wide enough to be unique across realistic AILang
|
|
/// codebases and short enough to read at a glance in pretty-printed
|
|
/// manifests. The hash is computed over the **canonical JSON byte
|
|
/// pre-image**, not over the in-memory struct, so any change to the
|
|
/// canonical form (new fields, different `skip_serializing_if`
|
|
/// behaviour, key reordering bug) changes every hash. The Iter 13a
|
|
/// regression test below pins concrete hashes for two example
|
|
/// definitions to catch that.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```ignore
|
|
/// use ailang_core::{ast::*, def_hash};
|
|
///
|
|
/// let def = Def::Const(ConstDef {
|
|
/// name: "answer".into(),
|
|
/// ty: Type::int(),
|
|
/// value: Term::Lit { lit: Literal::Int { value: 42 } },
|
|
/// doc: None,
|
|
/// });
|
|
///
|
|
/// // Stable across runs: same canonical bytes -> same hash.
|
|
/// assert_eq!(def_hash(&def), def_hash(&def));
|
|
/// assert_eq!(def_hash(&def).len(), 16);
|
|
/// ```
|
|
pub fn def_hash(def: &Def) -> String {
|
|
let bytes = canonical::to_bytes(def);
|
|
let h = blake3::hash(&bytes);
|
|
let hex = h.to_hex();
|
|
hex.as_str()[..16].to_string()
|
|
}
|