feat(lang): eliminate the Implicit ownership default — totality + the drop-soundness it demasks (#55)
Deletes `ParamMode::Implicit`. `ParamMode` is now `{Own, Borrow}`:
every fn-type slot on every signature carries an explicit `own` or
`borrow`, no defaulted position survives anywhere (model 0008 §2,
spec 0062). The parser rejects a bare fn-type slot; `borrow-return`
and `borrow-over-value` reject at the signature; the corpus is
migrated to minimal-ownership modes (consumed ⇒ own, read-only-heap
⇒ borrow, value ⇒ trivial-own). The documented `Implicit`-ret-mode
leak is fixed: an owned heap return now drops exactly once (live=0,
acceptance criterion 5).
This was the easy half. Removing the default ACTIVATED a family of
drop paths that `Implicit` had silently skipped — the pre-cutover
language was leaking (and in places mis-dropping) here rather than
crashing, because an Implicit scrutinee turned the drop off. Making
the modes explicit (Own) turned those paths on and exposed two
latent-bug clusters, all fixed RED-first as part of this cutover:
Drop-soundness family (four legs):
A. lit-sub-pattern double-free — the desugar re-matched the same
owned scrutinee in the lit fall-through; fixed by grouping
consecutive same-ctor arms into one match (bind fields once),
in ailang-core desugar.
B. Cons-husk leak on non-tail arm bodies — the lit-sub-pattern
desugar rebound the owned scrutinee via `Let $mp = xs`, which
bumped consume_count and suppressed the existing fn-return
partial_drop. Fixed by not rebinding a bare-Var scrutinee
(one husk-freeing mechanism, not two).
C. polymorphic `drop_<T>` rc_dec'd monomorphised value fields —
the per-ADT drop fn was emitted once from the polymorphic
TypeDef, defaulting type-var fields to ptr and rc_dec'ing
inline Ints (segfault). Fixed with per-monomorph drop
functions (new ailang-codegen::dropmono): the drop set is
collected from the lowered MIR, value-type fields are skipped,
heap fields still freed once; monomorphic-concrete ADTs keep
their byte-identical un-suffixed drop symbol.
D. static Str literal passed to an `(own Str)` param — the
literal lowers to a header-less rodata constant; the callee's
now-active rc_dec read its length field as a refcount and
freed a static address (segfault). Fixed with the missing
fourth StrRep::Static→Heap promotion in lower_to_mir's App arm,
gated on Own mode (borrow args stay static, no regression).
over-strict-mode lint over-fired: it suggested `(borrow V)` for
value-typed params (which `borrow-over-value` rejects — own is the
only legal mode there) and fired on `(intrinsic)` bodies (whose
consumption the linearity walk cannot observe). Tightened to skip
both; contract 0008 updated to the narrowed firing scope.
Irreversible step — canonical-form hash reset (model 0008 §6,
acceptance criterion 6). Every signature now carries explicit modes,
so the hashable canonical JSON changed for every module. RATIFY:
the corpus-wide hash-pin reset (hash_pin, prelude_module_hash_pin,
mono_hash_stability, eq_ord_e2e, embed_export_hash_stable, the
ct4/iter*/loop_recur schema-extension pins) and the list ir_snapshot
golden were regenerated once, deliberately, as the intended one-time
consequence of removing the mode elision from the canonical form —
not a regression. Each regenerated hash verified deterministic across
two runs.
Also fixes a pre-existing latent failure surfaced by the verification
gate, unrelated to this cutover: the `every_contract_names_a_resolvable_
ratifying_test` resolver (design_index_pin) could not resolve the
" + " dual-link ratifying-test form (`uniqueness.rs + linearity.rs`)
that the #57 audit-close (dfdc65f) introduced — it shipped red on that
commit. Resolver taught the dual-link form, mirroring its sibling.
Verification: cargo test --workspace = 731 passed, 0 failed (twice,
stable); e2e 102 passed, no binary exits non-zero (corpus crash-free);
grep-clean for Implicit/fn_implicit/mode_eq across crates; every drop
fix confirmed via emitted IR + AILANG_RC_STATS balance on the head==K,
head!=K, and Nil paths. Three BLOCKEDs en route (the unsound first
husk-dec attempt, the over-strict derivation premise, the leg-B fix
direction) were each treated as a real design/spec gap and rediagnosed,
not patched over.
Supersedes #54 (return-position-only leak patch). Precondition #57
(linearity hardening) was already met. Spec docs/specs/0062, plan
docs/plans/0121.
closes #55
This commit is contained in:
@@ -40,6 +40,7 @@ use ailang_mir::{Callee, MArg, MTerm, MirDef, MirWorkspace, Mode, StrRep};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
mod drop;
|
||||
mod dropmono;
|
||||
mod escape;
|
||||
mod intercepts;
|
||||
mod lambda;
|
||||
@@ -396,6 +397,14 @@ fn lower_workspace_inner(mir: &MirWorkspace, alloc: AllocStrategy, target: Targe
|
||||
module_consts.insert(mname.clone(), consts);
|
||||
}
|
||||
|
||||
// Leg C: workspace-global per-monomorph drop metadata. Built once
|
||||
// here (after the symbol-table pass, before lowering) and shared by
|
||||
// reference with every `Emitter`. Computes which polymorphic ADTs
|
||||
// take a per-instantiation drop fn and the concrete arg-tuples each
|
||||
// is used at, so the emission loop and the call-site manglers agree
|
||||
// on every `drop_<m>_<T>__<suffix>` symbol.
|
||||
let drop_monos = dropmono::collect_drop_monos(mir);
|
||||
|
||||
// Pass 2: lower per module. Globals/strings are accumulated per module,
|
||||
// because they are mangled per module.
|
||||
for (mname, mir_module) in &mir.modules {
|
||||
@@ -432,6 +441,7 @@ fn lower_workspace_inner(mir: &MirWorkspace, alloc: AllocStrategy, target: Targe
|
||||
&module_ctor_index,
|
||||
&module_consts,
|
||||
import_map,
|
||||
&drop_monos,
|
||||
alloc,
|
||||
);
|
||||
emitter
|
||||
@@ -770,6 +780,13 @@ struct Emitter<'a> {
|
||||
module_user_fns: &'a BTreeMap<String, BTreeMap<String, FnSig>>,
|
||||
/// Import map of the current module (alias/module name → actual module name).
|
||||
import_map: BTreeMap<String, String>,
|
||||
/// workspace-global per-monomorph drop metadata (leg C). Tells the
|
||||
/// drop-fn emission loop which polymorphic ADTs to emit one drop fn
|
||||
/// per instantiation for, and tells the call-site manglers whether
|
||||
/// a `Type::Con` takes a per-monomorph `__<suffix>` on its
|
||||
/// `drop_`/`partial_drop_` symbol. Shared by reference with every
|
||||
/// `Emitter`.
|
||||
drop_monos: &'a dropmono::DropAdtMeta,
|
||||
/// ADT table: type_name -> list of ctors in definition order.
|
||||
/// Tag of a ctor = index in this list.
|
||||
/// Kept around for future tools (pretty-printer for ADT values,
|
||||
@@ -874,15 +891,15 @@ struct Emitter<'a> {
|
||||
/// entry) from the fn type's `param_modes`. Consulted by
|
||||
/// `lower_match`'s arm-close pattern-binder dec emission (Iter A) to
|
||||
/// decide whether the scrutinee was statically owned: if the
|
||||
/// scrutinee resolves to a fn-param whose mode is `Borrow` or
|
||||
/// `Implicit`, the pattern-binder dec must NOT fire — the caller
|
||||
/// still holds a reference and dec'ing the pattern-binder would
|
||||
/// fragment the caller's structure.
|
||||
/// scrutinee resolves to a fn-param whose mode is `Borrow`, the
|
||||
/// pattern-binder dec must NOT fire — the caller still holds a
|
||||
/// reference and dec'ing the pattern-binder would fragment the
|
||||
/// caller's structure.
|
||||
///
|
||||
/// Symmetric with the Iter B gate at fn return (`emit_fn`'s Own-
|
||||
/// param dec): both sites must check the param-mode signal before
|
||||
/// dec'ing, because Implicit and Borrow do not carry the "caller
|
||||
/// handed off ownership" signal that makes the dec safe.
|
||||
/// param dec): both sites check the param-mode signal before
|
||||
/// dec'ing, because `Borrow` does not carry the "caller handed off
|
||||
/// ownership" signal that makes the dec safe.
|
||||
current_param_modes: BTreeMap<String, ParamMode>,
|
||||
/// Per-fn map of name → (alloca SSA name, AIL element type) for
|
||||
/// alloca-resident loop binders. Populated on entry to a
|
||||
@@ -973,6 +990,7 @@ impl<'a> Emitter<'a> {
|
||||
module_ctor_index: &'a BTreeMap<String, BTreeMap<String, CtorRef>>,
|
||||
module_consts: &'a BTreeMap<String, BTreeMap<String, ConstDef>>,
|
||||
import_map: BTreeMap<String, String>,
|
||||
drop_monos: &'a dropmono::DropAdtMeta,
|
||||
alloc: AllocStrategy,
|
||||
) -> Self {
|
||||
let mut types: BTreeMap<String, Vec<CtorInfo>> = BTreeMap::new();
|
||||
@@ -1030,6 +1048,7 @@ impl<'a> Emitter<'a> {
|
||||
str_counter: 0,
|
||||
module_user_fns,
|
||||
import_map,
|
||||
drop_monos,
|
||||
types,
|
||||
module_ctor_index,
|
||||
module_consts,
|
||||
@@ -1293,10 +1312,9 @@ impl<'a> Emitter<'a> {
|
||||
fn emit_fn(&mut self, f: &FnDef, body: Option<&MTerm>) -> Result<()> {
|
||||
// also lift `param_modes` out of the fn type. The
|
||||
// fn-return Own-param dec emission below consults it to decide
|
||||
// which params get a drop call before `ret`. `Implicit`
|
||||
// entries (legacy / unannotated) and `Borrow` entries are
|
||||
// skipped — only `Own` carries the static "caller handed off
|
||||
// ownership" signal.
|
||||
// which params get a drop call before `ret`. `Borrow` entries
|
||||
// are skipped — only `Own` carries the static "caller handed
|
||||
// off ownership" signal.
|
||||
let (param_tys, ret_ty, param_modes) = match &f.ty {
|
||||
Type::Fn {
|
||||
params,
|
||||
@@ -1339,7 +1357,10 @@ impl<'a> Emitter<'a> {
|
||||
self.pending_entry_allocas.clear();
|
||||
self.entry_block_end_marker = None;
|
||||
for (i, pname) in f.params.iter().enumerate() {
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit);
|
||||
// Codegen-synthesised fn-defs (lambda thunks, local-rec
|
||||
// lifts) may carry an empty `param_modes`; fall back to
|
||||
// `Own` (the synthesis default) rather than index-panic.
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own);
|
||||
self.current_param_modes.insert(pname.clone(), mode);
|
||||
}
|
||||
// run escape analysis over the fn body. The result
|
||||
@@ -1469,11 +1490,10 @@ impl<'a> Emitter<'a> {
|
||||
// the caller's frame; caller dec's, not us),
|
||||
// - the current block is still open.
|
||||
//
|
||||
// `Implicit`-mode params do NOT get this dec: they have
|
||||
// no static "caller handed off ownership" signal —
|
||||
// emitting a dec here might double-dec a value the caller
|
||||
// also dec's. `Borrow`-mode params definitely don't get
|
||||
// dec'd (the caller still owns them).
|
||||
// `Borrow`-mode params do NOT get dec'd: the caller still
|
||||
// owns them, so there is no caller-handed-off-ownership
|
||||
// signal and a dec here would fragment the caller's
|
||||
// structure.
|
||||
//
|
||||
// Closes the 18c.3/18c.4 carve-out: "fn parameters still
|
||||
// don't get dec'd at fn return — the caller-handed-off-
|
||||
@@ -1490,7 +1510,7 @@ impl<'a> Emitter<'a> {
|
||||
if plty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit);
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own);
|
||||
if !matches!(mode, ParamMode::Own) {
|
||||
continue;
|
||||
}
|
||||
@@ -1812,8 +1832,8 @@ impl<'a> Emitter<'a> {
|
||||
// If `value` is a `Term::Var` referencing a name in
|
||||
// `current_param_modes`, the let-binder inherits that
|
||||
// mode for the duration of the body. Without this,
|
||||
// `(let a t (match a ...))` where `t` is an Implicit
|
||||
// / Borrow-mode param defeats the
|
||||
// `(let a t (match a ...))` where `t` is a
|
||||
// `Borrow`-mode param defeats the
|
||||
// `scrutinee_is_owned` gate in `lower_match` (the
|
||||
// gate looks up `a` in `current_param_modes`, misses,
|
||||
// and defaults to "owned" — Iter A then dec's
|
||||
@@ -2690,8 +2710,7 @@ impl<'a> Emitter<'a> {
|
||||
// alias whose owner is some other binder and is dropped
|
||||
// there, never here;
|
||||
// - the matching callee param mode is `Borrow` — `Own` slots
|
||||
// consume the arg (the callee dec's it), `Implicit` is the
|
||||
// back-compat lane that carries no transfer signal;
|
||||
// consume the arg (the callee dec's it);
|
||||
// - the dropped SSA is never the call result `dst` (an input
|
||||
// argument SSA is always distinct from the freshly-minted
|
||||
// result SSA), so this can never dec a value that flows out
|
||||
@@ -2702,7 +2721,7 @@ impl<'a> Emitter<'a> {
|
||||
// `param_modes`), so the borrow-slot test reads it directly
|
||||
// — no re-lookup of the callee's signature from a codegen
|
||||
// sig table. `Borrow` slots borrow the arg, so an Own-ret
|
||||
// heap temp landing in one is dropped here; `Own`/`Implicit`
|
||||
// heap temp landing in one is dropped here; `Own`
|
||||
// slots consume the arg (the callee dec's it).
|
||||
for (arg, (arg_ssa, arg_ty)) in args.iter().zip(compiled_args.iter()) {
|
||||
let is_borrow_slot = matches!(arg.mode, Mode::Borrow);
|
||||
@@ -3168,7 +3187,7 @@ mod tests {
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["a".into(), "b".into()],
|
||||
body: Term::App {
|
||||
@@ -3192,7 +3211,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3238,7 +3257,7 @@ mod tests {
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit {
|
||||
@@ -3313,7 +3332,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3386,7 +3405,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3418,7 +3437,7 @@ mod tests {
|
||||
ret: Box::new(ret_ty),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body,
|
||||
@@ -3491,7 +3510,7 @@ mod tests {
|
||||
name: name.into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![], ret: Box::new(ret_ty), effects: vec![],
|
||||
param_modes: vec![], ret_mode: ParamMode::Implicit,
|
||||
param_modes: vec![], ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![], body, suppress: vec![], doc: None,
|
||||
export: None,
|
||||
@@ -3543,7 +3562,7 @@ mod tests {
|
||||
name: name.into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![], ret: Box::new(Type::float()), effects: vec![],
|
||||
param_modes: vec![], ret_mode: ParamMode::Implicit,
|
||||
param_modes: vec![], ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![], body, suppress: vec![], doc: None,
|
||||
export: None,
|
||||
@@ -3553,7 +3572,7 @@ mod tests {
|
||||
name: "main".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![], ret: Box::new(Type::unit()), effects: vec![],
|
||||
param_modes: vec![], ret_mode: ParamMode::Implicit,
|
||||
param_modes: vec![], ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![], body: Term::Lit { lit: Literal::Unit },
|
||||
suppress: vec![], doc: None,
|
||||
@@ -3598,7 +3617,7 @@ mod tests {
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
// Body is a placeholder — the intercept must
|
||||
@@ -3616,7 +3635,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3661,7 +3680,7 @@ mod tests {
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
body: Term::Lit { lit: Literal::Bool { value: false } },
|
||||
@@ -3676,7 +3695,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3725,7 +3744,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3824,8 +3843,11 @@ mod tests {
|
||||
params: vec![param_ail_ty.clone(), param_ail_ty.clone()],
|
||||
ret: Box::new(Type::Con { name: "Ordering".into(), args: vec![] }),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
// value-typed params (Int/Bool) cannot be `(borrow V)` —
|
||||
// the cutover's `borrow-over-value` reject forbids it; a
|
||||
// value type is copied, so `own` is the only legal mode.
|
||||
param_modes: vec![ParamMode::Own, ParamMode::Own],
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
// placeholder body; the `compare__<T>` intercept overrides
|
||||
@@ -3849,7 +3871,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3894,7 +3916,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3930,7 +3952,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -3972,7 +3994,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4033,7 +4055,7 @@ mod tests {
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
body: Term::Lit { lit: Literal::Bool { value: false } },
|
||||
@@ -4048,7 +4070,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -4129,7 +4151,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4170,7 +4192,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4212,7 +4234,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4254,7 +4276,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4296,7 +4318,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
|
||||
Reference in New Issue
Block a user