From 028227587b24ec73da7f319d1bae08dc0a12eff0 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 9 May 2026 20:21:24 +0200 Subject: [PATCH] =?UTF-8?q?iter=2022b.3.2:=20mono=5Fsymbol=20=E2=80=94=20p?= =?UTF-8?q?rimitive=20surface=20forms=20+=20hash=20for=20compound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ail/tests/typeclass_22b3.rs | 42 ++++++++++++++++++++++++++++ crates/ailang-check/src/lib.rs | 2 +- crates/ailang-check/src/mono.rs | 45 +++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/crates/ail/tests/typeclass_22b3.rs b/crates/ail/tests/typeclass_22b3.rs index b201b97..3e73f15 100644 --- a/crates/ail/tests/typeclass_22b3.rs +++ b/crates/ail/tests/typeclass_22b3.rs @@ -5,6 +5,7 @@ //! `examples_dir()` pattern — `cargo test`-cwd is the crate dir, //! the fixtures live in `/examples/`. +use ailang_core::ast::Type; use std::path::PathBuf; fn examples_dir() -> PathBuf { @@ -93,3 +94,44 @@ fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() { ); } } + +#[test] +fn mono_symbol_primitive_types_use_surface_form() { + // Primitive-type forms must be human-readable in diagnostics + // and ABI symbols. Per the spec's mono-symbol naming guidance: + // # for primitives. + let int_ty = Type::int(); + let bool_ty = Type::bool_(); + let str_ty = Type::str_(); + let unit_ty = Type::unit(); + + assert_eq!(ailang_check::mono::mono_symbol("show", &int_ty), "show#Int"); + assert_eq!(ailang_check::mono::mono_symbol("eq", &bool_ty), "eq#Bool"); + assert_eq!(ailang_check::mono::mono_symbol("show", &str_ty), "show#Str"); + assert_eq!(ailang_check::mono::mono_symbol("foo", &unit_ty), "foo#Unit"); +} + +#[test] +fn mono_symbol_compound_type_uses_hash_suffix() { + // Compound types fall through to canonical type_hash with an + // 8-hex-char prefix. Stability matters more than legibility + // here — same input must produce the same name across runs. + let pair_ty = Type::Con { + name: "Pair".into(), + args: vec![Type::int(), Type::bool_()], + }; + let name = ailang_check::mono::mono_symbol("show", &pair_ty); + + // Format: `show#<8-hex-chars>`. + assert!(name.starts_with("show#"), "name = {name}"); + let suffix = &name["show#".len()..]; + assert_eq!(suffix.len(), 8, "compound suffix is 8 hex chars: {suffix}"); + assert!( + suffix.chars().all(|c| c.is_ascii_hexdigit()), + "compound suffix must be hex: {suffix}" + ); + + // Stability: a second call with the same type produces the same name. + let again = ailang_check::mono::mono_symbol("show", &pair_ty); + assert_eq!(name, again, "mono_symbol must be deterministic"); +} diff --git a/crates/ailang-check/src/lib.rs b/crates/ailang-check/src/lib.rs index 669ba31..985b3f2 100644 --- a/crates/ailang-check/src/lib.rs +++ b/crates/ailang-check/src/lib.rs @@ -251,7 +251,7 @@ fn unify(a: &Type, b: &Type, subst: &mut Subst) -> Result<()> { pub mod builtins; pub mod diagnostic; pub mod lift; -mod mono; +pub mod mono; pub use diagnostic::{Diagnostic, Severity}; pub use lift::lift_letrecs; diff --git a/crates/ailang-check/src/mono.rs b/crates/ailang-check/src/mono.rs index 9d398db..b0c29fd 100644 --- a/crates/ailang-check/src/mono.rs +++ b/crates/ailang-check/src/mono.rs @@ -51,7 +51,7 @@ //! the implementation so the constraint is visible to anyone //! extending the skeleton. -use ailang_core::ast::Def; +use ailang_core::ast::{Def, Type}; use ailang_core::workspace::Workspace; use crate::Result; @@ -87,3 +87,46 @@ fn workspace_has_typeclasses(ws: &Workspace) -> bool { m.defs.iter().any(|d| matches!(d, Def::Class(_) | Def::Instance(_))) }) } + +/// Iter 22b.3: deterministic mono-symbol name for a `(method, +/// type)` pair. Primitive types (`Int`, `Bool`, `Str`, `Float`, +/// `Unit`) produce `#` for diagnostic and +/// ABI legibility. All other types — parameterised cons, +/// user-defined ADTs, function types — fall to +/// `#<8-hex-prefix-of-canonical-type-hash>`. The hash +/// route ensures uniqueness without requiring a flattened +/// surface form for arbitrarily nested types. +/// +/// Determinism: `ailang_core::canonical::type_hash` is the same +/// function `workspace::build_registry` uses to key +/// [`Registry::entries`], so a registry-key match implies a +/// `mono_symbol` match. +pub fn mono_symbol(method: &str, ty: &Type) -> String { + if let Some(prim) = primitive_surface_name(ty) { + return format!("{method}#{prim}"); + } + let full_hash = ailang_core::canonical::type_hash(ty); + // 8-hex prefix is enough for low-collision keying across the + // workspace; the full hash remains in the registry for + // disambiguation should one ever be needed. + format!("{method}#{}", &full_hash[..8]) +} + +/// Iter 22b.3: returns the surface name iff `ty` is a zero-arity +/// primitive `Type::Con`. Used by [`mono_symbol`] to gate the +/// human-readable form. The match is intentionally narrow: +/// `Int` (which is malformed but parser-accepting) is +/// treated as compound, so it falls to the hash form. +fn primitive_surface_name(ty: &Type) -> Option<&'static str> { + match ty { + Type::Con { name, args } if args.is_empty() => match name.as_str() { + "Int" => Some("Int"), + "Bool" => Some("Bool"), + "Str" => Some("Str"), + "Float" => Some("Float"), + "Unit" => Some("Unit"), + _ => None, + }, + _ => None, + } +}