# Flux Closures Staring at the MIR it looks like (roughly speaking) ## Representation in MIR "lambdas" are represented as a package `AggregateKind::Closure` which looks like the `(fun-id, tuple-over-free-vars)` and extra type substitutions e.g. for the tyvars map. For example, source like ```rust pub fn test(c: Option<bool>) -> Option<i32> { let mut x = 0; let mut y = false; c.map(|b| if b { x += 1; x } else { y = true; x }) } ``` is transformed, roughly, into a representation ```rust= // map : Option<T>, // pub fn map<T, U, F>(Option<T>, f: F) -> Option<U> // where // F: FnOnce(T) -> U, // // map : (Option<T>, FnOnce) -> Option<U> fn test(c: Option<bool>) -> Option<i32> { // env: t_env (fresh) let env.0 = &mut x; // t_x <: t_env.0 let env.1 = &mut y; // t_y <: t_env.1 let clos = CLOSURE( lam_fn, env ); // Fn(t_in) -> t_out map<T=t,U=u>(c, clos) // Fn(t_in) -> T_out ==> output type of map with t/u Fn(T) -> U Fn(t') -> u' // fresh template from t, u env' // fresh template from env CHECK: lam_fn impl (t', env') -> u' ISSUE: have to check TWO fun-body in SAME VC! } // extra 'env' represented via a struct (ish) .. type env_ty = (&mut i32, &mut bool); // ... explicitly passed into separate func fn lam_fn(b: bool, t: test_ty) -> Option<i32> { if b { let tmp = t.0; *tmp = *tmp + 1; *tmp } else { let tmp = t.0; *tmp = true; *t.0 }) } ``` ## Approach 1. Generate fresh "templates" (distinct kvar for each refinement) * free-vars tuple: `t_tuple` * fun-id-signature: `(t_sig_in) -> t_sig_out` 2. Check `lam_fn` impl `(t_sig_in, t_tuple) -> t_sig_out` * `check_fn_body` 3. Check `env` impl `t_tuple` ```rust (_5.0: &mut i32) = move _6; (_5.1: &i32) = move _7; ``` ``` Fn(T) -> U ``` ## MIR ```rust fn test(_1: std::option::Option<bool>) -> std::option::Option<i32> { let mut _0: std::option::Option<i32>; let mut _2: i32; let mut _3: bool; let mut _4: std::option::Option<bool>; let mut _5: [closure@attic/asdf.rs:7:11: 7:14]; let mut _6: &mut i32; let mut _7: &mut bool; bb0: { _2 = const 0_i32; _3 = const false; _4 = _1; _6 = &mut _2; _7 = &mut _3; _5 = [closure@attic/asdf.rs:7:11: 7:14] { x: move _6, y: move _7 }; _0 = std::option::Option::<bool>::map::<i32, [closure@attic/asdf.rs:7:11: 7:14]>(move _4, move _5) -> [return: bb1, unwind: bb2]; } bb1: { return; } bb2 (cleanup): { resume; } } ``` ### Closure substs We need to get the closure "fields" (i.e., captured environment) from an `AggregateKind::Closure(did, substs)` * We can call [`substs.as_closure()`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/list/struct.List.html#method.as_closure) to get a [`ClosureSubsts`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.ClosureSubsts.html) * Then we can get an iterator over the fields of the struct with [`ClosureSubsts::upvar_tys`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.ClosureSubsts.html#method.upvar_tys) ## Nico's Formulation from Slack (Nico): I think I know how to interpret the different strategies we discussed So for the example ```rust fn foo(opt: Option<bool>) -> Option<i32> { opt.map(|b| if b { 0 } else { 1 } ) } ``` This lowers to something like ```rust struct Closure0<A, B> { ... } fn foo(opt: Option<bool>) -> Option<i32> { let closure = Closure0<A = bool, B = i32> { ... }; map<T=bool, U=i32, F=Closure0>(opt, closure) } ``` Note that the struct Closure0 is generic on the type of input (A) and output (B) We can then instantiate parameters with kvars ```rust struct Closure0<A, B> { ... } fn foo(opt: Option<bool>) -> Option<i32> { let closure = Closure0<A = bool{$k0}, B = i32{$k1}> { ... }; map<T=bool{$k2}, U=i32{$k3}, F=Closure0>(opt, closure) } ``` At the call site we need to prove that Closure0 implements the trait FnOnce(bool{$k2}) -> i32{$k3}. The trick is that we can "generate" an implementation of the trait as we wish ### Option 1 Generate a single implementation at the point we create the closure and then check subtyping. So the implementation is something like ```rust impl FnOnce<bool{$k0}> -> i32{$k1} for Closure0<A=bool{$k0}, B=i32{$k1}> { fn call(b: bool{$k0}) -> i32{$k1} { if b { 0 } else { 1 } } } ``` And then we argue that the subtyping constraint fn(bool{$k0}) -> i32{$k1} <: fn(bool{$k2}) -> i32{$k3} implies Closure0<A = bool{$k0}, B = i32{$k1}>: FnOnce(bool{$k2}) -> i32{$k3}. ### Option 2 Generate an implementation at the call site as ```rust impl FnOnce<bool{$k2}> -> i32{$k3} for Closure0<A=bool{$k2}, B=i32{$k3}> { fn call(b: bool{$k2}) -> i32{$k3} { if b { 0 } else { 1 } } } ``` This is a bit more sketchy because we are dropping the $k0 and the $k1 , but I think the way to argue it's ok is that we are actually creating a different closure/struct at every use site (edited) I understand what you mean, we need to recover that ``` closure(DefId(0:5 ~ closure00[e82c]::test0::{closure#0}), [i32, Binder(FnSig { inputs_and_output: [(bool), i32] }, []), ()], ) implement FnOnce(T) -> U ``` I just hope this information is not somehow “erased” in the HIR because that will be a real PITA (edited) https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.predicates_defined_on https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.predicates_of ```rust fn map<T,U,F>(Option<T>, F) -> Option<U> where F: FnOnce(T) -> U forall F, T, U TRACE: closure_obligs = DefId(2:7540 ~ core[1ad7]::option::{impl#0}::map) with GenericPredicates { parent: Some(DefId(2:7524 ~ core[1ad7]::option::{impl#0})), predicates: [ (Binder(~const TraitPredicate(<F as std::ops::FnOnce<(T,)>>, polarity:Positive), []), (Binder(ProjectionPredicate(AliasTy { substs: [F, (T,)], def_id: DefId(2:2923 ~ core[1ad7]::ops::function::FnOnce::Output) }, Term::Ty(U)), []), impl<T> FnOnce<(T, )> for Closure0 { ... } ``` * To get the id for the traits we can use [`TyCtxt::require_lang_item`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.require_lang_item) ### Closure Body Surgery 1. MAKE body accessible to CHECKER ``` -- flux-driver::callbacks.rs let mir = unsafe { mir_storage::retrieve_mir_body(self.genv.tcx, def_id).body }; ``` specifically the WHOLE module `mir_storage` module including `retrieve_mir_body` should move to flux-middle (so its accessible to the flux-refineck / checker) closure-impl (clos9) ```rust fn clos9(b) { if b { 1 } else { 2 } } ``` closure-client (test0) ```rust pub fn test0(c: Option<bool>) -> Option<i32> { c.map(... clos9 ...) } ``` When REFINE-PHASE in `test0` (i.e. client) have we ALREADY completed the SHAPE-PHASE for 1. ALL functions ? 2. For `clos9` ? See the below -- must be updated/extended to work for a top-level "client" + all the closure-impl that it uses. pub struct Inference<'a> { bb_envs: &'a mut FxHashMap<BasicBlock, TypeEnvInfer>, } pub struct Check<'a> { bb_envs: FxHashMap<BasicBlock, BasicBlockEnv>, kvars: &'a mut KVarStore, } ```rust= fn foo(x:i32[n]) -> Opt<i32[n+5]> { let z = Some(5); z.map(|val| {val + x}); z } fn clos(val:i32, cap_x:i32) { val + cap_x } id :: a -> a; let x = id; fn foo(x:i32[n]) -> Opt<i32[n+5]> { let z = Some(5); let closure = Closure { cap_x: x } /// ... // check_call z.map(closure); // rcx: n: int, ...., // check_call z.map(closure); } fn clos(val:i32, closure: Closure) { val + closure.cap_x } ```