difftreelog
perf specialize super.field gets
in: master
4 files changed
cmds/jrsonnet/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -139,7 +139,7 @@
}
fn main_real(s: &State, opts: Opts) -> Result<(), Error> {
- let (_stack_guard, tla, _gc_guard) = opts.general.configure(s)?;
+ let (tla, _gc_guard) = opts.general.configure(s)?;
let manifest_format = opts.manifest.configure(s)?;
let input = opts.input.input.ok_or(Error::MissingInputArgument)?;
crates/jrsonnet-cli/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/lib.rs
+++ b/crates/jrsonnet-cli/src/lib.rs
@@ -6,9 +6,7 @@
use std::{env, marker::PhantomData, path::PathBuf};
use clap::Parser;
-use jrsonnet_evaluator::{
- error::Result, stack::StackDepthLimitOverrideGuard, FileImportResolver, State,
-};
+use jrsonnet_evaluator::{error::Result, stack::set_stack_depth_limit, FileImportResolver, State};
use jrsonnet_gcmodule::with_thread_object_space;
pub use manifest::*;
pub use stdlib::*;
@@ -48,7 +46,7 @@
jpath: Vec<PathBuf>,
}
impl ConfigureState for MiscOpts {
- type Guards = StackDepthLimitOverrideGuard;
+ type Guards = ();
fn configure(&self, s: &State) -> Result<Self::Guards> {
let mut library_paths = self.jpath.clone();
library_paths.reverse();
@@ -58,8 +56,8 @@
s.set_import_resolver(Box::new(FileImportResolver::new(library_paths)));
- let _depth_limit = jrsonnet_evaluator::stack::limit_stack_depth(self.max_stack);
- Ok(_depth_limit)
+ set_stack_depth_limit(self.max_stack);
+ Ok(())
}
}
@@ -81,17 +79,16 @@
impl ConfigureState for GeneralOpts {
type Guards = (
- <MiscOpts as ConfigureState>::Guards,
<TlaOpts as ConfigureState>::Guards,
<GcOpts as ConfigureState>::Guards,
);
fn configure(&self, s: &State) -> Result<Self::Guards> {
// Configure trace first, because tla-code/ext-code can throw
- let misc_guards = self.misc.configure(s)?;
+ self.misc.configure(s)?;
let tla_guards = self.tla.configure(s)?;
self.std.configure(s)?;
let gc_guards = self.gc.configure(s)?;
- Ok((misc_guards, tla_guards, gc_guards))
+ Ok((tla_guards, gc_guards))
}
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::{cmp::Ordering, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, FieldName, ForSpecData,7 IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,8};9use jrsonnet_types::ValType;1011use self::destructure::destruct;12use crate::{13 destructure::evaluate_dest,14 error::ErrorKind::*,15 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},16 function::{CallLocation, FuncDesc, FuncVal},17 tb, throw,18 typed::Typed,19 val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},20 Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,21 Unbound, Val,22};23pub mod destructure;24pub mod operator;2526pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {27 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {28 name,29 ctx,30 params,31 body,32 })))33}3435pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {36 Ok(match field_name {37 FieldName::Fixed(n) => Some(n.clone()),38 FieldName::Dyn(expr) => State::push(39 CallLocation::new(&expr.1),40 || "evaluating field name".to_string(),41 || {42 let value = evaluate(ctx, expr)?;43 if matches!(value, Val::Null) {44 Ok(None)45 } else {46 Ok(Some(IStr::from_untyped(value)?))47 }48 },49 )?,50 })51}5253pub fn evaluate_comp(54 ctx: Context,55 specs: &[CompSpec],56 callback: &mut impl FnMut(Context) -> Result<()>,57) -> Result<()> {58 match specs.get(0) {59 None => callback(ctx)?,60 Some(CompSpec::IfSpec(IfSpecData(cond))) => {61 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {62 evaluate_comp(ctx, &specs[1..], callback)?;63 }64 }65 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {66 Val::Arr(list) => {67 for item in list.iter_lazy() {68 let fctx = Pending::new();69 let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());70 destruct(var, item, fctx.clone(), &mut new_bindings)?;71 let ctx = ctx72 .clone()73 .extend(new_bindings, None, None, None)74 .into_future(fctx);7576 evaluate_comp(ctx, &specs[1..], callback)?;77 }78 }79 #[cfg(feature = "exp-object-iteration")]80 Val::Obj(obj) => {81 for field in obj.fields(82 // TODO: Should there be ability to preserve iteration order?83 #[cfg(feature = "exp-preserve-order")]84 false,85 ) {86 #[derive(Trace)]87 struct ObjectFieldThunk {88 obj: ObjValue,89 field: IStr,90 }91 impl ThunkValue for ObjectFieldThunk {92 type Output = Val;9394 fn get(self: Box<Self>) -> Result<Self::Output> {95 self.obj.get(self.field).transpose().expect(96 "field exists, as field name was obtained from object.fields()",97 )98 }99 }100101 let fctx = Pending::new();102 let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());103 let value = Thunk::evaluated(Val::Arr(ArrValue::Lazy(Cc::new(vec![104 Thunk::evaluated(Val::Str(field.clone())),105 Thunk::new(tb!(ObjectFieldThunk {106 field: field.clone(),107 obj: obj.clone(),108 })),109 ]))));110 destruct(var, value, fctx.clone(), &mut new_bindings)?;111 let ctx = ctx112 .clone()113 .extend(new_bindings, None, None, None)114 .into_future(fctx);115116 evaluate_comp(ctx, &specs[1..], callback)?;117 }118 }119 _ => throw!(InComprehensionCanOnlyIterateOverArray),120 },121 }122 Ok(())123}124125trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}126impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}127128fn evaluate_object_locals(129 fctx: Pending<Context>,130 locals: Rc<Vec<BindSpec>>,131) -> impl CloneableUnbound<Context> {132 #[derive(Trace, Clone)]133 struct UnboundLocals {134 fctx: Pending<Context>,135 locals: Rc<Vec<BindSpec>>,136 }137 impl Unbound for UnboundLocals {138 type Bound = Context;139140 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {141 let fctx = Context::new_future();142 let mut new_bindings =143 GcHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());144 for b in self.locals.iter() {145 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;146 }147148 let ctx = self.fctx.unwrap();149 let new_dollar = ctx.dollar().clone().or_else(|| this.clone());150151 let ctx = ctx152 .extend(new_bindings, new_dollar, sup, this)153 .into_future(fctx);154155 Ok(ctx)156 }157 }158159 UnboundLocals { fctx, locals }160}161162pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(163 builder: &mut ObjValueBuilder,164 ctx: Context,165 uctx: B,166 field: &FieldMember,167) -> Result<()> {168 let name = evaluate_field_name(ctx, &field.name)?;169 let Some(name) = name else {170 return Ok(());171 };172173 match field {174 FieldMember {175 plus,176 params: None,177 visibility,178 value,179 ..180 } => {181 #[derive(Trace)]182 struct UnboundValue<B: Trace> {183 uctx: B,184 value: LocExpr,185 name: IStr,186 }187 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {188 type Bound = Val;189 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {190 evaluate_named(self.uctx.bind(sup, this)?, &self.value, self.name.clone())191 }192 }193194 builder195 .member(name.clone())196 .with_add(*plus)197 .with_visibility(*visibility)198 .with_location(value.1.clone())199 .bindable(tb!(UnboundValue {200 uctx,201 value: value.clone(),202 name,203 }))?;204 }205 FieldMember {206 params: Some(params),207 visibility,208 value,209 ..210 } => {211 #[derive(Trace)]212 struct UnboundMethod<B: Trace> {213 uctx: B,214 value: LocExpr,215 params: ParamsDesc,216 name: IStr,217 }218 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {219 type Bound = Val;220 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {221 Ok(evaluate_method(222 self.uctx.bind(sup, this)?,223 self.name.clone(),224 self.params.clone(),225 self.value.clone(),226 ))227 }228 }229230 builder231 .member(name.clone())232 .with_visibility(*visibility)233 .with_location(value.1.clone())234 .bindable(tb!(UnboundMethod {235 uctx,236 value: value.clone(),237 params: params.clone(),238 name,239 }))?;240 }241 }242 Ok(())243}244245#[allow(clippy::too_many_lines)]246pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {247 let mut builder = ObjValueBuilder::new();248 let locals = Rc::new(249 members250 .iter()251 .filter_map(|m| match m {252 Member::BindStmt(bind) => Some(bind.clone()),253 _ => None,254 })255 .collect::<Vec<_>>(),256 );257258 let fctx = Context::new_future();259260 // We have single context for all fields, so we can cache binds261 let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));262263 for member in members.iter() {264 match member {265 Member::Field(field) => {266 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;267 }268 Member::AssertStmt(stmt) => {269 #[derive(Trace)]270 struct ObjectAssert<B: Trace> {271 uctx: B,272 assert: AssertStmt,273 }274 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {275 fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {276 let ctx = self.uctx.bind(sup, this)?;277 evaluate_assert(ctx, &self.assert)278 }279 }280 builder.assert(tb!(ObjectAssert {281 uctx: uctx.clone(),282 assert: stmt.clone(),283 }));284 }285 Member::BindStmt(_) => {286 // Already handled287 }288 }289 }290 let this = builder.build();291 fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));292 Ok(this)293}294295pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {296 Ok(match object {297 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,298 ObjBody::ObjComp(obj) => {299 let mut builder = ObjValueBuilder::new();300 let locals = Rc::new(301 obj.pre_locals302 .iter()303 .chain(obj.post_locals.iter())304 .cloned()305 .collect::<Vec<_>>(),306 );307 let mut ctxs = vec![];308 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {309 let fctx = Context::new_future();310 ctxs.push((ctx.clone(), fctx.clone()));311 let uctx = evaluate_object_locals(fctx, locals.clone());312313 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)314 })?;315316 let this = builder.build();317 for (ctx, fctx) in ctxs {318 let _ctx = ctx319 .extend(GcHashMap::new(), None, None, Some(this.clone()))320 .into_future(fctx);321 }322 this323 }324 })325}326327pub fn evaluate_apply(328 ctx: Context,329 value: &LocExpr,330 args: &ArgsDesc,331 loc: CallLocation<'_>,332 tailstrict: bool,333) -> Result<Val> {334 let value = evaluate(ctx.clone(), value)?;335 Ok(match value {336 Val::Func(f) => {337 let body = || f.evaluate(ctx, loc, args, tailstrict);338 if tailstrict {339 body()?340 } else {341 State::push(loc, || format!("function <{}> call", f.name()), body)?342 }343 }344 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),345 })346}347348pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {349 let value = &assertion.0;350 let msg = &assertion.1;351 let assertion_result = State::push(352 CallLocation::new(&value.1),353 || "assertion condition".to_owned(),354 || bool::from_untyped(evaluate(ctx.clone(), value)?),355 )?;356 if !assertion_result {357 State::push(358 CallLocation::new(&value.1),359 || "assertion failure".to_owned(),360 || {361 if let Some(msg) = msg {362 throw!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));363 }364 throw!(AssertionFailed(Val::Null.to_string()?));365 },366 )?;367 }368 Ok(())369}370371pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {372 use Expr::*;373 let LocExpr(raw_expr, _loc) = expr;374 Ok(match &**raw_expr {375 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),376 _ => evaluate(ctx, expr)?,377 })378}379380#[allow(clippy::too_many_lines)]381pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {382 use Expr::*;383 let LocExpr(expr, loc) = expr;384 Ok(match &**expr {385 Literal(LiteralType::This) => {386 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)387 }388 Literal(LiteralType::Super) => Val::Obj(389 ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(390 ctx.this()391 .clone()392 .expect("if super exists - then this should to"),393 ),394 ),395 Literal(LiteralType::Dollar) => {396 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)397 }398 Literal(LiteralType::True) => Val::Bool(true),399 Literal(LiteralType::False) => Val::Bool(false),400 Literal(LiteralType::Null) => Val::Null,401 Parened(e) => evaluate(ctx, e)?,402 Str(v) => Val::Str(v.clone()),403 Num(v) => Val::new_checked_num(*v)?,404 BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,405 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,406 Var(name) => State::push(407 CallLocation::new(loc),408 || format!("variable <{name}> access"),409 || ctx.binding(name.clone())?.evaluate(),410 )?,411 Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {412 (Val::Obj(v), Val::Str(key)) => State::push(413 CallLocation::new(loc),414 || format!("field <{key}> access"),415 || match v.get(key.clone()) {416 Ok(Some(v)) => Ok(v),417 #[cfg(not(feature = "friendly-errors"))]418 Ok(None) => throw!(NoSuchField(key.clone(), vec![])),419 #[cfg(feature = "friendly-errors")]420 Ok(None) => {421 let mut heap = Vec::new();422 for field in v.fields_ex(423 true,424 #[cfg(feature = "exp-preserve-order")]425 false,426 ) {427 let conf = strsim::jaro_winkler(&field as &str, &key as &str);428 if conf < 0.8 {429 continue;430 }431 heap.push((conf, field));432 }433 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));434435 throw!(NoSuchField(436 key.clone(),437 heap.into_iter().map(|(_, v)| v).collect()438 ))439 }440 Err(e) => Err(e),441 },442 )?,443 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(444 ValType::Obj,445 ValType::Str,446 n.value_type(),447 )),448449 (Val::Arr(v), Val::Num(n)) => {450 if n.fract() > f64::EPSILON {451 throw!(FractionalIndex)452 }453 v.get(n as usize)?454 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?455 }456 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),457 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(458 ValType::Arr,459 ValType::Num,460 n.value_type(),461 )),462463 (Val::Str(s), Val::Num(n)) => Val::Str({464 let v: IStr = s465 .chars()466 .skip(n as usize)467 .take(1)468 .collect::<String>()469 .into();470 if v.is_empty() {471 let size = s.chars().count();472 throw!(StringBoundsError(n as usize, size))473 }474 v475 }),476 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(477 ValType::Str,478 ValType::Num,479 n.value_type(),480 )),481482 (v, _) => throw!(CantIndexInto(v.value_type())),483 },484 LocalExpr(bindings, returned) => {485 let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =486 GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());487 let fctx = Context::new_future();488 for b in bindings {489 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;490 }491 let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);492 evaluate(ctx, &returned.clone())?493 }494 Arr(items) => {495 let mut out = Vec::with_capacity(items.len());496 for item in items {497 // TODO: Implement ArrValue::Lazy with same context for every element?498 #[derive(Trace)]499 struct ArrayElement {500 ctx: Context,501 item: LocExpr,502 }503 impl ThunkValue for ArrayElement {504 type Output = Val;505 fn get(self: Box<Self>) -> Result<Val> {506 evaluate(self.ctx, &self.item)507 }508 }509 out.push(Thunk::new(tb!(ArrayElement {510 ctx: ctx.clone(),511 item: item.clone(),512 })));513 }514 Val::Arr(out.into())515 }516 ArrComp(expr, comp_specs) => {517 let mut out = Vec::new();518 evaluate_comp(ctx, comp_specs, &mut |ctx| {519 out.push(evaluate(ctx, expr)?);520 Ok(())521 })?;522 Val::Arr(ArrValue::Eager(Cc::new(out)))523 }524 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),525 ObjExtend(a, b) => evaluate_add_op(526 &evaluate(ctx.clone(), a)?,527 &Val::Obj(evaluate_object(ctx, b)?),528 )?,529 Apply(value, args, tailstrict) => {530 evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)?531 }532 Function(params, body) => {533 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())534 }535 AssertExpr(assert, returned) => {536 evaluate_assert(ctx.clone(), assert)?;537 evaluate(ctx, returned)?538 }539 ErrorStmt(e) => State::push(540 CallLocation::new(loc),541 || "error statement".to_owned(),542 || throw!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),543 )?,544 IfElse {545 cond,546 cond_then,547 cond_else,548 } => {549 if State::push(550 CallLocation::new(loc),551 || "if condition".to_owned(),552 || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),553 )? {554 evaluate(ctx, cond_then)?555 } else {556 match cond_else {557 Some(v) => evaluate(ctx, v)?,558 None => Val::Null,559 }560 }561 }562 Slice(value, desc) => {563 fn parse_idx<T: Typed>(564 loc: CallLocation<'_>,565 ctx: &Context,566 expr: &Option<LocExpr>,567 desc: &'static str,568 ) -> Result<Option<T>> {569 if let Some(value) = expr {570 Ok(Some(State::push(571 loc,572 || format!("slice {desc}"),573 || T::from_untyped(evaluate(ctx.clone(), value)?),574 )?))575 } else {576 Ok(None)577 }578 }579580 let indexable = evaluate(ctx.clone(), value)?;581 let loc = CallLocation::new(loc);582583 let start = parse_idx(loc, &ctx, &desc.start, "start")?;584 let end = parse_idx(loc, &ctx, &desc.end, "end")?;585 let step = parse_idx(loc, &ctx, &desc.step, "step")?;586587 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?588 }589 i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {590 let Expr::Str(path) = &*path.0 else {591 throw!("computed imports are not supported")592 };593 let tmp = loc.clone().0;594 let s = ctx.state();595 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;596 match i {597 Import(_) => State::push(598 CallLocation::new(loc),599 || format!("import {:?}", path.clone()),600 || s.import_resolved(resolved_path),601 )?,602 ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),603 ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),604 _ => unreachable!(),605 }606 }607 })608}crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -128,7 +128,7 @@
assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
assertions_ran: RefCell<GcHashSet<ObjValue>>,
this_entries: Cc<GcHashMap<IStr, ObjMember>>,
- value_cache: RefCell<GcHashMap<IStr, CacheValue>>,
+ value_cache: RefCell<GcHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,
}
#[derive(Clone, Trace)]
@@ -387,7 +387,8 @@
pub fn get(&self, key: IStr) -> Result<Option<Val>> {
self.run_assertions()?;
- if let Some(v) = self.0.value_cache.borrow().get(&key) {
+ let cache_key = (key.clone(), None);
+ if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
return Ok(match v {
CacheValue::Cached(v) => Some(v.clone()),
CacheValue::NotFound => None,
@@ -398,21 +399,48 @@
self.0
.value_cache
.borrow_mut()
- .insert(key.clone(), CacheValue::Pending);
+ .insert(cache_key.clone(), CacheValue::Pending);
let value = self
- .get_raw(
- key.clone(),
- self.0.this.clone().unwrap_or_else(|| self.clone()),
- )
+ .get_raw(key, self.0.this.clone().unwrap_or_else(|| self.clone()))
.map_err(|e| {
self.0
.value_cache
.borrow_mut()
- .insert(key.clone(), CacheValue::Errored(e.clone()));
+ .insert(cache_key.clone(), CacheValue::Errored(e.clone()));
e
})?;
self.0.value_cache.borrow_mut().insert(
- key,
+ cache_key,
+ value
+ .as_ref()
+ .map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())),
+ );
+ Ok(value)
+ }
+ pub fn get_for(&self, key: IStr, this: Self) -> Result<Option<Val>> {
+ self.run_assertions()?;
+ let cache_key = (key.clone(), Some(this.clone().downgrade()));
+ if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
+ return Ok(match v {
+ CacheValue::Cached(v) => Some(v.clone()),
+ CacheValue::NotFound => None,
+ CacheValue::Pending => throw!(InfiniteRecursionDetected),
+ CacheValue::Errored(e) => return Err(e.clone()),
+ });
+ }
+ self.0
+ .value_cache
+ .borrow_mut()
+ .insert(cache_key.clone(), CacheValue::Pending);
+ let value = self.get_raw(key, this).map_err(|e| {
+ self.0
+ .value_cache
+ .borrow_mut()
+ .insert(cache_key.clone(), CacheValue::Errored(e.clone()));
+ e
+ })?;
+ self.0.value_cache.borrow_mut().insert(
+ cache_key,
value
.as_ref()
.map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())),