diff --git a/crates/ailang-check/src/lib.rs b/crates/ailang-check/src/lib.rs index 3a7f69a..181d93c 100644 --- a/crates/ailang-check/src/lib.rs +++ b/crates/ailang-check/src/lib.rs @@ -757,7 +757,8 @@ pub enum CheckError { /// (`allowed`, deterministically alphabetical from the BTreeSet /// source). Code: `param-not-in-restricted-set`. #[error( - "type-arg `{found}` is not in the restricted set for type-variable `{var}` of `{type_name}` (allowed: one of {allowed:?})" + "type-arg `{found}` is not in the restricted set for type-variable `{var}` of `{type_name}` (allowed: one of {{{}}})", + .allowed.join(", ") )] ParamNotInRestrictedSet { type_name: String, @@ -8527,6 +8528,56 @@ mod tests { ); } + /// #52: the allowed set in `param-not-in-restricted-set` must render in + /// AILang type syntax — a brace-wrapped, comma-separated set of bare + /// type names (`{Float, Int}`) — not a Rust debug list + /// (`["Float", "Int"]`). AILang's audience is an LLM author; a + /// language-level diagnostic must not leak Rust formatting (quotes, + /// brackets) into its surface. Order stays deterministic (alphabetical, + /// from the BTreeSet source). + #[test] + fn param_in_reject_message_renders_allowed_set_in_ailang_syntax() { + let m: Module = serde_json::from_value(serde_json::json!({ + "schema": SCHEMA, + "name": "k", + "defs": [ + {"kind": "type", "name": "StubT", "vars": ["a"], + "ctors": [{"name": "Stub", "fields": [{"k": "var", "name": "a"}]}], + "param-in": {"a": ["Int", "Float"]}}, + {"kind": "fn", "name": "bad", + "type": {"k": "fn", "params": [], + "ret": {"k": "con", "name": "StubT", + "args": [{"k": "con", "name": "Str"}]}, + "effects": []}, + "params": [], + "body": {"t": "lit", "lit": {"kind": "unit"}}} + ] + })).unwrap(); + let mut modules = BTreeMap::new(); + modules.insert("k".to_string(), m); + let ws = Workspace { + entry: "k".into(), + modules, + root_dir: std::path::PathBuf::from("."), + registry: ailang_core::workspace::Registry::default(), + }; + let diags = check_workspace(&ws); + let d = diags + .iter() + .find(|d| d.code == "param-not-in-restricted-set") + .unwrap_or_else(|| panic!("expected param-not-in-restricted-set, got {diags:?}")); + assert!( + d.message.contains("{Float, Int}"), + "allowed set must render as the AILang set `{{Float, Int}}`, got: {:?}", + d.message + ); + assert!( + !d.message.contains('"') && !d.message.contains('['), + "allowed set must not leak Rust debug formatting (quotes/brackets), got: {:?}", + d.message + ); + } + /// prep.3 (kernel-extension-mechanics): `param-in` enforcement /// accepts a type-arg inside the allowed set. Same TypeDef as /// the RED test, but `(con StubT (con Int))` — Int is in