difftreelog
refactor do not use eager compspec for destructuring
in: master
1 file changed
crates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_types::ValType;45use super::{6 destructure::{self, evaluate_locals, evaluate_locals_unbound},7 evaluate_field_member_static, evaluate_field_member_unbound,8};9use crate::{10 Context, ContextBuilder, ObjValue, ObjValueBuilder, Pending, Result, Thunk, Val,11 analyze::{LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp, LocalId},12 arr::ArrValue,13 bail,14 error::ErrorKind::*,15 evaluate::evaluate,16};1718trait CompCollector {19 fn reserve(&mut self, _guaranteed: usize) {}20 fn collect(&mut self, ctx: Context) -> Result<()>;21}2223struct EagerArrCollector<'a> {24 out: &'a mut Vec<Val>,25 value: &'a LExpr,26}27impl CompCollector for EagerArrCollector<'_> {28 fn reserve(&mut self, size_hint: usize) {29 self.out.reserve(size_hint);30 }31 fn collect(&mut self, ctx: Context) -> Result<()> {32 self.out.push(evaluate(ctx, self.value)?);33 Ok(())34 }35}3637struct LazyArrCollector<'a> {38 out: &'a mut Vec<Thunk<Val>>,39 value: &'a Rc<LExpr>,40}41impl CompCollector for LazyArrCollector<'_> {42 fn reserve(&mut self, size_hint: usize) {43 self.out.reserve(size_hint);44 }45 fn collect(&mut self, ctx: Context) -> Result<()> {46 let value_expr = self.value.clone();47 self.out.push(Thunk!(move || evaluate(ctx, &value_expr)));48 Ok(())49 }50}5152struct ObjCompCollectorStatic<'a> {53 builder: &'a mut ObjValueBuilder,54 locals: &'a [LBind],55 field: &'a LFieldMember,56}57impl CompCollector for ObjCompCollectorStatic<'_> {58 fn reserve(&mut self, guaranteed: usize) {59 self.builder.reserve_fields(guaranteed);60 }61 fn collect(&mut self, inner_ctx: Context) -> Result<()> {62 let value_ctx = evaluate_locals(inner_ctx.clone(), self.locals);63 evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)64 }65}6667struct ObjCompCollectorUnbound<'a> {68 builder: &'a mut ObjValueBuilder,69 locals: Rc<Vec<LBind>>,70 this_id: Option<LocalId>,71 field: &'a LFieldMember,72}73impl CompCollector for ObjCompCollectorUnbound<'_> {74 fn reserve(&mut self, guaranteed: usize) {75 self.builder.reserve_fields(guaranteed);76 }77 fn collect(&mut self, inner_ctx: Context) -> Result<()> {78 let uctx = evaluate_locals_unbound(inner_ctx.clone(), self.locals.clone(), self.this_id);79 evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)80 }81}8283pub fn evaluate_obj_comp(84 super_obj: Option<ObjValue>,85 ctx: Context,86 comp: &LObjComp,87) -> Result<Val> {88 let mut builder = ObjValueBuilder::new();89 if let Some(super_obj) = super_obj {90 builder.with_super(super_obj);91 }9293 let cached_overs = cache_overs(&ctx, &comp.compspecs)?;94 if comp.this.is_some() || comp.uses_super {95 evaluate_compspecs(96 ctx,97 &comp.compspecs,98 &cached_overs,99 0,100 0,101 &mut ObjCompCollectorUnbound {102 builder: &mut builder,103 locals: comp.locals.clone(),104 this_id: comp.this,105 field: &comp.field,106 },107 )?;108 } else {109 evaluate_compspecs(110 ctx,111 &comp.compspecs,112 &cached_overs,113 0,114 0,115 &mut ObjCompCollectorStatic {116 builder: &mut builder,117 locals: &comp.locals,118 field: &comp.field,119 },120 )?;121 }122123 Ok(Val::Obj(builder.build()))124}125126pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {127 let cached_overs = cache_overs(&ctx, &comp.compspecs)?;128129 // In eager evaluation, Context is not captured, thus updates in CoW fashion will likely to success130 'eager: {131 let mut out = Vec::new();132133 if evaluate_compspecs_eager(134 ctx.clone(),135 &comp.compspecs,136 &cached_overs,137 0,138 0,139 &mut EagerArrCollector {140 out: &mut out,141 value: &comp.value,142 },143 )144 .is_err()145 {146 break 'eager;147 }148 return Ok(Val::arr(out));149 }150151 let mut items: Vec<Thunk<Val>> = Vec::new();152 evaluate_compspecs(153 ctx,154 &comp.compspecs,155 &cached_overs,156 0,157 0,158 &mut LazyArrCollector {159 out: &mut items,160 value: &comp.value,161 },162 )?;163 Ok(Val::arr(items))164}165166fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<ArrValue>>> {167 specs168 .iter()169 .map(|spec| {170 Ok(match spec {171 LCompSpec::For {172 over,173 loop_invariant: true,174 ..175 } => {176 let val = evaluate(ctx.clone(), over)?;177 let Val::Arr(arr) = val else {178 bail!(InComprehensionCanOnlyIterateOverArray)179 };180 Some(arr)181 }182 _ => None,183 })184 })185 .collect::<Result<_>>()186}187188fn evaluate_compspecs_eager(189 ctx: Context,190 specs: &[LCompSpec],191 cached_overs: &[Option<ArrValue>],192 idx: usize,193 guaranteed_reserve: usize,194 collector: &mut dyn CompCollector,195) -> Result<()> {196 if idx >= specs.len() {197 collector.reserve(guaranteed_reserve);198 return collector.collect(ctx);199 }200 match &specs[idx] {201 LCompSpec::If(cond) => {202 let val = evaluate(ctx.clone(), cond)?;203 let Val::Bool(b) = val else {204 bail!(TypeMismatch(205 "if spec condition",206 vec![ValType::Bool],207 val.value_type()208 ))209 };210 if b {211 evaluate_compspecs_eager(ctx, specs, cached_overs, idx + 1, 0, collector)?;212 }213 }214 LCompSpec::For { destruct, over, .. } => {215 let arr = if let Some(cached) = &cached_overs[idx] {216 cached.clone()217 } else {218 let arr_val = evaluate(ctx.clone(), over)?;219 let Val::Arr(arr) = arr_val else {220 bail!(InComprehensionCanOnlyIterateOverArray)221 };222 arr223 };224 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;225 match destruct {226 LDestruct::Full(id) => {227 let id = *id;228 let mut inner_ctx = ContextBuilder::extend(ctx, 1).build();229 for (i, item) in arr.iter().enumerate() {230 // TODO: reuse one ContextBuilder for full evaluate_compspecs pipeline231 inner_ctx.cow_fill_binding(id, Thunk::evaluated(item?));232 evaluate_compspecs_eager(233 inner_ctx.clone(),234 specs,235 cached_overs,236 idx + 1,237 if i == 0 { inner_reserve } else { 0 },238 collector,239 )?;240 }241 }242 // TODO: Should not be eager? CoW won't work here243 #[cfg(feature = "exp-destruct")]244 _ => {245 for (i, item) in arr.iter().enumerate() {246 let item_val = item?;247 let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);248 let fctx = Pending::new();249 destructure::destruct(250 destruct,251 Thunk::evaluated(item_val),252 fctx.clone(),253 &mut inner_builder,254 );255 let inner_ctx = inner_builder.build().into_future(fctx);256 evaluate_compspecs_eager(257 inner_ctx,258 specs,259 cached_overs,260 idx + 1,261 if i == 0 { inner_reserve } else { 0 },262 collector,263 )?;264 }265 }266 }267 }268 }269 Ok(())270}271272fn evaluate_compspecs(273 ctx: Context,274 specs: &[LCompSpec],275 cached_overs: &[Option<ArrValue>],276 idx: usize,277 guaranteed_reserve: usize,278 collector: &mut dyn CompCollector,279) -> Result<()> {280 if idx >= specs.len() {281 collector.reserve(guaranteed_reserve);282 return collector.collect(ctx);283 }284 match &specs[idx] {285 LCompSpec::If(cond) => {286 let val = evaluate(ctx.clone(), cond)?;287 let Val::Bool(b) = val else {288 bail!(TypeMismatch(289 "if spec condition",290 vec![ValType::Bool],291 val.value_type()292 ))293 };294 if b {295 evaluate_compspecs(ctx, specs, cached_overs, idx + 1, 0, collector)?;296 }297 }298 LCompSpec::For { destruct, over, .. } => {299 let arr = if let Some(cached) = &cached_overs[idx] {300 cached.clone()301 } else {302 let arr_val = evaluate(ctx.clone(), over)?;303 let Val::Arr(arr) = arr_val else {304 bail!(InComprehensionCanOnlyIterateOverArray)305 };306 arr307 };308 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;309 for (i, item) in arr.iter().enumerate() {310 let item_val = item?;311 let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);312 let fctx = Pending::new();313 destructure::destruct(314 destruct,315 Thunk::evaluated(item_val),316 fctx.clone(),317 &mut inner_builder,318 );319 let inner_ctx = inner_builder.build().into_future(fctx);320 evaluate_compspecs(321 inner_ctx,322 specs,323 cached_overs,324 idx + 1,325 if i == 0 { inner_reserve } else { 0 },326 collector,327 )?;328 }329 }330 }331 Ok(())332}1use std::rc::Rc;23use jrsonnet_types::ValType;45use super::{6 destructure::{self, evaluate_locals, evaluate_locals_unbound},7 evaluate_field_member_static, evaluate_field_member_unbound,8};9use crate::{10 Context, ContextBuilder, ObjValue, ObjValueBuilder, Pending, Result, Thunk, Val,11 analyze::{LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp, LocalId},12 arr::ArrValue,13 bail,14 error::ErrorKind::*,15 evaluate::evaluate,16};1718trait CompCollector {19 fn reserve(&mut self, _guaranteed: usize) {}20 fn collect(&mut self, ctx: Context) -> Result<()>;21}2223struct EagerArrCollector<'a> {24 out: &'a mut Vec<Val>,25 value: &'a LExpr,26}27impl CompCollector for EagerArrCollector<'_> {28 fn reserve(&mut self, size_hint: usize) {29 self.out.reserve(size_hint);30 }31 fn collect(&mut self, ctx: Context) -> Result<()> {32 self.out.push(evaluate(ctx, self.value)?);33 Ok(())34 }35}3637struct LazyArrCollector<'a> {38 out: &'a mut Vec<Thunk<Val>>,39 value: &'a Rc<LExpr>,40}41impl CompCollector for LazyArrCollector<'_> {42 fn reserve(&mut self, size_hint: usize) {43 self.out.reserve(size_hint);44 }45 fn collect(&mut self, ctx: Context) -> Result<()> {46 let value_expr = self.value.clone();47 self.out.push(Thunk!(move || evaluate(ctx, &value_expr)));48 Ok(())49 }50}5152struct ObjCompCollectorStatic<'a> {53 builder: &'a mut ObjValueBuilder,54 locals: &'a [LBind],55 field: &'a LFieldMember,56}57impl CompCollector for ObjCompCollectorStatic<'_> {58 fn reserve(&mut self, guaranteed: usize) {59 self.builder.reserve_fields(guaranteed);60 }61 fn collect(&mut self, inner_ctx: Context) -> Result<()> {62 let value_ctx = evaluate_locals(inner_ctx.clone(), self.locals);63 evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)64 }65}6667struct ObjCompCollectorUnbound<'a> {68 builder: &'a mut ObjValueBuilder,69 locals: Rc<Vec<LBind>>,70 this_id: Option<LocalId>,71 field: &'a LFieldMember,72}73impl CompCollector for ObjCompCollectorUnbound<'_> {74 fn reserve(&mut self, guaranteed: usize) {75 self.builder.reserve_fields(guaranteed);76 }77 fn collect(&mut self, inner_ctx: Context) -> Result<()> {78 let uctx = evaluate_locals_unbound(inner_ctx.clone(), self.locals.clone(), self.this_id);79 evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)80 }81}8283pub fn evaluate_obj_comp(84 super_obj: Option<ObjValue>,85 ctx: Context,86 comp: &LObjComp,87) -> Result<Val> {88 let mut builder = ObjValueBuilder::new();89 if let Some(super_obj) = super_obj {90 builder.with_super(super_obj);91 }9293 let cached_overs = cache_overs(&ctx, &comp.compspecs)?;94 if comp.this.is_some() || comp.uses_super {95 evaluate_compspecs(96 ctx,97 &comp.compspecs,98 &cached_overs,99 0,100 0,101 &mut ObjCompCollectorUnbound {102 builder: &mut builder,103 locals: comp.locals.clone(),104 this_id: comp.this,105 field: &comp.field,106 },107 )?;108 } else {109 evaluate_compspecs(110 ctx,111 &comp.compspecs,112 &cached_overs,113 0,114 0,115 &mut ObjCompCollectorStatic {116 builder: &mut builder,117 locals: &comp.locals,118 field: &comp.field,119 },120 )?;121 }122123 Ok(Val::Obj(builder.build()))124}125126pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {127 let cached_overs = cache_overs(&ctx, &comp.compspecs)?;128129 // In eager evaluation, Context is not captured, thus updates in CoW fashion will likely to success130 'eager: {131 let mut out = Vec::new();132133 if comp.compspecs.iter().all(|c| {134 matches!(135 c,136 LCompSpec::If(_)137 | LCompSpec::For {138 destruct: LDestruct::Full(_),139 ..140 }141 )142 }) && evaluate_compspecs_eager(143 ctx.clone(),144 &comp.compspecs,145 &cached_overs,146 0,147 0,148 &mut EagerArrCollector {149 out: &mut out,150 value: &comp.value,151 },152 )153 .is_err()154 {155 break 'eager;156 }157 return Ok(Val::arr(out));158 }159160 let mut items: Vec<Thunk<Val>> = Vec::new();161 evaluate_compspecs(162 ctx,163 &comp.compspecs,164 &cached_overs,165 0,166 0,167 &mut LazyArrCollector {168 out: &mut items,169 value: &comp.value,170 },171 )?;172 Ok(Val::arr(items))173}174175fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<ArrValue>>> {176 specs177 .iter()178 .map(|spec| {179 Ok(match spec {180 LCompSpec::For {181 over,182 loop_invariant: true,183 ..184 } => {185 let val = evaluate(ctx.clone(), over)?;186 let Val::Arr(arr) = val else {187 bail!(InComprehensionCanOnlyIterateOverArray)188 };189 Some(arr)190 }191 _ => None,192 })193 })194 .collect::<Result<_>>()195}196197fn evaluate_compspecs_eager(198 ctx: Context,199 specs: &[LCompSpec],200 cached_overs: &[Option<ArrValue>],201 idx: usize,202 guaranteed_reserve: usize,203 collector: &mut dyn CompCollector,204) -> Result<()> {205 if idx >= specs.len() {206 collector.reserve(guaranteed_reserve);207 return collector.collect(ctx);208 }209 match &specs[idx] {210 LCompSpec::If(cond) => {211 let val = evaluate(ctx.clone(), cond)?;212 let Val::Bool(b) = val else {213 bail!(TypeMismatch(214 "if spec condition",215 vec![ValType::Bool],216 val.value_type()217 ))218 };219 if b {220 evaluate_compspecs_eager(ctx, specs, cached_overs, idx + 1, 0, collector)?;221 }222 }223 LCompSpec::For { destruct, over, .. } => {224 let arr = if let Some(cached) = &cached_overs[idx] {225 cached.clone()226 } else {227 let arr_val = evaluate(ctx.clone(), over)?;228 let Val::Arr(arr) = arr_val else {229 bail!(InComprehensionCanOnlyIterateOverArray)230 };231 arr232 };233 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;234 match destruct {235 LDestruct::Full(id) => {236 let id = *id;237 let mut inner_ctx = ContextBuilder::extend(ctx, 1).build();238 for (i, item) in arr.iter().enumerate() {239 // TODO: reuse one ContextBuilder for full evaluate_compspecs pipeline240 inner_ctx.cow_fill_binding(id, Thunk::evaluated(item?));241 evaluate_compspecs_eager(242 inner_ctx.clone(),243 specs,244 cached_overs,245 idx + 1,246 if i == 0 { inner_reserve } else { 0 },247 collector,248 )?;249 }250 }251 // TODO: Should not be eager? CoW won't work here252 #[cfg(feature = "exp-destruct")]253 _ => unreachable!("eager compspecs are not possible with non-full patterns"),254 }255 }256 }257 Ok(())258}259260fn evaluate_compspecs(261 ctx: Context,262 specs: &[LCompSpec],263 cached_overs: &[Option<ArrValue>],264 idx: usize,265 guaranteed_reserve: usize,266 collector: &mut dyn CompCollector,267) -> Result<()> {268 if idx >= specs.len() {269 collector.reserve(guaranteed_reserve);270 return collector.collect(ctx);271 }272 match &specs[idx] {273 LCompSpec::If(cond) => {274 let val = evaluate(ctx.clone(), cond)?;275 let Val::Bool(b) = val else {276 bail!(TypeMismatch(277 "if spec condition",278 vec![ValType::Bool],279 val.value_type()280 ))281 };282 if b {283 evaluate_compspecs(ctx, specs, cached_overs, idx + 1, 0, collector)?;284 }285 }286 LCompSpec::For { destruct, over, .. } => {287 let arr = if let Some(cached) = &cached_overs[idx] {288 cached.clone()289 } else {290 let arr_val = evaluate(ctx.clone(), over)?;291 let Val::Arr(arr) = arr_val else {292 bail!(InComprehensionCanOnlyIterateOverArray)293 };294 arr295 };296 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;297 for (i, item) in arr.iter().enumerate() {298 let item_val = item?;299 let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);300 let fctx = Pending::new();301 destructure::destruct(302 destruct,303 Thunk::evaluated(item_val),304 fctx.clone(),305 &mut inner_builder,306 );307 let inner_ctx = inner_builder.build().into_future(fctx);308 evaluate_compspecs(309 inner_ctx,310 specs,311 cached_overs,312 idx + 1,313 if i == 0 { inner_reserve } else { 0 },314 collector,315 )?;316 }317 }318 }319 Ok(())320}