1use std::{collections::HashMap, hash::BuildHasher};23use jrsonnet_interner::IStr;4use jrsonnet_ir::{BindSpec, Destruct};56#[cfg(feature = "exp-preserve-order")]7use crate::evaluate;8use crate::{9 Context, Pending, Thunk, Val, bail,10 error::{ErrorKind::*, Result},11 evaluate_method, evaluate_named_param,12};1314#[allow(clippy::too_many_lines)]15#[allow(unused_variables)]16pub fn destruct<H: BuildHasher>(17 d: &Destruct,18 parent: Thunk<Val>,19 fctx: Pending<Context>,20 new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,21) -> Result<()> {22 match d {23 Destruct::Full(v) => {24 let old = new_bindings.insert(v.clone(), parent);25 if old.is_some() {26 bail!(DuplicateLocalVar(v.clone()))27 }28 }29 #[cfg(feature = "exp-destruct")]30 Destruct::Skip => {}31 #[cfg(feature = "exp-destruct")]32 Destruct::Array { start, rest, end } => {33 use jrsonnet_ir::DestructRest;3435 let min_len = start.len() + end.len();36 let has_rest = rest.is_some();37 let full = Thunk!(move || {38 let v = parent.evaluate()?;39 let Val::Arr(arr) = v else {40 bail!("expected array");41 };42 if !has_rest {43 if arr.len() != min_len {44 bail!("expected {} elements, got {}", min_len, arr.len())45 }46 } else if arr.len() < min_len {47 bail!(48 "expected at least {} elements, but array was only {}",49 min_len,50 arr.len()51 )52 }53 Ok(arr)54 });5556 {57 for (i, d) in start.iter().enumerate() {58 let full = full.clone();59 destruct(60 d,61 Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),62 fctx.clone(),63 new_bindings,64 )?;65 }66 }6768 match rest {69 Some(DestructRest::Keep(v)) => {70 let start = start.len();71 let end = end.len();72 let full = full.clone();73 destruct(74 &Destruct::Full(v.clone()),75 Thunk!(move || {76 let full = full.evaluate()?;77 let to = full.len() - end;78 Ok(Val::Arr(full.slice(79 Some(start as i32),80 Some(to as i32),81 None,82 )))83 }),84 fctx.clone(),85 new_bindings,86 )?;87 }88 Some(DestructRest::Drop) | None => {}89 }9091 {92 for (i, d) in end.iter().enumerate() {93 let full = full.clone();94 let end = end.len();95 destruct(96 d,97 Thunk!(move || {98 let full = full.evaluate()?;99 Ok(full.get(full.len() - end + i)?.expect("length is checked"))100 }),101 fctx.clone(),102 new_bindings,103 )?;104 }105 }106 }107 #[cfg(feature = "exp-destruct")]108 Destruct::Object { fields, rest } => {109 use jrsonnet_ir::DestructRest;110 use rustc_hash::FxHashSet;111112 use crate::ObjValueBuilder;113114 let captured_fields: FxHashSet<_> = fields.iter().map(|f| f.0.clone()).collect();115 let field_names: Vec<_> = fields116 .iter()117 .map(|f| (f.0.clone(), f.2.is_some()))118 .collect();119 let has_rest = rest.is_some();120 let full = Thunk!(move || {121 let v = parent.evaluate()?;122 let Val::Obj(obj) = v else {123 bail!("expected object");124 };125 for (field, has_default) in &field_names {126 if !has_default && !obj.has_field_ex(field.clone(), true) {127 bail!("missing field: {field}");128 }129 }130 if !has_rest {131 let len = obj.len();132 if len > field_names.len() {133 bail!("too many fields, and rest not found");134 }135 }136 Ok(obj)137 });138139 match rest {140 Some(DestructRest::Keep(v)) => {141 let full = full.clone();142 destruct(143 &Destruct::Full(v.clone()),144 Thunk!(move || {145 let full = full.evaluate()?;146 let mut builder = ObjValueBuilder::new();147 builder148 .reserve_cores(1)149 .extend_with_core(full.as_standalone());150 builder.with_fields_omitted(captured_fields);151 Ok(Val::Obj(builder.build()))152 }),153 fctx.clone(),154 new_bindings,155 )?;156 }157 Some(DestructRest::Drop) | None => {}158 }159160 for (field, d, default) in fields {161 let default = default.clone().map(|e| (fctx.clone(), e));162 let value = {163 let field = field.clone();164 let full = full.clone();165 Thunk!(move || {166 let full = full.evaluate()?;167 if let Some(field) = full.get(field)? {168 Ok(field)169 } else {170 let (fctx, expr) = default.as_ref().expect("shape is checked");171 Ok(crate::evaluate(fctx.clone().unwrap(), expr)?)172 }173 })174 };175176 if let Some(d) = d {177 destruct(d, value, fctx.clone(), new_bindings)?;178 } else {179 destruct(180 &Destruct::Full(field.clone()),181 value,182 fctx.clone(),183 new_bindings,184 )?;185 }186 }187 }188 }189 Ok(())190}191192pub fn evaluate_dest<H: BuildHasher>(193 d: &BindSpec,194 fctx: Pending<Context>,195 new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,196) -> Result<()> {197 match d {198 BindSpec::Field { into, value } => {199 let name = into.name();200 let value = value.clone();201 let data = {202 let fctx = fctx.clone();203 Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))204 };205 destruct(into, data, fctx, new_bindings)?;206 }207 BindSpec::Function {208 name,209 params,210 value,211 } => {212 let params = params.clone();213 let name = name.clone();214 let value = value.clone();215 let old = new_bindings.insert(name.clone(), {216 let name = name.clone();217 Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))218 });219 if old.is_some() {220 bail!(DuplicateLocalVar(name))221 }222 }223 }224 Ok(())225}