1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6 analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},7 bail,8 evaluate::evaluate,9 Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,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 value: Thunk<Val>,19 fctx: Pending<Context>,20 builder: &mut ContextBuilder,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() as usize != min_len {31 bail!("expected {} elements, got {}", min_len, arr.len())32 }33 } else if (arr.len() as usize) < min_len {34 bail!(35 "expected at least {} elements, but array was only {}",36 min_len,37 arr.len()38 )39 }40 Ok(arr)41 });4243 for (i, d) in start.iter().enumerate() {44 let full = full.clone();45 destruct(46 d,47 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),48 fctx.clone(),49 builder,50 );51 }5253 let start_len = start.len() as u32;54 let end_len = end.len() as u32;5556 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {57 let full = full.clone();58 builder.bind(59 id,60 Thunk!(move || {61 let full = full.evaluate()?;62 let to = full.len() - end_len;63 Ok(Val::Arr(full.slice(64 Some(start_len as i32),65 Some(to as i32),66 None,67 )))68 }),69 );70 }7172 for (i, d) in end.iter().enumerate() {73 let full = full.clone();74 destruct(75 d,76 Thunk!(move || {77 let full = full.evaluate()?;78 Ok(full79 .get(full.len() - end_len + i as u32)?80 .expect("length is checked"))81 }),82 fctx.clone(),83 builder,84 );85 }86}8788#[allow(dead_code, reason = "not dead in exp-destruct")]89fn destruct_object(90 fields: &[LDestructField],91 rest: Option<LDestructRest>,9293 value: Thunk<Val>,94 fctx: Pending<Context>,95 builder: &mut ContextBuilder,96) {97 use jrsonnet_interner::IStr;98 use rustc_hash::FxHashSet;99100 use crate::{bail, ObjValueBuilder};101102 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();103 let field_names: Vec<(IStr, bool)> = fields104 .iter()105 .map(|f| (f.name.clone(), f.default.is_some()))106 .collect();107 let has_rest = rest.is_some();108 let full = Thunk!(move || {109 let v = value.evaluate()?;110 let Val::Obj(obj) = v else {111 bail!("expected object");112 };113 for (field, has_default) in &field_names {114 if !has_default && !obj.has_field_ex(field.clone(), true) {115 bail!("missing field: {field}");116 }117 }118 if !has_rest {119 let len = obj.len();120 if len as usize > field_names.len() {121 bail!("too many fields, and rest not found");122 }123 }124 Ok(obj)125 });126127 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {128 let full = full.clone();129 builder.bind(130 id,131 Thunk!(move || {132 let full = full.evaluate()?;133 let mut out = ObjValueBuilder::new();134 out.extend_with_core(full.as_standalone());135 out.with_fields_omitted(captured_fields);136 Ok(Val::Obj(out.build()))137 }),138 );139 }140141 for field in fields {142 let field_name = field.name.clone();143 let default: Option<(Pending<Context>, Rc<LExpr>)> =144 field.default.as_ref().map(|e| (fctx.clone(), e.clone()));145 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 || {150 let (fctx, expr) = default.as_ref().expect("shape is checked");151 evaluate(fctx.unwrap(), expr)152 },153 Ok,154 )155 });156157 if let Some(into) = &field.into {158 destruct(into, value_thunk, fctx.clone(), builder);159 } else {160 unreachable!("analyzer lowers object-destruct shorthands into `into`");161 }162 }163}164165166167168169#[allow(unused_variables)]170pub fn destruct(171 d: &LDestruct,172 value: Thunk<Val>,173 fctx: Pending<Context>,174 builder: &mut ContextBuilder,175) {176 match d {177 LDestruct::Full(id) => builder.bind(*id, value),178 #[cfg(feature = "exp-destruct")]179 LDestruct::Skip => {}180 #[cfg(feature = "exp-destruct")]181 LDestruct::Array { start, rest, end } => destruct_array(start, rest, end, value, fctx, builder),182 #[cfg(feature = "exp-destruct")]183 LDestruct::Object { fields, rest } => destruct_object(fields, rest, value, fctx, builder),184 }185}186187188189190pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {191 let value = bind.value.clone();192 let fctx_clone = fctx.clone();193 let thunk = Thunk!(move || {194 let ctx = fctx_clone.unwrap();195 evaluate(ctx, &value)196 });197 destruct(&bind.destruct, thunk, fctx, builder);198}199200201202pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {203 if binds.is_empty() {204 return parent;205 }206 let fctx = Context::new_future();207 let mut builder =208 ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());209 for bind in binds {210 evaluate_dest(bind, fctx.clone(), &mut builder);211 }212 builder.build().into_future(fctx)213}214215pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}216impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}217218pub fn evaluate_locals_unbound(219 fctx: Context,220 locals: Rc<Vec<LBind>>,221 this_id: Option<LocalId>,222) -> impl CloneableUnbound<Context> {223 #[derive(Trace, Clone)]224 struct UnboundLocals {225 fctx: Context,226 locals: Rc<Vec<LBind>>,227 this_id: Option<LocalId>,228 }229 impl Unbound for UnboundLocals {230 type Bound = Context;231232 fn bind(&self, sup_this: SupThis) -> Result<Context> {233 let parent = self.fctx.clone();234235 let fctx = Context::new_future();236 let mut builder = ContextBuilder::extend(237 parent,238 self.locals.iter().map(|b| b.destruct.ids().len()).sum(),239 );240 for b in self.locals.iter() {241 evaluate_dest(b, fctx.clone(), &mut builder);242 }243 if let Some(this_id) = self.this_id {244 builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));245 }246 let ctx = builder.build_sup_this(sup_this).into_future(fctx);247 Ok(ctx)248 }249 }250251 UnboundLocals {252 fctx,253 locals,254 this_id,255 }256}