git.delta.rocks / jrsonnet / refs/commits / 55128e2ebc3a

difftreelog

fix correct null-coaelse chaining

Yaroslav Bolyukin2023-08-10parent: #23d571a.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -446,108 +446,113 @@
 			|| format!("variable <{name}> access"),
 			|| ctx.binding(name.clone())?.evaluate(),
 		)?,
-		Index {
-			indexable: LocExpr(v, _),
-			index,
-			#[cfg(feature = "exp-null-coaelse")]
-			null_coaelse,
-		} if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
-			let name = evaluate(ctx.clone(), index)?;
-			let Val::Str(name) = name else {
-				throw!(ValueIndexMustBeTypeGot(
-					ValType::Obj,
-					ValType::Str,
-					name.value_type(),
-				))
-			};
-			let Some(super_obj) = ctx.super_obj() else {
-				#[cfg(feature = "exp-null-coaelse")]
-				if *null_coaelse {
-					return Ok(Val::Null);
+		Index { indexable, parts } => {
+			let mut parts = parts.iter();
+			let mut indexable = match &indexable {
+				// Cheaper to execute than creating object with overriden `this`
+				LocExpr(v, _) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
+					let part = parts.next().expect("at least part should exist");
+					let Some(super_obj) = ctx.super_obj() else {
+						#[cfg(feature = "exp-null-coaelse")]
+						if part.null_coaelse {
+							return Ok(Val::Null);
+						}
+						throw!(NoSuperFound)
+					};
+					let name = evaluate(ctx.clone(), &part.value)?;
+
+					let Val::Str(name) = name else {
+						throw!(ValueIndexMustBeTypeGot(
+							ValType::Obj,
+							ValType::Str,
+							name.value_type(),
+						))
+					};
+
+					let this = ctx
+						.this()
+						.expect("no this found, while super present, should not happen");
+					let name = name.into_flat();
+					match super_obj
+						.get_for(name.clone(), this.clone())
+						.with_description_src(&part.value, || format!("field <{name}> access"))?
+					{
+						Some(v) => v,
+						#[cfg(feature = "exp-null-coaelse")]
+						None if part.null_coaelse => return Ok(Val::Null),
+						None => {
+							let suggestions = suggest_object_fields(super_obj, name.clone());
+
+							throw!(NoSuchField(name, suggestions))
+						}
+					}
 				}
-				throw!(NoSuperFound)
+				e => evaluate(ctx.clone(), e)?,
 			};
-			let this = ctx
-				.this()
-				.expect("no this found, while super present, should not happen");
-			let key = name.into_flat();
-			match super_obj.get_for(key.clone(), this.clone())? {
-				Some(v) => v,
-				#[cfg(feature = "exp-null-coaelse")]
-				None if *null_coaelse => Val::Null,
-				None => {
-					let suggestions = suggest_object_fields(super_obj, key.clone());
 
-					throw!(NoSuchField(key, suggestions))
-				}
-			}
-		}
-		Index {
-			indexable,
-			index,
-			#[cfg(feature = "exp-null-coaelse")]
-			null_coaelse,
-		} => match (evaluate(ctx.clone(), indexable)?, evaluate(ctx, index)?) {
-			(Val::Obj(v), Val::Str(key)) => State::push(
-				CallLocation::new(loc),
-				|| format!("field <{key}> access"),
-				|| match v.get(key.clone().into_flat()) {
-					Ok(Some(v)) => Ok(v),
-					#[cfg(feature = "exp-null-coaelse")]
-					Ok(None) if *null_coaelse => Ok(Val::Null),
-					Ok(None) => {
-						let suggestions = suggest_object_fields(&v, key.clone().into_flat());
+			for part in parts {
+				indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {
+					(Val::Obj(v), Val::Str(key)) => match v
+						.get(key.clone().into_flat())
+						.with_description_src(&part.value, || format!("field <{key}> access"))?
+					{
+						Some(v) => v,
+						#[cfg(feature = "exp-null-coaelse")]
+						None if part.null_coaelse => return Ok(Val::Null),
+						None => {
+							let suggestions = suggest_object_fields(&v, key.clone().into_flat());
 
-						throw!(NoSuchField(key.clone().into_flat(), suggestions))
+							throw!(NoSuchField(key.clone().into_flat(), suggestions))
+						}
+					},
+					(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(
+						ValType::Obj,
+						ValType::Str,
+						n.value_type(),
+					)),
+					(Val::Arr(v), Val::Num(n)) => {
+						if n.fract() > f64::EPSILON {
+							throw!(FractionalIndex)
+						}
+						v.get(n as usize)?
+							.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?
 					}
-					Err(e) => Err(e),
-				},
-			)?,
-			(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(
-				ValType::Obj,
-				ValType::Str,
-				n.value_type(),
-			)),
+					(Val::Arr(_), Val::Str(n)) => {
+						throw!(AttemptedIndexAnArrayWithString(n.into_flat()))
+					}
+					(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(
+						ValType::Arr,
+						ValType::Num,
+						n.value_type(),
+					)),
 
-			(Val::Arr(v), Val::Num(n)) => {
-				if n.fract() > f64::EPSILON {
-					throw!(FractionalIndex)
-				}
-				v.get(n as usize)?
-					.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?
+					(Val::Str(s), Val::Num(n)) => Val::Str({
+						let v: IStr = s
+							.clone()
+							.into_flat()
+							.chars()
+							.skip(n as usize)
+							.take(1)
+							.collect::<String>()
+							.into();
+						if v.is_empty() {
+							let size = s.into_flat().chars().count();
+							throw!(StringBoundsError(n as usize, size))
+						}
+						StrValue::Flat(v)
+					}),
+					(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(
+						ValType::Str,
+						ValType::Num,
+						n.value_type(),
+					)),
+					#[cfg(feature = "exp-null-coaelse")]
+					(Val::Null, _) if part.null_coaelse => return Ok(Val::Null),
+					(v, _) => throw!(CantIndexInto(v.value_type())),
+				};
 			}
-			(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n.into_flat())),
-			(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(
-				ValType::Arr,
-				ValType::Num,
-				n.value_type(),
-			)),
-
-			(Val::Str(s), Val::Num(n)) => Val::Str({
-				let v: IStr = s
-					.clone()
-					.into_flat()
-					.chars()
-					.skip(n as usize)
-					.take(1)
-					.collect::<String>()
-					.into();
-				if v.is_empty() {
-					let size = s.into_flat().chars().count();
-					throw!(StringBoundsError(n as usize, size))
-				}
-				StrValue::Flat(v)
-			}),
-			(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(
-				ValType::Str,
-				ValType::Num,
-				n.value_type(),
-			)),
-			#[cfg(feature = "exp-null-coaelse")]
-			(Val::Null, _) if *null_coaelse => Val::Null,
-
-			(v, _) => throw!(CantIndexInto(v.value_type())),
-		},
+			indexable
+		}
 		LocalExpr(bindings, returned) => {
 			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =
 				GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -406,9 +406,7 @@
 	/// a[b], a.b, a?.b
 	Index {
 		indexable: LocExpr,
-		index: LocExpr,
-		#[cfg(feature = "exp-null-coaelse")]
-		null_coaelse: bool,
+		parts: Vec<IndexPart>,
 	},
 	/// function(x) x
 	Function(ParamsDesc, LocExpr),
@@ -421,6 +419,15 @@
 	Slice(LocExpr, SliceDesc),
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[derive(Debug, PartialEq, Trace)]
+pub struct IndexPart {
+	pub value: LocExpr,
+	#[cfg(feature = "exp-null-coaelse")]
+	pub null_coaelse: bool,
+}
+
 /// file, begin offset, end offset
 #[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
337 unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}337 unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}
338 --338 --
339 a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}339 a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}
340 indexable:(@) _ null_coaelse:("?" _ ensure_null_coaelse())? "." _ index:id_loc(s) {Expr::Index{340 indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable, parts}}
341 indexable, index,
342 #[cfg(feature = "exp-null-coaelse")]
343 null_coaelse: null_coaelse.is_some(),
344 }}
345 indexable:(@) _ null_coaelse:("?" _ "." _ ensure_null_coaelse())? "[" _ index:expr(s) _ "]" {Expr::Index{
346 indexable, index,
347 #[cfg(feature = "exp-null-coaelse")]
348 null_coaelse: null_coaelse.is_some(),
349 }}
350 a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}341 a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}
351 a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}342 a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}
352 --343 --
353 e:expr_basic(s) {e}344 e:expr_basic(s) {e}
354 "(" _ e:expr(s) _ ")" {Expr::Parened(e)}345 "(" _ e:expr(s) _ ")" {Expr::Parened(e)}
355 }346 }
347 pub rule index_part(s: &ParserSettings) -> IndexPart
348 = n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart {
349 value,
350 #[cfg(feature = "exp-null-coaelse")]
351 null_coaelse: n.is_some(),
352 }}
353 / n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart {
354 value,
355 #[cfg(feature = "exp-null-coaelse")]
356 null_coaelse: n.is_some(),
357 }}
356358
357 pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}359 pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}
358 }360 }