iter 23.2.2-fixup: emit closure adapter for primitive-instance-bodied fns
This commit is contained in:
@@ -1060,13 +1060,20 @@ impl<'a> Emitter<'a> {
|
||||
self.start_block("entry");
|
||||
|
||||
// Iter 23.2: primitive-instance body intercept. Returns true if
|
||||
// the body was emitted in full; we then skip the normal body
|
||||
// lowering for this fn.
|
||||
if self.try_emit_primitive_instance_body(&f.name, &llvm_param_tys, &llvm_ret)? {
|
||||
return Ok(());
|
||||
}
|
||||
// the body was emitted in full (including the closing `}\n\n`).
|
||||
// When that fires we skip the normal body-lowering block below
|
||||
// but still fall through to deferred-thunk flush + closure-pair
|
||||
// emission, so the intercepted fn participates in the same
|
||||
// post-body machinery as every other top-level fn (iter 23.2.2
|
||||
// fixup: previously this short-circuited past
|
||||
// `emit_adapter_and_static_closure`, leaving the fn without a
|
||||
// closure-pair symbol — a footgun the moment any caller
|
||||
// referenced it by value).
|
||||
let body_was_intercepted =
|
||||
self.try_emit_primitive_instance_body(&f.name, &llvm_param_tys, &llvm_ret)?;
|
||||
|
||||
let (val, val_ty) = self.lower_term(&f.body)?;
|
||||
if !body_was_intercepted {
|
||||
let (val, val_ty) = self.lower_term(&f.body)?;
|
||||
if !self.block_terminated {
|
||||
if val_ty != llvm_ret {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
@@ -1185,6 +1192,7 @@ impl<'a> Emitter<'a> {
|
||||
// the function body — no fall-through ret.
|
||||
self.body.push_str("}\n\n");
|
||||
}
|
||||
} // close `if !body_was_intercepted`
|
||||
|
||||
// Iter 8b: flush lambda thunks collected while lowering this fn's
|
||||
// body. They go after the closing `}` of the parent fn, before
|
||||
@@ -3481,4 +3489,61 @@ mod tests {
|
||||
"eq__Str body must call @ail_str_eq; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 23.2.2-fixup: every top-level fn — including primitive-
|
||||
/// instance-bodied ones like `eq__Str` — must flow through
|
||||
/// `emit_adapter_and_static_closure` so that referencing the fn
|
||||
/// as a `Term::Var` value (dictionary entry, by-value pass to
|
||||
/// higher-order code) finds the expected closure-pair symbol.
|
||||
/// The intercept path used to short-circuit past adapter emission,
|
||||
/// leaving `eq__Str` as the only top-level fn in the module
|
||||
/// without an `@ail_<m>_<f>_adapter` / `@ail_<m>_<f>_clos` pair.
|
||||
/// This test pins the invariant.
|
||||
#[test]
|
||||
fn eq_str_mono_symbol_emits_closure_adapter_pair() {
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "prelude".into(),
|
||||
imports: vec![],
|
||||
defs: vec![
|
||||
Def::Fn(FnDef {
|
||||
name: "eq__Str".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![Type::str_(), Type::str_()],
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
body: Term::Lit { lit: Literal::Bool { value: false } },
|
||||
suppress: vec![],
|
||||
doc: None,
|
||||
}),
|
||||
Def::Fn(FnDef {
|
||||
name: "main".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
suppress: vec![],
|
||||
doc: None,
|
||||
}),
|
||||
],
|
||||
};
|
||||
let ir = emit_ir(&m).unwrap();
|
||||
assert!(
|
||||
ir.contains("@ail_prelude_eq__Str_adapter"),
|
||||
"eq__Str must emit a closure adapter; ir was:\n{ir}"
|
||||
);
|
||||
assert!(
|
||||
ir.contains("@ail_prelude_eq__Str_clos"),
|
||||
"eq__Str must emit a static closure pair; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user