test(cli): RED pin for type-scoped describe resolution

`ail describe <Type>.<op>` is the canonical form (model 0007 §1) but
currently errors `no module \`Series\` in workspace` because
resolve_describe_name (crates/ail/src/main.rs) only looks up the left
segment as a MODULE, never as a known TYPE. The module-scoped
`series.push` and bare `push` forms already resolve fine.

RED pin: describe_resolves_type_scoped_op asserts
`ail describe --workspace examples/series_sma.ail Series.push` exits 0
and prints the `series.push` def. Fails today on the exit-0 assertion
(feature absent), not on scaffolding. GREEN side follows.

refs #64
This commit is contained in:
2026-06-02 14:14:08 +02:00
parent a35878154b
commit 8d58c8310b
@@ -0,0 +1,63 @@
//! Pins the canonical type-scoped resolution path of `ail describe`.
//!
//! Property protected: `ail describe` resolves the CANONICAL
//! `<Type>.<op>` form for a type-associated operation (model 0007 §1
//! declares `<Type>.<op>` the canonical form; the op lives in the
//! type's home module). For `series_sma`, `Series` is a known TYPE
//! (its `TypeDef` is defined in module `series`), not a module — so
//! `Series.push` must resolve to the SAME `push` fn that the
//! module-scoped `series.push` and the bare `push` already resolve to.
//!
//! Scope: this pins ONLY `Type.op` resolution (issue #64 part 1). It
//! does NOT pin "describe Type lists the type's ops" (part 2).
//!
//! Pure reader: spawns the committed `ail` binary against the
//! committed `examples/series_sma.ail` fixture; mutates nothing.
use ailang_test_support::examples_dir;
use std::path::PathBuf;
use std::process::Command;
fn ail_bin() -> PathBuf {
// CARGO_BIN_EXE_<name> is set by cargo when building integration tests.
PathBuf::from(env!("CARGO_BIN_EXE_ail"))
}
/// `ail describe --workspace examples/series_sma.ail Series.push`
/// exits 0 and prints the `series.push` function definition — the
/// same def the module-scoped `series.push` resolves to. `Series` is
/// a type defined in module `series`, so the canonical `<Type>.<op>`
/// form must resolve through the type's home module.
#[test]
fn describe_resolves_type_scoped_op() {
let fixture = examples_dir().join("series_sma.ail");
let output = Command::new(ail_bin())
.args([
"describe",
"--workspace",
fixture.to_str().unwrap(),
"Series.push",
])
.output()
.expect("ail binary must launch");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"describe Series.push must exit 0 (resolve the canonical \
type-scoped form); exit={:?}\nstdout: {stdout}\nstderr: {stderr}",
output.status.code(),
);
// Same def the module-scoped `series.push` prints: the home
// module line and the `push` fn header (Form-A projection).
assert!(
stdout.contains("module: series"),
"expected the home-module line `module: series`; got:\n{stdout}"
);
assert!(
stdout.contains("(fn push"),
"expected the `push` fn definition in the output; got:\n{stdout}"
);
}