git.delta.rocks / jrsonnet / refs/commits / 64f3674f18c4

difftreelog

perf specialize super.field gets

Yaroslav Bolyukin2022-11-20parent: #30b370e.patch.diff
in: master

4 files changed

modifiedcmds/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)?;
modifiedcrates/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))
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate/mod.rs
1use 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}
modifiedcrates/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())),