832375f2ac
All 176 files in the four accumulating directories now use a zero-padded 4-digit counter prefix that reflects creation order (`NNNN-slug.md`). The counter is assigned per directory in strict git-log creation order; ties broken alphabetically by original name. The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is dropped — the date is recoverable from git log and the counter carries the ordering. A file's counter is stable for the life of the file: never reassigned, never reused, never compacted. Deleted files retire their counter; subsequent files do not fill the gap. This is the property that lets cross-references stay literal — refs use the full filename including the counter (`design/contracts/0007-honesty-rule.md`) so they grep cleanly and resolve directly without a glob step. 313 cross-references updated across .md/.rs/.toml/.c/.json files (test pins, include_str! paths, design-INDEX entries, baseline notes, runtime C comments, inter-contract markdown links incl. bare basename and `../models/foo.md` forms). CLAUDE.md gets a new "File-naming convention" section spelling out the rule and rationale. skills/brainstorm/SKILL.md and skills/planner/SKILL.md updated so new spec/plan creation produces counter-prefixed names from the start. The full test suite (cargo test --workspace) passes.
72 lines
3.1 KiB
Rust
72 lines
3.1 KiB
Rust
//! Pin for the iter-24.3 FreeFnCall constraint-residual-push
|
|
//! invariant (design/contracts/0013-typeclasses.md, "Cross-module
|
|
//! references in synthesised bodies" invariant 3).
|
|
//!
|
|
//! Property protected: bare-name references to polymorphic free fns
|
|
//! that resolve through the auto-injected-prelude path AT THE
|
|
//! DOT-QUALIFIED SYNTH BRANCH push residuals for the fn's declared
|
|
//! constraints. The discharge loop then fires `NoInstance` at
|
|
//! typecheck if no instance ships for the unified concrete type.
|
|
//!
|
|
//! Failure mode this pin catches: a future refactor changes the
|
|
//! prelude auto-injection resolution path so that bare-name `print`
|
|
//! reaches synth via a different branch (e.g. locals, env.module_globals
|
|
//! direct hit) that does NOT push residuals. Without this pin, the
|
|
//! regression surfaces as `unknown variable: show` from codegen for
|
|
//! the negative case — confusing diagnostic, hard to bisect.
|
|
|
|
use ailang_check::check_workspace;
|
|
use ailang_surface::load_workspace;
|
|
use std::path::PathBuf;
|
|
|
|
fn fixture_path() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../examples")
|
|
.join("show_no_instance.ail")
|
|
}
|
|
|
|
#[test]
|
|
fn bare_name_polyfn_fires_typecheck_no_instance_not_codegen_unknown_var() {
|
|
// The fixture calls `print f` bare-name (no `prelude.` qualifier)
|
|
// where `f : Int -> Int`. The auto-injected-prelude resolution
|
|
// must route this through the dot-qualified synth branch so that
|
|
// the `Show a` declared constraint of `print` produces a residual,
|
|
// and the discharge loop fires `no-instance`.
|
|
let ws = load_workspace(&fixture_path()).expect("workspace loads");
|
|
let diags = check_workspace(&ws);
|
|
|
|
// Exactly one `no-instance` diagnostic — proves:
|
|
// (a) the bare-name `print` resolved (was not "unknown variable")
|
|
// (b) the resolution reached the dot-qualified synth branch
|
|
// which pushes the declared-constraint residual
|
|
// (c) the discharge loop ran with the residual and fired the
|
|
// NoInstance because no `Show (Int -> Int)` instance exists.
|
|
let no_inst: Vec<_> = diags.iter().filter(|d| d.code == "no-instance").collect();
|
|
assert_eq!(
|
|
no_inst.len(),
|
|
1,
|
|
"expected exactly one 'no-instance' diagnostic — got {} (all diags: {diags:?})",
|
|
no_inst.len()
|
|
);
|
|
|
|
// No "unknown variable" or other codegen-grade errors at typecheck:
|
|
// if the residual push did NOT fire, the typecheck would pass
|
|
// silently and the error would only surface at codegen.
|
|
let unknown_vars: Vec<_> = diags
|
|
.iter()
|
|
.filter(|d| {
|
|
d.code == "unknown-variable"
|
|
|| d.message.contains("unknown variable")
|
|
|| d.code == "internal"
|
|
})
|
|
.collect();
|
|
assert!(
|
|
unknown_vars.is_empty(),
|
|
"expected zero 'unknown variable' or 'internal' diagnostics at typecheck — \
|
|
got {} (all diags: {diags:?}). If this fires, the bare-name `print` \
|
|
resolution bypassed the dot-qualified synth branch and the constraint \
|
|
residual was never pushed.",
|
|
unknown_vars.len()
|
|
);
|
|
}
|