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(),
|
||||
type_args: vec![int_ty.clone()],
|
||||
defining_module: "cmp_max_smoke".into(),
|
||||
scope: None,
|
||||
};
|
||||
let synth = synthesise_mono_fn_for_free_fn(&target, &ws)
|
||||
.expect("synthesis succeeds");
|
||||
@@ -212,6 +213,7 @@ fn mono_target_free_fn_variant_keys_distinctly_from_class_method() {
|
||||
name: "cmp_max".into(),
|
||||
type_args: vec![int_ty.clone()],
|
||||
defining_module: "prelude".into(),
|
||||
scope: None,
|
||||
};
|
||||
assert_ne!(
|
||||
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
|
||||
/// substitution was fully pinned.
|
||||
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
|
||||
@@ -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
|
||||
// lookup precedence. A class method shadowed by a
|
||||
// local binding fires the shadow warning so the
|
||||
@@ -3323,7 +3330,7 @@ pub(crate) fn synth(
|
||||
.module_globals
|
||||
.get(&env.current_module)
|
||||
.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);
|
||||
(t.clone(), owner)
|
||||
} 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);
|
||||
// Bare name resolves through implicit import; the
|
||||
// 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) {
|
||||
// type-driven dispatch entry. The Var arm runs
|
||||
// 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)
|
||||
};
|
||||
// Dot-qualified `prefix.suffix`: the unqualified name in
|
||||
// the owner module is `suffix`.
|
||||
(qualified, Some((target_module, suffix.to_string())))
|
||||
// the owner module is `suffix`. raw-buf.3: when the
|
||||
// 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 {
|
||||
return Err(CheckError::UnknownIdent(name.clone()));
|
||||
};
|
||||
@@ -3546,7 +3563,7 @@ pub(crate) fn synth(
|
||||
// by bare name); the source-level `name` may be a
|
||||
// dot-qualified form (`prefix.suffix`) the synth started
|
||||
// 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)
|
||||
{
|
||||
let (metas, inst) = instantiate(vars, body, counter);
|
||||
@@ -3604,6 +3621,7 @@ pub(crate) fn synth(
|
||||
owner_module: owner.clone(),
|
||||
forall_vars: vars.clone(),
|
||||
metas: metas.clone(),
|
||||
scope: scope.clone(),
|
||||
});
|
||||
return Ok(inst);
|
||||
}
|
||||
|
||||
@@ -358,6 +358,22 @@ fn poly_free_fn_names_for_module(
|
||||
// `<TypeName>.<fn>` for every TypeDef declared in the imported
|
||||
// module — `synth`'s Var-arm resolves these via the TypeDef-first
|
||||
// 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) {
|
||||
for (alias_or_name, target_mod) in imports {
|
||||
let target_types: Vec<String> = env
|
||||
@@ -370,7 +386,10 @@ fn poly_free_fn_names_for_module(
|
||||
if matches!(t, Type::Forall { .. }) {
|
||||
// Implicit-import bare name (today only the
|
||||
// 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.
|
||||
out.insert(format!("{alias_or_name}.{n}"));
|
||||
// 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>`
|
||||
// per TypeDef in the imported module — mirror of
|
||||
// `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) {
|
||||
for (alias_or_name, target_mod) in imports {
|
||||
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) {
|
||||
for (n, t) in target_globals {
|
||||
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());
|
||||
for tn in &target_types {
|
||||
out.insert(format!("{tn}.{n}"), constraints.len());
|
||||
@@ -527,6 +557,17 @@ pub fn mono_symbol_n(base: &str, types: &[Type]) -> String {
|
||||
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 {
|
||||
match primitive_surface_name(ty) {
|
||||
Some(p) => p.to_string(),
|
||||
@@ -581,6 +622,10 @@ pub enum MonoTarget {
|
||||
/// Module in which the polymorphic source `Def::Fn` is declared;
|
||||
/// the synthesised mono `Def::Fn` is appended there.
|
||||
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}"),
|
||||
ailang_core::canonical::type_hash(type_),
|
||||
),
|
||||
MonoTarget::FreeFn { name, type_args, .. } => {
|
||||
MonoTarget::FreeFn { name, type_args, scope, .. } => {
|
||||
let joined = type_args
|
||||
.iter()
|
||||
.map(ailang_core::canonical::type_hash)
|
||||
.collect::<Vec<_>>()
|
||||
.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(),
|
||||
type_args,
|
||||
defining_module: fc.owner_module.clone(),
|
||||
scope: fc.scope.clone(),
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
@@ -1004,9 +1050,9 @@ pub fn synthesise_mono_fn_for_free_fn(
|
||||
target: &MonoTarget,
|
||||
ws: &Workspace,
|
||||
) -> Result<AstFnDef> {
|
||||
let (name, type_args, defining_module) = match target {
|
||||
MonoTarget::FreeFn { name, type_args, defining_module } => {
|
||||
(name, type_args, defining_module)
|
||||
let (name, type_args, defining_module, scope) = match target {
|
||||
MonoTarget::FreeFn { name, type_args, defining_module, scope } => {
|
||||
(name, type_args, defining_module, scope)
|
||||
}
|
||||
MonoTarget::ClassMethod { .. } => {
|
||||
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);
|
||||
|
||||
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,
|
||||
params: source_fn.params.clone(),
|
||||
body: new_body,
|
||||
@@ -1172,8 +1218,8 @@ fn rewrite_mono_calls(
|
||||
MonoTarget::ClassMethod { method, type_, defining_module, .. } => {
|
||||
(mono_symbol(method, type_), defining_module.as_str())
|
||||
}
|
||||
MonoTarget::FreeFn { name: fn_name, type_args, defining_module } => {
|
||||
(mono_symbol_n(fn_name, type_args.as_slice()), defining_module.as_str())
|
||||
MonoTarget::FreeFn { name: fn_name, type_args, defining_module, scope } => {
|
||||
(mono_symbol_n(&scoped_base(scope, fn_name), type_args.as_slice()), defining_module.as_str())
|
||||
}
|
||||
};
|
||||
let new_name = if defining_module == caller_module {
|
||||
@@ -1490,6 +1536,7 @@ pub(crate) fn collect_residuals_ordered(
|
||||
name: fc.name.clone(),
|
||||
type_args,
|
||||
defining_module: fc.owner_module.clone(),
|
||||
scope: fc.scope.clone(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -171,6 +171,20 @@ pub(crate) static INTERCEPTS: &[Intercept] = &[
|
||||
wants_alwaysinline: false,
|
||||
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> {
|
||||
@@ -458,11 +472,38 @@ pub(crate) fn emit_answer(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::{lookup, INTERCEPTS};
|
||||
use ailang_check::mono::mono_symbol;
|
||||
use ailang_core::ast::{Def, Term};
|
||||
use ailang_check::mono::{mono_symbol, mono_symbol_n};
|
||||
use ailang_core::ast::{Def, FnDef, Module, Term, Type};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// INTERCEPTS entries that intercept the monomorphised `__Int`
|
||||
@@ -485,6 +526,34 @@ mod tests {
|
||||
] {
|
||||
for def in &module.defs {
|
||||
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
|
||||
// (float_eq, answer, ...).
|
||||
Def::Fn(f) if matches!(f.body, Term::Intrinsic) => {
|
||||
@@ -548,4 +617,71 @@ mod tests {
|
||||
"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");
|
||||
assert!(allowed.contains("Int"));
|
||||
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.")
|
||||
(type (fn-type (params) (ret (con Int))))
|
||||
(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)))
|
||||
|
||||
@@ -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