1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6 Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,7 analyze::{ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LocalSlot},8 bail,9 evaluate::evaluate,10};1112#[allow(dead_code, reason = "not dead in exp-destruct")]13fn destruct_array(14 start: &[LDestruct],15 rest: Option<&LDestructRest>,16 end: &[LDestruct],1718 fill: &LocalsFrame,19 value: Thunk<Val>,20 ctx: &Context,21) {22 let min_len = start.len() + end.len();23 let has_rest = rest.is_some();24 let full = Thunk!(move || {25 let v = value.evaluate()?;26 let Val::Arr(arr) = v else {27 bail!("expected array");28 };29 if !has_rest {30 if arr.len() != min_len {31 bail!("expected {} elements, got {}", min_len, arr.len32())32 }33 } else if arr.len() < min_len {34 bail!(35 "expected at least {} elements, but array was only {}",36 min_len,37 arr.len32()38 )39 }40 Ok(arr)41 });4243 for (i, d) in start.iter().enumerate() {44 let full = full.clone();45 destruct(46 d,47 fill,48 Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),49 ctx,50 );51 }5253 let start_len = start.len();54 let end_len = end.len();5556 if let Some(LDestructRest::Keep(slot)) = rest {57 let full = full.clone();58 fill.set(59 *slot,60 Thunk!(move || {61 let full = full.evaluate()?;62 let to = full.len() - end_len;63 Ok(Val::Arr(full.slice(start_len..to)))64 }),65 );66 }6768 for (i, d) in end.iter().enumerate() {69 let full = full.clone();70 destruct(71 d,72 fill,73 Thunk!(move || {74 let full = full.evaluate()?;75 Ok(full76 .get(full.len() - end_len + i)?77 .expect("length is checked"))78 }),79 ctx,80 );81 }82}8384#[allow(dead_code, reason = "not dead in exp-destruct")]85fn destruct_object(86 fields: &[LDestructField],87 rest: Option<&LDestructRest>,8889 fill: &LocalsFrame,90 value: Thunk<Val>,91 ctx: &Context,92) {93 use jrsonnet_interner::IStr;94 use rustc_hash::FxHashSet;9596 use crate::{ObjValueBuilder, bail};9798 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();99 let field_names: Vec<(IStr, bool)> = fields100 .iter()101 .map(|f| (f.name.clone(), f.default.is_some()))102 .collect();103 let has_rest = rest.is_some();104 let full = Thunk!(move || {105 let v = value.evaluate()?;106 let Val::Obj(obj) = v else {107 bail!("expected object");108 };109 for (field, has_default) in &field_names {110 if !has_default && !obj.has_field_ex(field.clone(), true) {111 bail!("missing field: {field}");112 }113 }114 if !has_rest {115 let len = obj.len32();116 if len as usize > field_names.len() {117 bail!("too many fields, and rest not found");118 }119 }120 Ok(obj)121 });122123 if let Some(LDestructRest::Keep(slot)) = rest {124 let full = full.clone();125 fill.set(126 *slot,127 Thunk!(move || {128 let full = full.evaluate()?;129 let mut out = ObjValueBuilder::new();130 out.extend_with_core(full.as_standalone());131 out.with_fields_omitted(captured_fields);132 Ok(Val::Obj(out.build()))133 }),134 );135 }136137 for field in fields {138 let field_name = field.name.clone();139 let default_thunk: Option<Thunk<Val>> = field.default.as_ref().map(|(shape, expr)| {140 let expr = expr.clone();141 let env = Context::enter_using(ctx, shape);142 Thunk!(move || evaluate(env, &expr))143 });144145 let field_full = full.clone();146 let value_thunk = Thunk!(move || {147 let obj = field_full.evaluate()?;148 obj.get(field_name)?.map_or_else(149 || default_thunk.as_ref().expect("shape is checked").evaluate(),150 Ok,151 )152 });153154 if let Some(into) = &field.into {155 destruct(into, fill, value_thunk, ctx);156 } else {157 unreachable!("analyzer lowers object-destruct shorthands into `into`");158 }159 }160}161162#[allow(unused_variables)]163pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {164 match d {165 LDestruct::Full(slot) => fill.set(*slot, value),166 #[cfg(feature = "exp-destruct")]167 LDestruct::Skip => {}168 #[cfg(feature = "exp-destruct")]169 LDestruct::Array { start, rest, end } => {170 destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)171 }172 #[cfg(feature = "exp-destruct")]173 LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),174 }175}176177pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {178 for bind in binds {179 let expr = bind.value.clone();180 let env = Context::enter_using(ctx, &bind.value_shape);181 destruct(182 &bind.destruct,183 fill,184 Thunk!(move || evaluate(env, &expr)),185 ctx,186 );187 }188}189190pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}191impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}192193pub fn evaluate_locals_unbound(194 outer: &Context,195 frame_shape: &ClosureShape,196 this_slot: Option<LocalSlot>,197 locals: Rc<Vec<LBind>>,198) -> impl CloneableUnbound<Context> {199 #[derive(Trace, Clone)]200 struct UnboundLocals {201 captures: PackedContext,202 this_slot: Option<LocalSlot>,203 locals: Rc<Vec<LBind>>,204 }205 impl Unbound for UnboundLocals {206 type Bound = Context;207208 fn bind(&self, sup_this: SupThis) -> Result<Context> {209 Ok(self.captures.clone().enter(sup_this, |fill, ctx| {210 if let Some(slot) = self.this_slot {211 let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();212 fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));213 }214 fill_letrec_binds(fill, ctx, &self.locals);215 }))216 }217 }218219 UnboundLocals {220 captures: outer.pack_captures(frame_shape),221 this_slot,222 locals,223 }224}