iter raw-buf.3 typescoped-intrinsic-mechanism (DONE 2/2): scope-qualified mono symbols + bijection expansion (refs #7)
New language mechanism: a type-scoped polymorphic top-level (intrinsic) op now mints a scope-qualified mono symbol (StubT_peek__Int, not the colliding bare peek__Int), and the intrinsic<->intercept bijection expresses one such polymorphic marker as its N per-param-in-element entries. Ratified standalone on the stub via a new StubT.peek op; no RawBuf, no Term::New, no codegen E2E this iteration (RawBuf consumes the mechanism in raw-buf.4). Task 1 — scope threading. Added scope: Option<String> to FreeFnCall + MonoTarget::FreeFn + the synth free_fn_owner tuple. Populated Some(prefix) ONLY on the type-scoped T.f resolution branch (lib.rs:3538, gated on type_home.is_some()); None on every local / same-module / implicit-import branch, so existing bare-resolved symbols stay byte-identical. Consumed via a scoped_base helper at both mono-symbol mint sites (synthesise + rewrite, which read the same MonoTarget → automatically in lockstep) and folded into mono_target_key so a scoped op and a bare op of the same name never dedup-collide. Task 2 — ratifier + bijection. Added StubT.peek to the stub source; extended workspace_intrinsic_markers with a type-scoped-poly arm that finds the unique same-module TypeDef referenced in the fn signature and expands over its param-in set, building the T_f__<elem> strings via the SAME mono_symbol_n on Type::Con elems so collector and mint agree byte-for-byte; registered StubT_peek__Int/Float entries + emit fns; extended kernel_stub_module_round_trips to cover peek; added mono_scoped_symbol integration test (own-param calls, no Term::New, no codegen — asserts both scoped symbols minted and bare peek__Int absent). Latent bug fixed inline (regression caught at the full-suite gate, RED via the existing escape_local_demo E2E). kernel_stub is implicitly imported, so poly_free_fn_names_for_module unconditionally added peek's bare name to every consumer; a consumer with its OWN monomorphic peek then had its local call mistreated as a poly-free-fn site, over-advancing the mono slot cursor and misaligning a print target. Fix: suppress the bare implicit-import name when the current module declares a same-name global, mirroring synth's resolution ladder (same-module global beats implicit-import). Applied in lockstep to both poly_free_fn name + and constraint-count maps; dot-qualified / type-scoped spellings stay unconditional. This was a pre-existing inconsistency between mono's name-set and synth's resolution that peek (first implicitly-imported poly free fn colliding with a fixture-local name) exposed. Plan deviations (all sound): collect_con_heads omits Borrow/Own recursion (no such Type variant — borrow/own are ParamMode on Type::Fn, verified ast.rs:752); scope bound via the existing synthesise_mono_fn_for_free_fn MonoTarget destructure rather than a new param; the mono-pin fixture uses own params (borrow + peek-returns-a trips consume-while-borrowed; peek is never instantiated so the mode is immaterial to the scope-symbol ratification). Verification (orchestrator, this session): cargo build --workspace clean; cargo test --workspace 670 passed / 0 failed / 2 ignored (669 baseline + the one new mono test). Bijection green (1 marker -> 2 entries), round-trip green, escape_local_demo green. .ll snapshots unchanged (peek polymorphic -> never instantiated without a call site). Existing eq__*/ compare__*/float_* symbols byte-identical (mono_hash_stability green).
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
//! raw-buf.3: a type-scoped polymorphic intrinsic call `StubT.peek`
|
||||||
|
//! monomorphises to the scope-qualified symbol `StubT_peek__<elem>`
|
||||||
|
//! (not the bare `peek__<elem>`). Proves the scope threads
|
||||||
|
//! resolution → FreeFnCall → MonoTarget → mint. No codegen / no
|
||||||
|
//! Term::New: assertion is on the monomorphised def names.
|
||||||
|
|
||||||
|
use ailang_core::ast::Def;
|
||||||
|
use ailang_surface::load_workspace;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn fixture_path() -> PathBuf {
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.pop();
|
||||||
|
d.pop();
|
||||||
|
d.join("examples").join("peek_mono_pin_smoke.ail")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stubt_peek_monomorphises_to_scope_qualified_symbols() {
|
||||||
|
let ws = load_workspace(&fixture_path()).expect("workspace loads");
|
||||||
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
||||||
|
let post_mono = ailang_check::monomorphise_workspace(&ws).expect("mono green");
|
||||||
|
|
||||||
|
// Free-fn mono defs are appended to the polymorphic source's
|
||||||
|
// defining module — `kernel_stub`, where `peek` lives.
|
||||||
|
let names: Vec<String> = post_mono
|
||||||
|
.modules
|
||||||
|
.values()
|
||||||
|
.flat_map(|m| m.defs.iter())
|
||||||
|
.filter_map(|d| match d {
|
||||||
|
Def::Fn(f) => Some(f.name.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
names.iter().any(|n| n == "StubT_peek__Int"),
|
||||||
|
"expected scope-qualified StubT_peek__Int; got: {names:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
names.iter().any(|n| n == "StubT_peek__Float"),
|
||||||
|
"expected scope-qualified StubT_peek__Float; got: {names:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!names.iter().any(|n| n == "peek__Int"),
|
||||||
|
"bare peek__Int must NOT be minted (scope-qualified only)"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -179,6 +179,7 @@ fn synthesise_mono_fn_free_fn_arm_substitutes_rigid_vars() {
|
|||||||
name: "cmp_max".into(),
|
name: "cmp_max".into(),
|
||||||
type_args: vec![int_ty.clone()],
|
type_args: vec![int_ty.clone()],
|
||||||
defining_module: "cmp_max_smoke".into(),
|
defining_module: "cmp_max_smoke".into(),
|
||||||
|
scope: None,
|
||||||
};
|
};
|
||||||
let synth = synthesise_mono_fn_for_free_fn(&target, &ws)
|
let synth = synthesise_mono_fn_for_free_fn(&target, &ws)
|
||||||
.expect("synthesis succeeds");
|
.expect("synthesis succeeds");
|
||||||
@@ -212,6 +213,7 @@ fn mono_target_free_fn_variant_keys_distinctly_from_class_method() {
|
|||||||
name: "cmp_max".into(),
|
name: "cmp_max".into(),
|
||||||
type_args: vec![int_ty.clone()],
|
type_args: vec![int_ty.clone()],
|
||||||
defining_module: "prelude".into(),
|
defining_module: "prelude".into(),
|
||||||
|
scope: None,
|
||||||
};
|
};
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
mono_target_key(&class_tgt),
|
mono_target_key(&class_tgt),
|
||||||
|
|||||||
@@ -2753,6 +2753,13 @@ pub struct FreeFnCall {
|
|||||||
/// yields the concrete type for `forall_vars[i]` iff the call site's
|
/// yields the concrete type for `forall_vars[i]` iff the call site's
|
||||||
/// substitution was fully pinned.
|
/// substitution was fully pinned.
|
||||||
pub metas: Vec<Type>,
|
pub metas: Vec<Type>,
|
||||||
|
/// The TypeDef scope the call resolved through, when it was
|
||||||
|
/// spelled type-scoped (`T.f`, prep.1) — `Some("StubT")` for
|
||||||
|
/// `StubT.peek`, `Some("RawBuf")` for `RawBuf.get`. `None` for a
|
||||||
|
/// bare / dot-qualified / local resolution. raw-buf.3: drives the
|
||||||
|
/// scope-qualified mono symbol `T_f__<elem>` so two types' ops
|
||||||
|
/// of the same name never collide.
|
||||||
|
pub scope: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// lift a canonical-form class-ref value to the qualified
|
/// lift a canonical-form class-ref value to the qualified
|
||||||
@@ -3307,7 +3314,7 @@ pub(crate) fn synth(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (raw, free_fn_owner): (Type, Option<(String, String)>) = if let Some(t) = locals.get(name) {
|
let (raw, free_fn_owner): (Type, Option<(String, String, Option<String>)>) = if let Some(t) = locals.get(name) {
|
||||||
// Locals (lambda / let / fn-params) — fn wins per
|
// Locals (lambda / let / fn-params) — fn wins per
|
||||||
// lookup precedence. A class method shadowed by a
|
// lookup precedence. A class method shadowed by a
|
||||||
// local binding fires the shadow warning so the
|
// local binding fires the shadow warning so the
|
||||||
@@ -3323,7 +3330,7 @@ pub(crate) fn synth(
|
|||||||
.module_globals
|
.module_globals
|
||||||
.get(&env.current_module)
|
.get(&env.current_module)
|
||||||
.filter(|m| m.contains_key(name))
|
.filter(|m| m.contains_key(name))
|
||||||
.map(|_| (env.current_module.clone(), name.clone()));
|
.map(|_| (env.current_module.clone(), name.clone(), None));
|
||||||
emit_shadow_warning_if_class_method(name, env.current_module.as_str(), warnings);
|
emit_shadow_warning_if_class_method(name, env.current_module.as_str(), warnings);
|
||||||
(t.clone(), owner)
|
(t.clone(), owner)
|
||||||
} else if let Some((owner_module, raw_ty)) = env
|
} else if let Some((owner_module, raw_ty)) = env
|
||||||
@@ -3361,7 +3368,7 @@ pub(crate) fn synth(
|
|||||||
let qualified = qualify_local_types(&raw_ty, &owner_module, &owner_types);
|
let qualified = qualify_local_types(&raw_ty, &owner_module, &owner_types);
|
||||||
// Bare name resolves through implicit import; the
|
// Bare name resolves through implicit import; the
|
||||||
// unqualified name in the owner module is the same `name`.
|
// unqualified name in the owner module is the same `name`.
|
||||||
(qualified, Some((owner_module, name.clone())))
|
(qualified, Some((owner_module, name.clone(), None)))
|
||||||
} else if is_class_method_dispatch(name, env) {
|
} else if is_class_method_dispatch(name, env) {
|
||||||
// type-driven dispatch entry. The Var arm runs
|
// type-driven dispatch entry. The Var arm runs
|
||||||
// before App-arm unification, so the arg type is not
|
// before App-arm unification, so the arg type is not
|
||||||
@@ -3531,8 +3538,18 @@ pub(crate) fn synth(
|
|||||||
qualify_local_types(&raw_ty, &target_module, &owner_types)
|
qualify_local_types(&raw_ty, &target_module, &owner_types)
|
||||||
};
|
};
|
||||||
// Dot-qualified `prefix.suffix`: the unqualified name in
|
// Dot-qualified `prefix.suffix`: the unqualified name in
|
||||||
// the owner module is `suffix`.
|
// the owner module is `suffix`. raw-buf.3: when the
|
||||||
(qualified, Some((target_module, suffix.to_string())))
|
// resolution went through the TypeDef-first ladder
|
||||||
|
// (`type_home.is_some()`), the call is type-scoped — carry
|
||||||
|
// the TypeDef prefix as the mono-symbol scope. The legacy
|
||||||
|
// module-as-receiver sub-case (`type_home == None`) is not
|
||||||
|
// type-scoped → `scope = None`.
|
||||||
|
let scope = if type_home.is_some() {
|
||||||
|
Some(prefix.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
(qualified, Some((target_module, suffix.to_string(), scope)))
|
||||||
} else {
|
} else {
|
||||||
return Err(CheckError::UnknownIdent(name.clone()));
|
return Err(CheckError::UnknownIdent(name.clone()));
|
||||||
};
|
};
|
||||||
@@ -3546,7 +3563,7 @@ pub(crate) fn synth(
|
|||||||
// by bare name); the source-level `name` may be a
|
// by bare name); the source-level `name` may be a
|
||||||
// dot-qualified form (`prefix.suffix`) the synth started
|
// dot-qualified form (`prefix.suffix`) the synth started
|
||||||
// with.
|
// with.
|
||||||
if let (Type::Forall { vars, constraints, body }, Some((owner, unqualified_name))) =
|
if let (Type::Forall { vars, constraints, body }, Some((owner, unqualified_name, scope))) =
|
||||||
(&raw, &free_fn_owner)
|
(&raw, &free_fn_owner)
|
||||||
{
|
{
|
||||||
let (metas, inst) = instantiate(vars, body, counter);
|
let (metas, inst) = instantiate(vars, body, counter);
|
||||||
@@ -3604,6 +3621,7 @@ pub(crate) fn synth(
|
|||||||
owner_module: owner.clone(),
|
owner_module: owner.clone(),
|
||||||
forall_vars: vars.clone(),
|
forall_vars: vars.clone(),
|
||||||
metas: metas.clone(),
|
metas: metas.clone(),
|
||||||
|
scope: scope.clone(),
|
||||||
});
|
});
|
||||||
return Ok(inst);
|
return Ok(inst);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,6 +358,22 @@ fn poly_free_fn_names_for_module(
|
|||||||
// `<TypeName>.<fn>` for every TypeDef declared in the imported
|
// `<TypeName>.<fn>` for every TypeDef declared in the imported
|
||||||
// module — `synth`'s Var-arm resolves these via the TypeDef-first
|
// module — `synth`'s Var-arm resolves these via the TypeDef-first
|
||||||
// ladder and `rewrite_mono_calls` consumes the same spelling.
|
// ladder and `rewrite_mono_calls` consumes the same spelling.
|
||||||
|
//
|
||||||
|
// raw-buf.3: the bare implicit-import name is suppressed when the
|
||||||
|
// current module declares a same-name global. `synth`'s Var-arm
|
||||||
|
// resolves a same-module global (lib.rs ~3317) BEFORE the
|
||||||
|
// implicit-import fall-through (~3329), so a bare call there is the
|
||||||
|
// local def, not the imported poly fn. Adding the bare name here
|
||||||
|
// anyway would make `rewrite_mono_calls` treat the local call as a
|
||||||
|
// poly-free-fn site and over-advance the slot cursor (surfaced when
|
||||||
|
// kernel_stub gained a `peek` colliding with a fixture-local
|
||||||
|
// monomorphic `peek`). The dot-qualified / type-scoped spellings
|
||||||
|
// are unambiguous and stay unconditional.
|
||||||
|
let local_global_names: BTreeSet<String> = env
|
||||||
|
.module_globals
|
||||||
|
.get(mname)
|
||||||
|
.map(|g| g.keys().cloned().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
if let Some(imports) = env.module_imports.get(mname) {
|
if let Some(imports) = env.module_imports.get(mname) {
|
||||||
for (alias_or_name, target_mod) in imports {
|
for (alias_or_name, target_mod) in imports {
|
||||||
let target_types: Vec<String> = env
|
let target_types: Vec<String> = env
|
||||||
@@ -370,7 +386,10 @@ fn poly_free_fn_names_for_module(
|
|||||||
if matches!(t, Type::Forall { .. }) {
|
if matches!(t, Type::Forall { .. }) {
|
||||||
// Implicit-import bare name (today only the
|
// Implicit-import bare name (today only the
|
||||||
// prelude is implicit; the loop is generic).
|
// prelude is implicit; the loop is generic).
|
||||||
out.insert(n.clone());
|
// Suppressed if shadowed by a same-module global.
|
||||||
|
if !local_global_names.contains(n) {
|
||||||
|
out.insert(n.clone());
|
||||||
|
}
|
||||||
// Dot-qualified form.
|
// Dot-qualified form.
|
||||||
out.insert(format!("{alias_or_name}.{n}"));
|
out.insert(format!("{alias_or_name}.{n}"));
|
||||||
// prep.1: type-scoped form per TypeDef.
|
// prep.1: type-scoped form per TypeDef.
|
||||||
@@ -419,6 +438,15 @@ fn poly_free_fn_constraint_counts_for_module(
|
|||||||
// prep.1: also enumerate type-scoped spellings `<TypeName>.<fn>`
|
// prep.1: also enumerate type-scoped spellings `<TypeName>.<fn>`
|
||||||
// per TypeDef in the imported module — mirror of
|
// per TypeDef in the imported module — mirror of
|
||||||
// `poly_free_fn_names_for_module`.
|
// `poly_free_fn_names_for_module`.
|
||||||
|
//
|
||||||
|
// raw-buf.3: same bare-name shadow suppression as
|
||||||
|
// `poly_free_fn_names_for_module` — kept in lockstep so the two
|
||||||
|
// maps agree on which spellings count as poly-free-fn sites.
|
||||||
|
let local_global_names: BTreeSet<String> = env
|
||||||
|
.module_globals
|
||||||
|
.get(mname)
|
||||||
|
.map(|g| g.keys().cloned().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
if let Some(imports) = env.module_imports.get(mname) {
|
if let Some(imports) = env.module_imports.get(mname) {
|
||||||
for (alias_or_name, target_mod) in imports {
|
for (alias_or_name, target_mod) in imports {
|
||||||
let target_types: Vec<String> = env
|
let target_types: Vec<String> = env
|
||||||
@@ -429,7 +457,9 @@ fn poly_free_fn_constraint_counts_for_module(
|
|||||||
if let Some(target_globals) = env.module_globals.get(target_mod) {
|
if let Some(target_globals) = env.module_globals.get(target_mod) {
|
||||||
for (n, t) in target_globals {
|
for (n, t) in target_globals {
|
||||||
if let Type::Forall { constraints, .. } = t {
|
if let Type::Forall { constraints, .. } = t {
|
||||||
out.insert(n.clone(), constraints.len());
|
if !local_global_names.contains(n) {
|
||||||
|
out.insert(n.clone(), constraints.len());
|
||||||
|
}
|
||||||
out.insert(format!("{alias_or_name}.{n}"), constraints.len());
|
out.insert(format!("{alias_or_name}.{n}"), constraints.len());
|
||||||
for tn in &target_types {
|
for tn in &target_types {
|
||||||
out.insert(format!("{tn}.{n}"), constraints.len());
|
out.insert(format!("{tn}.{n}"), constraints.len());
|
||||||
@@ -527,6 +557,17 @@ pub fn mono_symbol_n(base: &str, types: &[Type]) -> String {
|
|||||||
parts.join("__")
|
parts.join("__")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// raw-buf.3: the symbol base for a free-fn mono target. A
|
||||||
|
/// type-scoped op (`scope = Some("StubT")`, name `"peek"`) bases on
|
||||||
|
/// `"StubT_peek"`, so `mono_symbol_n` mints `StubT_peek__Int`; a
|
||||||
|
/// bare free fn (`scope = None`) keeps its bare base.
|
||||||
|
pub fn scoped_base(scope: &Option<String>, name: &str) -> String {
|
||||||
|
match scope {
|
||||||
|
Some(t) => format!("{t}_{name}"),
|
||||||
|
None => name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn type_to_mono_suffix(ty: &Type) -> String {
|
fn type_to_mono_suffix(ty: &Type) -> String {
|
||||||
match primitive_surface_name(ty) {
|
match primitive_surface_name(ty) {
|
||||||
Some(p) => p.to_string(),
|
Some(p) => p.to_string(),
|
||||||
@@ -581,6 +622,10 @@ pub enum MonoTarget {
|
|||||||
/// Module in which the polymorphic source `Def::Fn` is declared;
|
/// Module in which the polymorphic source `Def::Fn` is declared;
|
||||||
/// the synthesised mono `Def::Fn` is appended there.
|
/// the synthesised mono `Def::Fn` is appended there.
|
||||||
defining_module: String,
|
defining_module: String,
|
||||||
|
/// The TypeDef scope (`Some("StubT")`) when the call resolved
|
||||||
|
/// type-scoped; `None` for a bare free fn. Part of the dedup
|
||||||
|
/// key and the minted symbol base.
|
||||||
|
scope: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,13 +642,13 @@ pub fn mono_target_key(t: &MonoTarget) -> (String, String, String) {
|
|||||||
format!("{class}.{method}"),
|
format!("{class}.{method}"),
|
||||||
ailang_core::canonical::type_hash(type_),
|
ailang_core::canonical::type_hash(type_),
|
||||||
),
|
),
|
||||||
MonoTarget::FreeFn { name, type_args, .. } => {
|
MonoTarget::FreeFn { name, type_args, scope, .. } => {
|
||||||
let joined = type_args
|
let joined = type_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(ailang_core::canonical::type_hash)
|
.map(ailang_core::canonical::type_hash)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("|");
|
.join("|");
|
||||||
("free".into(), name.clone(), joined)
|
("free".into(), scoped_base(scope, name), joined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -858,6 +903,7 @@ pub fn collect_mono_targets(
|
|||||||
name: fc.name.clone(),
|
name: fc.name.clone(),
|
||||||
type_args,
|
type_args,
|
||||||
defining_module: fc.owner_module.clone(),
|
defining_module: fc.owner_module.clone(),
|
||||||
|
scope: fc.scope.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@@ -1004,9 +1050,9 @@ pub fn synthesise_mono_fn_for_free_fn(
|
|||||||
target: &MonoTarget,
|
target: &MonoTarget,
|
||||||
ws: &Workspace,
|
ws: &Workspace,
|
||||||
) -> Result<AstFnDef> {
|
) -> Result<AstFnDef> {
|
||||||
let (name, type_args, defining_module) = match target {
|
let (name, type_args, defining_module, scope) = match target {
|
||||||
MonoTarget::FreeFn { name, type_args, defining_module } => {
|
MonoTarget::FreeFn { name, type_args, defining_module, scope } => {
|
||||||
(name, type_args, defining_module)
|
(name, type_args, defining_module, scope)
|
||||||
}
|
}
|
||||||
MonoTarget::ClassMethod { .. } => {
|
MonoTarget::ClassMethod { .. } => {
|
||||||
return Err(crate::CheckError::Internal(
|
return Err(crate::CheckError::Internal(
|
||||||
@@ -1077,7 +1123,7 @@ pub fn synthesise_mono_fn_for_free_fn(
|
|||||||
let new_body = crate::substitute_rigids_in_term(&source_fn.body, &mapping);
|
let new_body = crate::substitute_rigids_in_term(&source_fn.body, &mapping);
|
||||||
|
|
||||||
Ok(AstFnDef {
|
Ok(AstFnDef {
|
||||||
name: mono_symbol_n(name, type_args.as_slice()),
|
name: mono_symbol_n(&scoped_base(scope, name), type_args.as_slice()),
|
||||||
ty: new_type,
|
ty: new_type,
|
||||||
params: source_fn.params.clone(),
|
params: source_fn.params.clone(),
|
||||||
body: new_body,
|
body: new_body,
|
||||||
@@ -1172,8 +1218,8 @@ fn rewrite_mono_calls(
|
|||||||
MonoTarget::ClassMethod { method, type_, defining_module, .. } => {
|
MonoTarget::ClassMethod { method, type_, defining_module, .. } => {
|
||||||
(mono_symbol(method, type_), defining_module.as_str())
|
(mono_symbol(method, type_), defining_module.as_str())
|
||||||
}
|
}
|
||||||
MonoTarget::FreeFn { name: fn_name, type_args, defining_module } => {
|
MonoTarget::FreeFn { name: fn_name, type_args, defining_module, scope } => {
|
||||||
(mono_symbol_n(fn_name, type_args.as_slice()), defining_module.as_str())
|
(mono_symbol_n(&scoped_base(scope, fn_name), type_args.as_slice()), defining_module.as_str())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let new_name = if defining_module == caller_module {
|
let new_name = if defining_module == caller_module {
|
||||||
@@ -1490,6 +1536,7 @@ pub(crate) fn collect_residuals_ordered(
|
|||||||
name: fc.name.clone(),
|
name: fc.name.clone(),
|
||||||
type_args,
|
type_args,
|
||||||
defining_module: fc.owner_module.clone(),
|
defining_module: fc.owner_module.clone(),
|
||||||
|
scope: fc.scope.clone(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -171,6 +171,20 @@ pub(crate) static INTERCEPTS: &[Intercept] = &[
|
|||||||
wants_alwaysinline: false,
|
wants_alwaysinline: false,
|
||||||
emit: emit_answer,
|
emit: emit_answer,
|
||||||
},
|
},
|
||||||
|
Intercept {
|
||||||
|
name: "StubT_peek__Int",
|
||||||
|
expected_params: &["ptr"], // borrow (con StubT Int) lowers to ptr
|
||||||
|
expected_ret: "i64",
|
||||||
|
wants_alwaysinline: false,
|
||||||
|
emit: emit_stubt_peek_int,
|
||||||
|
},
|
||||||
|
Intercept {
|
||||||
|
name: "StubT_peek__Float",
|
||||||
|
expected_params: &["ptr"],
|
||||||
|
expected_ret: "double",
|
||||||
|
wants_alwaysinline: false,
|
||||||
|
emit: emit_stubt_peek_float,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> {
|
pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> {
|
||||||
@@ -458,11 +472,38 @@ pub(crate) fn emit_answer(emitter: &mut Emitter<'_>) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// raw-buf.3 ratifier emit. NOTE: never instantiated to codegen this
|
||||||
|
/// iteration (no Term::New to build a StubT; unit-test-ratified only).
|
||||||
|
/// The load offset/correctness is exercised end-to-end only by
|
||||||
|
/// raw-buf.4's RawBuf path; here the entry exists to satisfy the
|
||||||
|
/// bijection. Retires with the stub in raw-buf.5.
|
||||||
|
pub(crate) fn emit_stubt_peek_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||||
|
let n = emitter.locals.len();
|
||||||
|
let s = emitter.locals[n - 1].1.clone();
|
||||||
|
let v = emitter.fresh_ssa();
|
||||||
|
emitter.body.push_str(&format!(" {v} = load i64, ptr {s}\n"));
|
||||||
|
emitter.body.push_str(&format!(" ret i64 {v}\n"));
|
||||||
|
emitter.body.push_str("}\n\n");
|
||||||
|
emitter.block_terminated = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn emit_stubt_peek_float(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||||
|
let n = emitter.locals.len();
|
||||||
|
let s = emitter.locals[n - 1].1.clone();
|
||||||
|
let v = emitter.fresh_ssa();
|
||||||
|
emitter.body.push_str(&format!(" {v} = load double, ptr {s}\n"));
|
||||||
|
emitter.body.push_str(&format!(" ret double {v}\n"));
|
||||||
|
emitter.body.push_str("}\n\n");
|
||||||
|
emitter.block_terminated = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{lookup, INTERCEPTS};
|
use super::{lookup, INTERCEPTS};
|
||||||
use ailang_check::mono::mono_symbol;
|
use ailang_check::mono::{mono_symbol, mono_symbol_n};
|
||||||
use ailang_core::ast::{Def, Term};
|
use ailang_core::ast::{Def, FnDef, Module, Term, Type};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
/// INTERCEPTS entries that intercept the monomorphised `__Int`
|
/// INTERCEPTS entries that intercept the monomorphised `__Int`
|
||||||
@@ -485,6 +526,34 @@ mod tests {
|
|||||||
] {
|
] {
|
||||||
for def in &module.defs {
|
for def in &module.defs {
|
||||||
match def {
|
match def {
|
||||||
|
// raw-buf.3: a polymorphic top-level (intrinsic) op
|
||||||
|
// type-scoped to a same-module TypeDef T (its
|
||||||
|
// signature mentions `(con T …)`) expands to one
|
||||||
|
// marker per element type in T's `param-in` set —
|
||||||
|
// the same `T_f__<elem>` strings the mono pass mints
|
||||||
|
// (mono::scoped_base + mono_symbol_n). One marker → N
|
||||||
|
// entries.
|
||||||
|
Def::Fn(f)
|
||||||
|
if matches!(f.body, Term::Intrinsic)
|
||||||
|
&& matches!(f.ty, Type::Forall { .. }) =>
|
||||||
|
{
|
||||||
|
match scope_typedef_and_elems(f, &module) {
|
||||||
|
Some((tdef, elems)) => {
|
||||||
|
for elem in elems {
|
||||||
|
markers.insert(mono_symbol_n(
|
||||||
|
&format!("{tdef}_{}", f.name),
|
||||||
|
std::slice::from_ref(&elem),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// a Forall intrinsic not scoped to a
|
||||||
|
// param-in TypeDef keeps the bare name (no
|
||||||
|
// such case ships today; future-proofing).
|
||||||
|
None => {
|
||||||
|
markers.insert(f.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Top-level intrinsic fn: name is already the symbol
|
// Top-level intrinsic fn: name is already the symbol
|
||||||
// (float_eq, answer, ...).
|
// (float_eq, answer, ...).
|
||||||
Def::Fn(f) if matches!(f.body, Term::Intrinsic) => {
|
Def::Fn(f) if matches!(f.body, Term::Intrinsic) => {
|
||||||
@@ -548,4 +617,71 @@ mod tests {
|
|||||||
"optimisation-only allowlist names not in INTERCEPTS: {stale_allow:?}"
|
"optimisation-only allowlist names not in INTERCEPTS: {stale_allow:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// raw-buf.3: for a type-scoped polymorphic intrinsic fn `f`, return
|
||||||
|
/// `(TypeDef-name, element-types)` where the TypeDef is the unique
|
||||||
|
/// same-module `Def::Type` referenced via `(con T …)` in `f`'s
|
||||||
|
/// signature and the element types are its `param-in` set expressed as
|
||||||
|
/// `Type::Con` values (so `mono_symbol_n` produces the same suffix the
|
||||||
|
/// mono pass mints). `None` if `f` references zero or several
|
||||||
|
/// same-module TypeDefs.
|
||||||
|
fn scope_typedef_and_elems(f: &FnDef, module: &Module) -> Option<(String, Vec<Type>)> {
|
||||||
|
let typedef_names: BTreeSet<&str> = module
|
||||||
|
.defs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| match d {
|
||||||
|
Def::Type(td) => Some(td.name.as_str()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// collect Con-heads referenced anywhere in f's type
|
||||||
|
let mut referenced: BTreeSet<String> = BTreeSet::new();
|
||||||
|
collect_con_heads(&f.ty, &mut referenced);
|
||||||
|
let scoped: Vec<&String> = referenced
|
||||||
|
.iter()
|
||||||
|
.filter(|n| typedef_names.contains(n.as_str()))
|
||||||
|
.collect();
|
||||||
|
let tdef = match scoped.as_slice() {
|
||||||
|
[one] => (*one).clone(),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let td = module.defs.iter().find_map(|d| match d {
|
||||||
|
Def::Type(td) if td.name == tdef => Some(td),
|
||||||
|
_ => None,
|
||||||
|
})?;
|
||||||
|
// param-in is keyed by the type var; take its allowed surface
|
||||||
|
// names and build `Type::Con` element types. The element strings
|
||||||
|
// (`Int`, `Float`) flow through `mono_symbol_n`'s
|
||||||
|
// `primitive_surface_name` gate to the same `__Int` / `__Float`
|
||||||
|
// suffixes the mono pass mints.
|
||||||
|
let allowed = td.param_in.values().next()?; // single-var TypeDefs
|
||||||
|
let elems: Vec<Type> = allowed
|
||||||
|
.iter()
|
||||||
|
.map(|s| Type::Con { name: s.clone(), args: vec![] })
|
||||||
|
.collect();
|
||||||
|
Some((tdef, elems))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// raw-buf.3: push every `Type::Con` head name reachable in `ty`
|
||||||
|
/// into `out`. `borrow` / `own` are `ParamMode` metadata on
|
||||||
|
/// `Type::Fn`, not wrapper `Type` variants, so the only recursive
|
||||||
|
/// shapes are `Forall.body`, `Fn.params`/`ret`, and `Con.args`.
|
||||||
|
fn collect_con_heads(ty: &Type, out: &mut BTreeSet<String>) {
|
||||||
|
match ty {
|
||||||
|
Type::Con { name, args } => {
|
||||||
|
out.insert(name.clone());
|
||||||
|
for a in args {
|
||||||
|
collect_con_heads(a, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Fn { params, ret, .. } => {
|
||||||
|
for p in params {
|
||||||
|
collect_con_heads(p, out);
|
||||||
|
}
|
||||||
|
collect_con_heads(ret, out);
|
||||||
|
}
|
||||||
|
Type::Forall { body, .. } => collect_con_heads(body, out),
|
||||||
|
Type::Var { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -763,4 +763,12 @@ fn kernel_stub_module_round_trips() {
|
|||||||
let allowed = td.param_in.get("a").expect("StubT.a restricted");
|
let allowed = td.param_in.get("a").expect("StubT.a restricted");
|
||||||
assert!(allowed.contains("Int"));
|
assert!(allowed.contains("Int"));
|
||||||
assert!(allowed.contains("Float"));
|
assert!(allowed.contains("Float"));
|
||||||
|
|
||||||
|
// raw-buf.3: the type-scoped polymorphic intrinsic ratifier op
|
||||||
|
// round-trips and is an `(intrinsic)` marker.
|
||||||
|
let peek = recovered.defs.iter().find_map(|d| match d {
|
||||||
|
Def::Fn(f) if f.name == "peek" => Some(f),
|
||||||
|
_ => None,
|
||||||
|
}).expect("kernel_stub ships the peek ratifier op (raw-buf.3)");
|
||||||
|
assert!(matches!(peek.body, Term::Intrinsic), "peek is an (intrinsic) marker");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,9 @@
|
|||||||
(doc "Ratifies the intrinsic mechanism end-to-end: codegen intercept answer emits ret i64 42.")
|
(doc "Ratifies the intrinsic mechanism end-to-end: codegen intercept answer emits ret i64 42.")
|
||||||
(type (fn-type (params) (ret (con Int))))
|
(type (fn-type (params) (ret (con Int))))
|
||||||
(params)
|
(params)
|
||||||
|
(intrinsic))
|
||||||
|
(fn peek
|
||||||
|
(doc "Ratifies the type-scoped polymorphic intrinsic mechanism: StubT.peek is polymorphic over a (param-in {Int,Float}) and type-scoped to StubT. Codegen intercepts StubT_peek__Int / StubT_peek__Float emit a load of the Stub payload. Retires with the stub in raw-buf.5.")
|
||||||
|
(type (forall (vars a) (fn-type (params (borrow (con StubT a))) (ret a))))
|
||||||
|
(params s)
|
||||||
(intrinsic)))
|
(intrinsic)))
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
(module peek_mono_pin_smoke
|
||||||
|
(fn at_int
|
||||||
|
(type (fn-type (params (own (con StubT (con Int)))) (ret (con Int))))
|
||||||
|
(params s)
|
||||||
|
(body (app StubT.peek s)))
|
||||||
|
(fn at_float
|
||||||
|
(type (fn-type (params (own (con StubT (con Float)))) (ret (con Float))))
|
||||||
|
(params s)
|
||||||
|
(body (app StubT.peek s))))
|
||||||
Reference in New Issue
Block a user