@@ -3292,25 +3292,50 @@ impl<'a> RustLowerer<'a> {
32923292 expected : & TypeRef ,
32933293 ) -> String {
32943294 let previous_value_types = self . value_types . clone ( ) ;
3295+ let previous_param_effects = self . param_effects . clone ( ) ;
32953296 for ( param, ty) in params. iter ( ) . zip ( expected. fn_params . iter ( ) ) {
32963297 self . value_types . insert ( param. clone ( ) , ty. clone ( ) ) ;
32973298 }
3299+ // Register each closure parameter's data effect so the body lowers
3300+ // exactly like a regular function with the same parameter effects: a
3301+ // `read T` parameter is a borrowed `&T` (reads `.clone()` out of it where
3302+ // needed), a `mut T` parameter is an exclusive `&mut T` whose field
3303+ // assignments propagate to the caller, and a `take`/defaulted parameter is
3304+ // owned by value. This is what lets a stored `mut Ctx` rule mutate `ctx`.
3305+ for ( index, param) in params. iter ( ) . enumerate ( ) {
3306+ if let Some ( effect) = expected. fn_param_effects . get ( index) . copied ( ) . flatten ( ) {
3307+ self . param_effects . insert ( param. clone ( ) , effect) ;
3308+ }
3309+ }
3310+ // The closure's parameter list must match the stored
3311+ // `Rc<dyn Fn(&UOp, &mut Ctx) -> ..>` signature: annotate each parameter
3312+ // with the same ref it would carry as a regular function parameter
3313+ // (`read T` -> `&T`, `mut T` -> `&mut T`, owned otherwise), mirroring
3314+ // `lower_param`'s effect-to-ref mapping.
32983315 let lowered_params = params
32993316 . iter ( )
33003317 . enumerate ( )
33013318 . map ( |( index, param) | {
33023319 let name = rust_ident ( param) ;
3303- expected
3320+ let Some ( ty ) = expected
33043321 . fn_params
33053322 . get ( index)
33063323 . filter ( |ty| self . type_ref_is_concrete_for_annotation ( ty) )
3307- . map ( |ty| {
3308- format ! (
3309- "{name}: {}" ,
3310- self . lower_type_ref( ty, ManagedPosition :: Param )
3311- )
3312- } )
3313- . unwrap_or ( name)
3324+ else {
3325+ return name;
3326+ } ;
3327+ let bare = self . lower_type_ref ( ty, ManagedPosition :: Param ) ;
3328+ let effect = expected. fn_param_effects . get ( index) . copied ( ) . flatten ( ) ;
3329+ // Match the stored `Rc<dyn Fn(..)>` parameter ABI exactly (see
3330+ // `lower_type_ref` for `Fn`): `read T` -> `&T`, `mut T` -> `&mut T`,
3331+ // owned otherwise. Kept uniform (no by-value-`Copy` shortcut) so
3332+ // the closure literal, its stored type, and every call site agree.
3333+ let rust_ty = match effect {
3334+ Some ( DataEffect :: Read ) => format ! ( "&{bare}" ) ,
3335+ Some ( DataEffect :: Mut ) => format ! ( "&mut {bare}" ) ,
3336+ Some ( DataEffect :: Take ) | None => bare,
3337+ } ;
3338+ format ! ( "{name}: {rust_ty}" )
33143339 } )
33153340 . collect :: < Vec < _ > > ( )
33163341 . join ( ", " ) ;
@@ -3323,6 +3348,7 @@ impl<'a> RustLowerer<'a> {
33233348 ) ;
33243349 self . current_return_type = previous_return_type;
33253350 self . value_types = previous_value_types;
3351+ self . param_effects = previous_param_effects;
33263352 return lowered;
33273353 }
33283354 let mut out = String :: new ( ) ;
@@ -3331,6 +3357,7 @@ impl<'a> RustLowerer<'a> {
33313357 out. push ( '}' ) ;
33323358 self . current_return_type = previous_return_type;
33333359 self . value_types = previous_value_types;
3360+ self . param_effects = previous_param_effects;
33343361 out
33353362 }
33363363
@@ -3523,19 +3550,61 @@ impl<'a> RustLowerer<'a> {
35233550 . is_some_and ( |ty| ty. name == "Fn" && ty. is_owned )
35243551 }
35253552
3553+ /// The declared data effect of the `index`-th parameter of a first-class
3554+ /// closure value's stored `Fn` type, so a call site can pass the argument
3555+ /// with the matching Rust ABI (`read` -> `&`, `mut` -> `&mut`).
3556+ fn closure_value_param_effect ( & self , callee : & Callee , index : usize ) -> Option < DataEffect > {
3557+ let Callee :: Name ( name) = callee else {
3558+ return None ;
3559+ } ;
3560+ let ty = self . value_types . get ( name) ?;
3561+ if ty. name != "Fn" {
3562+ return None ;
3563+ }
3564+ ty. fn_param_effects . get ( index) . copied ( ) . flatten ( )
3565+ }
3566+
35263567 fn lower_call_arg_for_callee (
35273568 & mut self ,
35283569 callee : & Callee ,
35293570 arg : & CallArg ,
35303571 index : usize ,
35313572 ) -> String {
3532- // Calling a first-class closure value (`let f = r.fxn; f(read u)`): the
3533- // callee is `Rc<dyn Fn(P0, P1, ..) -> R>` whose parameters lower BY
3534- // VALUE. So each argument is passed by value (a `read` of a non-`Copy`
3535- // value becomes an owned `.clone()`), not borrowed — `f(&u)` would not
3536- // match `Fn(UOp)`.
3573+ // Calling a first-class closure value (`let f = r.fxn; f(read u, mut ctx)`):
3574+ // the callee is `Rc<dyn Fn(P0, P1, ..) -> R>` whose parameters carry the
3575+ // stored `Fn` type's per-parameter data effects. Pass each argument to
3576+ // match that effect's Rust ABI: `read T` -> `&T` (by value for `Copy`),
3577+ // `mut T` -> `&mut T` (the closure may mutate it; the borrow is exclusive
3578+ // for the call), and a `take`/defaulted parameter by value (a `read` of a
3579+ // non-`Copy` value becomes an owned `.clone()`). This mirrors `lower_param`
3580+ // and the stored `Rc<dyn Fn(&UOp, &mut Ctx)>` signature.
35373581 if self . callee_is_closure_value ( callee) {
3538- return self . lower_owned_expr ( & arg. value ) ;
3582+ let effect = self . closure_value_param_effect ( callee, index) ;
3583+ let inner = match & arg. value {
3584+ Expr :: Effect { value, .. } => value. as_ref ( ) ,
3585+ other => other,
3586+ } ;
3587+ return match effect {
3588+ Some ( DataEffect :: Read ) => format ! ( "&{}" , self . lower_expr( inner) ) ,
3589+ Some ( DataEffect :: Mut ) => {
3590+ // When the argument is itself a `mut` parameter it is already
3591+ // a `&mut T`; reborrow it as the closure's `&mut T` argument by
3592+ // passing the binding directly (`f(read u, mut ctx)` where
3593+ // `ctx: &mut Ctx`), exactly like a `mut`-arg to a regular
3594+ // function. Otherwise take `&mut` of the lowered place.
3595+ if let Expr :: Ident ( name, _) = inner
3596+ && self . param_effects . get ( name) == Some ( & DataEffect :: Mut )
3597+ {
3598+ rust_value_ident ( name)
3599+ } else {
3600+ format ! ( "&mut {}" , self . lower_expr( inner) )
3601+ }
3602+ }
3603+ // A defaulted/`take` parameter is passed by value; a `read`
3604+ // call-site marker without a declared effect (older value-model
3605+ // `Fn` types) still lowers by value via `lower_owned_expr`.
3606+ _ => self . lower_owned_expr ( & arg. value ) ,
3607+ } ;
35393608 }
35403609 if runtime_intrinsic_wants_managed_handle_arg ( callee, arg. name . as_deref ( ) )
35413610 && let Expr :: Effect { effect, value, .. } = & arg. value
@@ -4101,6 +4170,7 @@ impl<'a> RustLowerer<'a> {
41014170 is_noescape : false ,
41024171 is_owned : false ,
41034172 fn_params : Vec :: new ( ) ,
4173+ fn_param_effects : Vec :: new ( ) ,
41044174 fn_return : None ,
41054175 span : receiver_type. span . clone ( ) ,
41064176 } ) ,
@@ -4149,6 +4219,7 @@ impl<'a> RustLowerer<'a> {
41494219 is_noescape : false ,
41504220 is_owned : false ,
41514221 fn_params : Vec :: new ( ) ,
4222+ fn_param_effects : Vec :: new ( ) ,
41524223 fn_return : None ,
41534224 span : receiver_type. span . clone ( ) ,
41544225 } ) ,
@@ -4665,10 +4736,24 @@ impl<'a> RustLowerer<'a> {
46654736
46664737 fn lower_type_ref ( & self , ty : & TypeRef , position : ManagedPosition ) -> String {
46674738 if ty. name == "Fn" {
4739+ // A `Fn`-type parameter's data effect determines how the parameter is
4740+ // PASSED at the Rust call boundary: `read T` -> `&T` (shared borrow),
4741+ // `mut T` -> `&mut T` (exclusive borrow, mutation propagates back),
4742+ // and an omitted effect keeps the value-model default (by value). This
4743+ // mirrors how regular fn params lower and is what makes a stored
4744+ // `Rc<dyn Fn(&UOp, &mut Ctx) -> ..>` rule able to mutate `mut Ctx`.
46684745 let params = ty
46694746 . fn_params
46704747 . iter ( )
4671- . map ( |param| self . lower_type_ref ( param, ManagedPosition :: Param ) )
4748+ . enumerate ( )
4749+ . map ( |( index, param) | {
4750+ let lowered = self . lower_type_ref ( param, ManagedPosition :: Param ) ;
4751+ match ty. fn_param_effects . get ( index) . copied ( ) . flatten ( ) {
4752+ Some ( DataEffect :: Read ) => format ! ( "&{lowered}" ) ,
4753+ Some ( DataEffect :: Mut ) => format ! ( "&mut {lowered}" ) ,
4754+ Some ( DataEffect :: Take ) | None => lowered,
4755+ }
4756+ } )
46724757 . collect :: < Vec < _ > > ( )
46734758 . join ( ", " ) ;
46744759 let return_ty = ty. fn_return . as_ref ( ) . map ( |return_ty| {
@@ -5786,6 +5871,7 @@ fn type_ref_from_display(name: &str, span: &Span) -> TypeRef {
57865871 is_noescape : false ,
57875872 is_owned : false ,
57885873 fn_params : Vec :: new ( ) ,
5874+ fn_param_effects : Vec :: new ( ) ,
57895875 fn_return : None ,
57905876 span : span. clone ( ) ,
57915877 }
@@ -5800,6 +5886,7 @@ fn simple_type_ref(name: &str, span: &Span) -> TypeRef {
58005886 is_noescape : false ,
58015887 is_owned : false ,
58025888 fn_params : Vec :: new ( ) ,
5889+ fn_param_effects : Vec :: new ( ) ,
58035890 fn_return : None ,
58045891 span : span. clone ( ) ,
58055892 }
@@ -5898,6 +5985,7 @@ fn fn_type_ref(params: Vec<TypeRef>, return_ty: Option<TypeRef>, span: &Span) ->
58985985 is_fresh : false ,
58995986 is_noescape : true ,
59005987 is_owned : false ,
5988+ fn_param_effects : vec ! [ None ; params. len( ) ] ,
59015989 fn_params : params,
59025990 fn_return : return_ty. map ( Box :: new) ,
59035991 span : span. clone ( ) ,
0 commit comments