05c3c018de
The Float-aware `NoInstance` addendum emitted the same hint for every Eq/Ord method at Float: "use float_eq / float_lt (and siblings ...)". For `eq`/`ne`/`lt`/`le`/`gt`/`ge` that is correct — each has a drop-in boolean `float_*` sibling. For `compare` it is incomplete and misleading: `compare` returns three-way `Ordering`, but no `float_compare` ships (a deliberate non-ship, docs/specs/2026-05-20-operator-routing-eq-ord.md). An LLM author who reached for `compare` to get three-way ordering was pointed at boolean siblings as if a drop-in existed, and left guessing whether to reconstruct it themselves. Fix: branch the addendum on the called method. The boolean arm keeps the existing wording; the `compare` arm instead names the deliberate `float_compare` omission and points at building a three-way `Ordering` from `float_lt` + `float_eq`. One added branch on the existing `method` field (already carried by `CheckError::NoInstance`), no new machinery; the structured `ctx` is untouched. Out of scope (the issue's deliberate non-ship): shipping a `float_compare` fn. The friction was that the diagnostic did not acknowledge the omission — now it does. RED-first, inline: a one-branch diagnostic-text change at a single site whose surrounding code was already loaded while working the adjacent #52 fix this session (CLAUDE.md "context already loaded" carve-out). TDD discipline preserved by the new RED test `compare_at_float_names_deliberate_no_float_compare` over a new minimal fixture `examples/compare_float_noinstance.ail` (`compare` at Float); it failed RED on the old shared wording and passes GREEN on the branch. The pre-existing `eq_at_float_fires_float_aware_noinstance` still passes (eq arm unchanged). Verification: full ailang-check lib suite (121) + full ail test suite green; live CLI renders both arms correctly. closes #25
97 lines
3.9 KiB
Rust
97 lines
3.9 KiB
Rust
//! Pins the Float-aware `NoInstance` diagnostic added in milestone 23.
|
|
//!
|
|
//! Property protected: a polymorphic Eq/Ord helper invoked at Float
|
|
//! fires `no-instance` (since Float has neither Eq nor Ord instance
|
|
//! per design/contracts/0005-float-semantics.md) AND the diagnostic message
|
|
//! cross-references the canonical float-semantics contract so the
|
|
//! LLM author immediately learns the partial-Float story rather
|
|
//! than seeing a bare "no instance" message.
|
|
|
|
use ailang_check::check_workspace;
|
|
use ailang_surface::load_workspace;
|
|
use std::path::PathBuf;
|
|
|
|
fn fixture(name: &str) -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../examples")
|
|
.join(name)
|
|
}
|
|
|
|
#[test]
|
|
fn eq_at_float_fires_float_aware_noinstance() {
|
|
let ws = load_workspace(&fixture("eq_float_noinstance.ail")).expect("load");
|
|
let diags = check_workspace(&ws);
|
|
|
|
assert!(!diags.is_empty(), "expected NoInstance diagnostic, got none");
|
|
|
|
let no_inst = diags
|
|
.iter()
|
|
.find(|d| d.code == "no-instance")
|
|
.unwrap_or_else(|| panic!("expected diagnostic with code 'no-instance', got: {diags:#?}"));
|
|
|
|
assert!(
|
|
no_inst.message.contains("Float"),
|
|
"expected Float-aware NoInstance diagnostic, got message: {:?}",
|
|
no_inst.message
|
|
);
|
|
|
|
// Cross-ref to the float-semantics contract — the LLM author should
|
|
// be pointed at the canonical explanation of partial Float orderability.
|
|
assert!(
|
|
no_inst.message.contains("design/contracts/0005-float-semantics.md"),
|
|
"expected NoInstance message to cross-reference the float-semantics contract, got: {:?}",
|
|
no_inst.message
|
|
);
|
|
// 2026-05-21 operator-routing-eq-ord: the addendum names the
|
|
// explicit IEEE-aware alternative `float_eq` (and siblings)
|
|
// alongside the contract cross-reference, so the LLM author
|
|
// learns the named-fn surface from the diagnostic itself.
|
|
assert!(
|
|
no_inst.message.contains("float_eq"),
|
|
"expected NoInstance message to name `float_eq` as the explicit IEEE-aware alternative, got: {:?}",
|
|
no_inst.message
|
|
);
|
|
}
|
|
|
|
/// #25: the Float-aware `NoInstance` addendum branches on the called
|
|
/// method. `compare` returns a three-way `Ordering`, but no `float_compare`
|
|
/// ships — a deliberate non-ship per the operator-routing-eq-ord spec. The
|
|
/// `eq`/`ne`/`lt`/`le`/`gt`/`ge` arm keeps the existing wording (the boolean
|
|
/// `float_*` siblings are drop-in replacements); the `compare` arm must
|
|
/// instead acknowledge the omission and point the author at building a
|
|
/// three-way result from `float_lt` + `float_eq`, rather than listing the
|
|
/// boolean siblings as if a `float_compare` existed.
|
|
#[test]
|
|
fn compare_at_float_names_deliberate_no_float_compare() {
|
|
let ws = load_workspace(&fixture("compare_float_noinstance.ail")).expect("load");
|
|
let diags = check_workspace(&ws);
|
|
|
|
let no_inst = diags
|
|
.iter()
|
|
.find(|d| d.code == "no-instance")
|
|
.unwrap_or_else(|| panic!("expected diagnostic with code 'no-instance', got: {diags:#?}"));
|
|
|
|
assert!(
|
|
no_inst.message.contains("Float"),
|
|
"expected Float-aware NoInstance diagnostic, got: {:?}",
|
|
no_inst.message
|
|
);
|
|
assert!(
|
|
no_inst.message.contains("design/contracts/0005-float-semantics.md"),
|
|
"expected the float-semantics contract cross-reference, got: {:?}",
|
|
no_inst.message
|
|
);
|
|
// The compare-specific branch: name the deliberate `float_compare`
|
|
// non-ship and the `float_lt` + `float_eq` building blocks.
|
|
assert!(
|
|
no_inst.message.contains("float_compare"),
|
|
"expected the compare addendum to name the deliberate `float_compare` non-ship, got: {:?}",
|
|
no_inst.message
|
|
);
|
|
assert!(
|
|
no_inst.message.contains("float_lt") && no_inst.message.contains("float_eq"),
|
|
"expected the compare addendum to name float_lt + float_eq as the three-way building blocks, got: {:?}",
|
|
no_inst.message
|
|
);
|
|
}
|