git.delta.rocks / jrsonnet / refs/commits / 7b01ecbd8fbc

difftreelog

fix preserve visibility for object method members

Yaroslav Bolyukin2022-11-09parent: #f20aa24.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
after · 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::Error::*,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.clone(), &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					Ok(evaluate_named(191						self.uctx.bind(sup, this)?,192						&self.value,193						self.name.clone(),194					)?)195				}196			}197198			builder199				.member(name.clone())200				.with_add(*plus)201				.with_visibility(*visibility)202				.with_location(value.1.clone())203				.bindable(tb!(UnboundValue {204					uctx: uctx.clone(),205					value: value.clone(),206					name: name.clone()207				}))?;208		}209		FieldMember {210			params: Some(params),211			visibility,212			value,213			..214		} => {215			#[derive(Trace)]216			struct UnboundMethod<B: Trace> {217				uctx: B,218				value: LocExpr,219				params: ParamsDesc,220				name: IStr,221			}222			impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {223				type Bound = Val;224				fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {225					Ok(evaluate_method(226						self.uctx.bind(sup, this)?,227						self.name.clone(),228						self.params.clone(),229						self.value.clone(),230					))231				}232			}233234			builder235				.member(name.clone())236				.with_visibility(*visibility)237				.with_location(value.1.clone())238				.bindable(tb!(UnboundMethod {239					uctx: uctx.clone(),240					value: value.clone(),241					params: params.clone(),242					name: name.clone()243				}))?;244		}245	}246	Ok(())247}248249#[allow(clippy::too_many_lines)]250pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {251	let mut builder = ObjValueBuilder::new();252	let locals = Rc::new(253		members254			.iter()255			.filter_map(|m| match m {256				Member::BindStmt(bind) => Some(bind.clone()),257				_ => None,258			})259			.collect::<Vec<_>>(),260	);261262	let fctx = Context::new_future();263264	// We have single context for all fields, so we can cache binds265	let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));266267	for member in members.iter() {268		match member {269			Member::Field(field) => {270				evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?271			}272			Member::AssertStmt(stmt) => {273				#[derive(Trace)]274				struct ObjectAssert<B: Trace> {275					uctx: B,276					assert: AssertStmt,277				}278				impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {279					fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {280						let ctx = self.uctx.bind(sup, this)?;281						evaluate_assert(ctx, &self.assert)282					}283				}284				builder.assert(tb!(ObjectAssert {285					uctx: uctx.clone(),286					assert: stmt.clone(),287				}));288			}289			Member::BindStmt(_) => {290				// Already handled291			}292		}293	}294	let this = builder.build();295	fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));296	Ok(this)297}298299pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {300	Ok(match object {301		ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,302		ObjBody::ObjComp(obj) => {303			let mut builder = ObjValueBuilder::new();304			let locals = Rc::new(305				obj.pre_locals306					.iter()307					.chain(obj.post_locals.iter())308					.cloned()309					.collect::<Vec<_>>(),310			);311			let mut ctxs = vec![];312			evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {313				let fctx = Context::new_future();314				ctxs.push((ctx.clone(), fctx.clone()));315				let uctx = evaluate_object_locals(fctx, locals.clone());316317				evaluate_field_member(&mut builder, ctx, uctx, &obj.field)318			})?;319320			let this = builder.build();321			for (ctx, fctx) in ctxs {322				let _ctx = ctx323					.extend(GcHashMap::new(), None, None, Some(this.clone()))324					.into_future(fctx);325			}326			this327		}328	})329}330331pub fn evaluate_apply(332	ctx: Context,333	value: &LocExpr,334	args: &ArgsDesc,335	loc: CallLocation<'_>,336	tailstrict: bool,337) -> Result<Val> {338	let value = evaluate(ctx.clone(), value)?;339	Ok(match value {340		Val::Func(f) => {341			let body = || f.evaluate(ctx, loc, args, tailstrict);342			if tailstrict {343				body()?344			} else {345				State::push(loc, || format!("function <{}> call", f.name()), body)?346			}347		}348		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),349	})350}351352pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {353	let value = &assertion.0;354	let msg = &assertion.1;355	let assertion_result = State::push(356		CallLocation::new(&value.1),357		|| "assertion condition".to_owned(),358		|| bool::from_untyped(evaluate(ctx.clone(), value)?),359	)?;360	if !assertion_result {361		State::push(362			CallLocation::new(&value.1),363			|| "assertion failure".to_owned(),364			|| {365				if let Some(msg) = msg {366					throw!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));367				}368				throw!(AssertionFailed(Val::Null.to_string()?));369			},370		)?;371	}372	Ok(())373}374375pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {376	use Expr::*;377	let LocExpr(raw_expr, _loc) = expr;378	Ok(match &**raw_expr {379		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),380		_ => evaluate(ctx, expr)?,381	})382}383384#[allow(clippy::too_many_lines)]385pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {386	use Expr::*;387	let LocExpr(expr, loc) = expr;388	Ok(match &**expr {389		Literal(LiteralType::This) => {390			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)391		}392		Literal(LiteralType::Super) => Val::Obj(393			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(394				ctx.this()395					.clone()396					.expect("if super exists - then this should to"),397			),398		),399		Literal(LiteralType::Dollar) => {400			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)401		}402		Literal(LiteralType::True) => Val::Bool(true),403		Literal(LiteralType::False) => Val::Bool(false),404		Literal(LiteralType::Null) => Val::Null,405		Parened(e) => evaluate(ctx, e)?,406		Str(v) => Val::Str(v.clone()),407		Num(v) => Val::new_checked_num(*v)?,408		BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,409		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,410		Var(name) => State::push(411			CallLocation::new(loc),412			|| format!("variable <{name}> access"),413			|| ctx.binding(name.clone())?.evaluate(),414		)?,415		Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {416			(Val::Obj(v), Val::Str(key)) => State::push(417				CallLocation::new(loc),418				|| format!("field <{key}> access"),419				|| match v.get(key.clone()) {420					Ok(Some(v)) => Ok(v),421					#[cfg(not(feature = "friendly-errors"))]422					Ok(None) => throw!(NoSuchField(key.clone(), vec![])),423					#[cfg(feature = "friendly-errors")]424					Ok(None) => {425						let mut heap = Vec::new();426						for field in v.fields_ex(427							true,428							#[cfg(feature = "exp-preserve-order")]429							false,430						) {431							let conf = strsim::jaro_winkler(&field as &str, &key as &str);432							if conf < 0.8 {433								continue;434							}435							heap.push((conf, field));436						}437						heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));438439						throw!(NoSuchField(440							key.clone(),441							heap.into_iter().map(|(_, v)| v).collect()442						))443					}444					Err(e) => Err(e),445				},446			)?,447			(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(448				ValType::Obj,449				ValType::Str,450				n.value_type(),451			)),452453			(Val::Arr(v), Val::Num(n)) => {454				if n.fract() > f64::EPSILON {455					throw!(FractionalIndex)456				}457				v.get(n as usize)?458					.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?459			}460			(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),461			(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(462				ValType::Arr,463				ValType::Num,464				n.value_type(),465			)),466467			(Val::Str(s), Val::Num(n)) => Val::Str({468				let v: IStr = s469					.chars()470					.skip(n as usize)471					.take(1)472					.collect::<String>()473					.into();474				if v.is_empty() {475					let size = s.chars().count();476					throw!(StringBoundsError(n as usize, size))477				}478				v479			}),480			(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(481				ValType::Str,482				ValType::Num,483				n.value_type(),484			)),485486			(v, _) => throw!(CantIndexInto(v.value_type())),487		},488		LocalExpr(bindings, returned) => {489			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =490				GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());491			let fctx = Context::new_future();492			for b in bindings {493				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;494			}495			let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);496			evaluate(ctx, &returned.clone())?497		}498		Arr(items) => {499			let mut out = Vec::with_capacity(items.len());500			for item in items {501				// TODO: Implement ArrValue::Lazy with same context for every element?502				#[derive(Trace)]503				struct ArrayElement {504					ctx: Context,505					item: LocExpr,506				}507				impl ThunkValue for ArrayElement {508					type Output = Val;509					fn get(self: Box<Self>) -> Result<Val> {510						evaluate(self.ctx, &self.item)511					}512				}513				out.push(Thunk::new(tb!(ArrayElement {514					ctx: ctx.clone(),515					item: item.clone(),516				})));517			}518			Val::Arr(out.into())519		}520		ArrComp(expr, comp_specs) => {521			let mut out = Vec::new();522			evaluate_comp(ctx, comp_specs, &mut |ctx| {523				out.push(evaluate(ctx, expr)?);524				Ok(())525			})?;526			Val::Arr(ArrValue::Eager(Cc::new(out)))527		}528		Obj(body) => Val::Obj(evaluate_object(ctx, body)?),529		ObjExtend(a, b) => evaluate_add_op(530			&evaluate(ctx.clone(), a)?,531			&Val::Obj(evaluate_object(ctx, b)?),532		)?,533		Apply(value, args, tailstrict) => {534			evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)?535		}536		Function(params, body) => {537			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())538		}539		AssertExpr(assert, returned) => {540			evaluate_assert(ctx.clone(), assert)?;541			evaluate(ctx, returned)?542		}543		ErrorStmt(e) => State::push(544			CallLocation::new(loc),545			|| "error statement".to_owned(),546			|| throw!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),547		)?,548		IfElse {549			cond,550			cond_then,551			cond_else,552		} => {553			if State::push(554				CallLocation::new(loc),555				|| "if condition".to_owned(),556				|| bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),557			)? {558				evaluate(ctx, cond_then)?559			} else {560				match cond_else {561					Some(v) => evaluate(ctx, v)?,562					None => Val::Null,563				}564			}565		}566		Slice(value, desc) => {567			fn parse_idx<T: Typed>(568				loc: CallLocation<'_>,569				ctx: &Context,570				expr: &Option<LocExpr>,571				desc: &'static str,572			) -> Result<Option<T>> {573				if let Some(value) = expr {574					Ok(Some(State::push(575						loc,576						|| format!("slice {desc}"),577						|| T::from_untyped(evaluate(ctx.clone(), value)?),578					)?))579				} else {580					Ok(None)581				}582			}583584			let indexable = evaluate(ctx.clone(), value)?;585			let loc = CallLocation::new(loc);586587			let start = parse_idx(loc, &ctx, &desc.start, "start")?;588			let end = parse_idx(loc, &ctx, &desc.end, "end")?;589			let step = parse_idx(loc, &ctx, &desc.step, "step")?;590591			IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?592		}593		i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {594			let tmp = loc.clone().0;595			let s = ctx.state();596			let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;597			match i {598				Import(_) => State::push(599					CallLocation::new(loc),600					|| format!("import {:?}", path.clone()),601					|| s.import_resolved(resolved_path),602				)?,603				ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),604				ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),605				_ => unreachable!(),606			}607		}608	})609}