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
before · crates/jrsonnet-parser/src/expr.rs
1use std::{2	fmt::{self, Debug, Display},3	ops::Deref,4	rc::Rc,5};67use jrsonnet_gcmodule::Trace;8use jrsonnet_interner::IStr;9#[cfg(feature = "serde")]10use serde::{Deserialize, Serialize};11#[cfg(feature = "structdump")]12use structdump::Codegen;1314use crate::source::Source;1516#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]17#[cfg_attr(feature = "structdump", derive(Codegen))]18#[derive(Debug, PartialEq, Trace)]19pub enum FieldName {20	/// {fixed: 2}21	Fixed(IStr),22	/// {["dyn"+"amic"]: 3}23	Dyn(LocExpr),24}2526#[cfg_attr(feature = "structdump", derive(Codegen))]27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]28#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]29pub enum Visibility {30	/// :31	Normal,32	/// ::33	Hidden,34	/// :::35	Unhide,36}3738impl Visibility {39	pub fn is_visible(&self) -> bool {40		matches!(self, Self::Normal | Self::Unhide)41	}42}4344#[cfg_attr(feature = "structdump", derive(Codegen))]45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]46#[derive(Clone, Debug, PartialEq, Trace)]47pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);4849#[cfg_attr(feature = "structdump", derive(Codegen))]50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]51#[derive(Debug, PartialEq, Trace)]52pub struct FieldMember {53	pub name: FieldName,54	pub plus: bool,55	pub params: Option<ParamsDesc>,56	pub visibility: Visibility,57	pub value: LocExpr,58}5960#[cfg_attr(feature = "structdump", derive(Codegen))]61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]62#[derive(Debug, PartialEq, Trace)]63pub enum Member {64	Field(FieldMember),65	BindStmt(BindSpec),66	AssertStmt(AssertStmt),67}6869#[cfg_attr(feature = "structdump", derive(Codegen))]70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]71#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]72pub enum UnaryOpType {73	Plus,74	Minus,75	BitNot,76	Not,77}7879impl Display for UnaryOpType {80	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {81		use UnaryOpType::*;82		write!(83			f,84			"{}",85			match self {86				Plus => "+",87				Minus => "-",88				BitNot => "~",89				Not => "!",90			}91		)92	}93}9495#[cfg_attr(feature = "structdump", derive(Codegen))]96#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]97#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]98pub enum BinaryOpType {99	Mul,100	Div,101102	/// Implemented as intrinsic, put here for completeness103	Mod,104105	Add,106	Sub,107108	Lhs,109	Rhs,110111	Lt,112	Gt,113	Lte,114	Gte,115116	BitAnd,117	BitOr,118	BitXor,119120	Eq,121	Neq,122123	And,124	Or,125	#[cfg(feature = "exp-null-coaelse")]126	NullCoaelse,127128	// Equialent to std.objectHasEx(a, b, true)129	In,130}131132impl Display for BinaryOpType {133	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134		use BinaryOpType::*;135		write!(136			f,137			"{}",138			match self {139				Mul => "*",140				Div => "/",141				Mod => "%",142				Add => "+",143				Sub => "-",144				Lhs => "<<",145				Rhs => ">>",146				Lt => "<",147				Gt => ">",148				Lte => "<=",149				Gte => ">=",150				BitAnd => "&",151				BitOr => "|",152				BitXor => "^",153				Eq => "==",154				Neq => "!=",155				And => "&&",156				Or => "||",157				In => "in",158				#[cfg(feature = "exp-null-coaelse")]159				NullCoaelse => "??",160			}161		)162	}163}164165/// name, default value166#[cfg_attr(feature = "structdump", derive(Codegen))]167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]168#[derive(Debug, PartialEq, Trace)]169pub struct Param(pub Destruct, pub Option<LocExpr>);170171/// Defined function parameters172#[cfg_attr(feature = "structdump", derive(Codegen))]173#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]174#[derive(Debug, Clone, PartialEq, Trace)]175pub struct ParamsDesc(pub Rc<Vec<Param>>);176177impl Deref for ParamsDesc {178	type Target = Vec<Param>;179	fn deref(&self) -> &Self::Target {180		&self.0181	}182}183184#[cfg_attr(feature = "structdump", derive(Codegen))]185#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]186#[derive(Debug, PartialEq, Trace)]187pub struct ArgsDesc {188	pub unnamed: Vec<LocExpr>,189	pub named: Vec<(IStr, LocExpr)>,190}191impl ArgsDesc {192	pub fn new(unnamed: Vec<LocExpr>, named: Vec<(IStr, LocExpr)>) -> Self {193		Self { unnamed, named }194	}195}196197#[cfg_attr(feature = "structdump", derive(Codegen))]198#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]199#[derive(Debug, Clone, PartialEq, Eq, Trace)]200pub enum DestructRest {201	/// ...rest202	Keep(IStr),203	/// ...204	Drop,205}206207#[cfg_attr(feature = "structdump", derive(Codegen))]208#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]209#[derive(Debug, Clone, PartialEq, Trace)]210pub enum Destruct {211	Full(IStr),212	#[cfg(feature = "exp-destruct")]213	Skip,214	#[cfg(feature = "exp-destruct")]215	Array {216		start: Vec<Destruct>,217		rest: Option<DestructRest>,218		end: Vec<Destruct>,219	},220	#[cfg(feature = "exp-destruct")]221	Object {222		fields: Vec<(IStr, Option<Destruct>, Option<LocExpr>)>,223		rest: Option<DestructRest>,224	},225}226impl Destruct {227	/// Name of destructure, used for function parameter names228	pub fn name(&self) -> Option<IStr> {229		match self {230			Self::Full(name) => Some(name.clone()),231			#[cfg(feature = "exp-destruct")]232			_ => None,233		}234	}235	pub fn capacity_hint(&self) -> usize {236		#[cfg(feature = "exp-destruct")]237		fn cap_rest(rest: &Option<DestructRest>) -> usize {238			match rest {239				Some(DestructRest::Keep(_)) => 1,240				Some(DestructRest::Drop) => 0,241				None => 0,242			}243		}244		match self {245			Self::Full(_) => 1,246			#[cfg(feature = "exp-destruct")]247			Self::Skip => 0,248			#[cfg(feature = "exp-destruct")]249			Self::Array { start, rest, end } => {250				start.iter().map(Destruct::capacity_hint).sum::<usize>()251					+ end.iter().map(Destruct::capacity_hint).sum::<usize>()252					+ cap_rest(rest)253			}254			#[cfg(feature = "exp-destruct")]255			Self::Object { fields, rest } => {256				let mut out = 0;257				for (_, into, _) in fields {258					match into {259						Some(v) => out += v.capacity_hint(),260						// Field is destructured to default name261						None => out += 1,262					}263				}264				out + cap_rest(rest)265			}266		}267	}268}269270#[cfg_attr(feature = "structdump", derive(Codegen))]271#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]272#[derive(Debug, Clone, PartialEq, Trace)]273pub enum BindSpec {274	Field {275		into: Destruct,276		value: LocExpr,277	},278	Function {279		name: IStr,280		params: ParamsDesc,281		value: LocExpr,282	},283}284impl BindSpec {285	pub fn capacity_hint(&self) -> usize {286		match self {287			BindSpec::Field { into, .. } => into.capacity_hint(),288			BindSpec::Function { .. } => 1,289		}290	}291}292293#[cfg_attr(feature = "structdump", derive(Codegen))]294#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]295#[derive(Debug, PartialEq, Trace)]296pub struct IfSpecData(pub LocExpr);297298#[cfg_attr(feature = "structdump", derive(Codegen))]299#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]300#[derive(Debug, PartialEq, Trace)]301pub struct ForSpecData(pub Destruct, pub LocExpr);302303#[cfg_attr(feature = "structdump", derive(Codegen))]304#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]305#[derive(Debug, PartialEq, Trace)]306pub enum CompSpec {307	IfSpec(IfSpecData),308	ForSpec(ForSpecData),309}310311#[cfg_attr(feature = "structdump", derive(Codegen))]312#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]313#[derive(Debug, PartialEq, Trace)]314pub struct ObjComp {315	pub pre_locals: Vec<BindSpec>,316	pub field: FieldMember,317	pub post_locals: Vec<BindSpec>,318	pub compspecs: Vec<CompSpec>,319}320321#[cfg_attr(feature = "structdump", derive(Codegen))]322#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]323#[derive(Debug, PartialEq, Trace)]324pub enum ObjBody {325	MemberList(Vec<Member>),326	ObjComp(ObjComp),327}328329#[cfg_attr(feature = "structdump", derive(Codegen))]330#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]331#[derive(Debug, PartialEq, Eq, Clone, Copy, Trace)]332pub enum LiteralType {333	This,334	Super,335	Dollar,336	Null,337	True,338	False,339}340341#[cfg_attr(feature = "structdump", derive(Codegen))]342#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]343#[derive(Debug, PartialEq, Trace)]344pub struct SliceDesc {345	pub start: Option<LocExpr>,346	pub end: Option<LocExpr>,347	pub step: Option<LocExpr>,348}349350/// Syntax base351#[cfg_attr(feature = "structdump", derive(Codegen))]352#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]353#[derive(Debug, PartialEq, Trace)]354pub enum Expr {355	Literal(LiteralType),356357	/// String value: "hello"358	Str(IStr),359	/// Number: 1, 2.0, 2e+20360	Num(f64),361	/// Variable name: test362	Var(IStr),363364	/// Array of expressions: [1, 2, "Hello"]365	Arr(Vec<LocExpr>),366	/// Array comprehension:367	/// ```jsonnet368	///  ingredients: [369	///    { kind: kind, qty: 4 / 3 }370	///    for kind in [371	///      'Honey Syrup',372	///      'Lemon Juice',373	///      'Farmers Gin',374	///    ]375	///  ],376	/// ```377	ArrComp(LocExpr, Vec<CompSpec>),378379	/// Object: {a: 2}380	Obj(ObjBody),381	/// Object extension: var1 {b: 2}382	ObjExtend(LocExpr, ObjBody),383384	/// (obj)385	Parened(LocExpr),386387	/// -2388	UnaryOp(UnaryOpType, LocExpr),389	/// 2 - 2390	BinaryOp(LocExpr, BinaryOpType, LocExpr),391	/// assert 2 == 2 : "Math is broken"392	AssertExpr(AssertStmt, LocExpr),393	/// local a = 2; { b: a }394	LocalExpr(Vec<BindSpec>, LocExpr),395396	/// import "hello"397	Import(LocExpr),398	/// importStr "file.txt"399	ImportStr(LocExpr),400	/// importBin "file.txt"401	ImportBin(LocExpr),402	/// error "I'm broken"403	ErrorStmt(LocExpr),404	/// a(b, c)405	Apply(LocExpr, ArgsDesc, bool),406	/// a[b], a.b, a?.b407	Index {408		indexable: LocExpr,409		index: LocExpr,410		#[cfg(feature = "exp-null-coaelse")]411		null_coaelse: bool,412	},413	/// function(x) x414	Function(ParamsDesc, LocExpr),415	/// if true == false then 1 else 2416	IfElse {417		cond: IfSpecData,418		cond_then: LocExpr,419		cond_else: Option<LocExpr>,420	},421	Slice(LocExpr, SliceDesc),422}423424/// file, begin offset, end offset425#[cfg_attr(feature = "structdump", derive(Codegen))]426#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]427#[derive(Clone, PartialEq, Eq, Trace)]428#[trace(skip)]429#[repr(C)]430pub struct ExprLocation(pub Source, pub u32, pub u32);431impl ExprLocation {432	pub fn belongs_to(&self, other: &ExprLocation) -> bool {433		other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2434	}435}436437#[cfg(target_pointer_width = "64")]438static_assertions::assert_eq_size!(ExprLocation, [u8; 16]);439440impl Debug for ExprLocation {441	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {442		write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2)443	}444}445446/// Holds AST expression and its location in source file447#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]448#[cfg_attr(feature = "structdump", derive(Codegen))]449#[derive(Clone, PartialEq, Trace)]450pub struct LocExpr(pub Rc<Expr>, pub ExprLocation);451452#[cfg(target_pointer_width = "64")]453static_assertions::assert_eq_size!(LocExpr, [u8; 24]);454455impl Debug for LocExpr {456	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {457		if f.alternate() {458			write!(f, "{:#?}", self.0)?;459		} else {460			write!(f, "{:?}", self.0)?;461		}462		write!(f, " from {:?}", self.1)?;463		Ok(())464	}465}
after · crates/jrsonnet-parser/src/expr.rs
1use std::{2	fmt::{self, Debug, Display},3	ops::Deref,4	rc::Rc,5};67use jrsonnet_gcmodule::Trace;8use jrsonnet_interner::IStr;9#[cfg(feature = "serde")]10use serde::{Deserialize, Serialize};11#[cfg(feature = "structdump")]12use structdump::Codegen;1314use crate::source::Source;1516#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]17#[cfg_attr(feature = "structdump", derive(Codegen))]18#[derive(Debug, PartialEq, Trace)]19pub enum FieldName {20	/// {fixed: 2}21	Fixed(IStr),22	/// {["dyn"+"amic"]: 3}23	Dyn(LocExpr),24}2526#[cfg_attr(feature = "structdump", derive(Codegen))]27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]28#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]29pub enum Visibility {30	/// :31	Normal,32	/// ::33	Hidden,34	/// :::35	Unhide,36}3738impl Visibility {39	pub fn is_visible(&self) -> bool {40		matches!(self, Self::Normal | Self::Unhide)41	}42}4344#[cfg_attr(feature = "structdump", derive(Codegen))]45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]46#[derive(Clone, Debug, PartialEq, Trace)]47pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);4849#[cfg_attr(feature = "structdump", derive(Codegen))]50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]51#[derive(Debug, PartialEq, Trace)]52pub struct FieldMember {53	pub name: FieldName,54	pub plus: bool,55	pub params: Option<ParamsDesc>,56	pub visibility: Visibility,57	pub value: LocExpr,58}5960#[cfg_attr(feature = "structdump", derive(Codegen))]61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]62#[derive(Debug, PartialEq, Trace)]63pub enum Member {64	Field(FieldMember),65	BindStmt(BindSpec),66	AssertStmt(AssertStmt),67}6869#[cfg_attr(feature = "structdump", derive(Codegen))]70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]71#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]72pub enum UnaryOpType {73	Plus,74	Minus,75	BitNot,76	Not,77}7879impl Display for UnaryOpType {80	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {81		use UnaryOpType::*;82		write!(83			f,84			"{}",85			match self {86				Plus => "+",87				Minus => "-",88				BitNot => "~",89				Not => "!",90			}91		)92	}93}9495#[cfg_attr(feature = "structdump", derive(Codegen))]96#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]97#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]98pub enum BinaryOpType {99	Mul,100	Div,101102	/// Implemented as intrinsic, put here for completeness103	Mod,104105	Add,106	Sub,107108	Lhs,109	Rhs,110111	Lt,112	Gt,113	Lte,114	Gte,115116	BitAnd,117	BitOr,118	BitXor,119120	Eq,121	Neq,122123	And,124	Or,125	#[cfg(feature = "exp-null-coaelse")]126	NullCoaelse,127128	// Equialent to std.objectHasEx(a, b, true)129	In,130}131132impl Display for BinaryOpType {133	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134		use BinaryOpType::*;135		write!(136			f,137			"{}",138			match self {139				Mul => "*",140				Div => "/",141				Mod => "%",142				Add => "+",143				Sub => "-",144				Lhs => "<<",145				Rhs => ">>",146				Lt => "<",147				Gt => ">",148				Lte => "<=",149				Gte => ">=",150				BitAnd => "&",151				BitOr => "|",152				BitXor => "^",153				Eq => "==",154				Neq => "!=",155				And => "&&",156				Or => "||",157				In => "in",158				#[cfg(feature = "exp-null-coaelse")]159				NullCoaelse => "??",160			}161		)162	}163}164165/// name, default value166#[cfg_attr(feature = "structdump", derive(Codegen))]167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]168#[derive(Debug, PartialEq, Trace)]169pub struct Param(pub Destruct, pub Option<LocExpr>);170171/// Defined function parameters172#[cfg_attr(feature = "structdump", derive(Codegen))]173#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]174#[derive(Debug, Clone, PartialEq, Trace)]175pub struct ParamsDesc(pub Rc<Vec<Param>>);176177impl Deref for ParamsDesc {178	type Target = Vec<Param>;179	fn deref(&self) -> &Self::Target {180		&self.0181	}182}183184#[cfg_attr(feature = "structdump", derive(Codegen))]185#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]186#[derive(Debug, PartialEq, Trace)]187pub struct ArgsDesc {188	pub unnamed: Vec<LocExpr>,189	pub named: Vec<(IStr, LocExpr)>,190}191impl ArgsDesc {192	pub fn new(unnamed: Vec<LocExpr>, named: Vec<(IStr, LocExpr)>) -> Self {193		Self { unnamed, named }194	}195}196197#[cfg_attr(feature = "structdump", derive(Codegen))]198#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]199#[derive(Debug, Clone, PartialEq, Eq, Trace)]200pub enum DestructRest {201	/// ...rest202	Keep(IStr),203	/// ...204	Drop,205}206207#[cfg_attr(feature = "structdump", derive(Codegen))]208#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]209#[derive(Debug, Clone, PartialEq, Trace)]210pub enum Destruct {211	Full(IStr),212	#[cfg(feature = "exp-destruct")]213	Skip,214	#[cfg(feature = "exp-destruct")]215	Array {216		start: Vec<Destruct>,217		rest: Option<DestructRest>,218		end: Vec<Destruct>,219	},220	#[cfg(feature = "exp-destruct")]221	Object {222		fields: Vec<(IStr, Option<Destruct>, Option<LocExpr>)>,223		rest: Option<DestructRest>,224	},225}226impl Destruct {227	/// Name of destructure, used for function parameter names228	pub fn name(&self) -> Option<IStr> {229		match self {230			Self::Full(name) => Some(name.clone()),231			#[cfg(feature = "exp-destruct")]232			_ => None,233		}234	}235	pub fn capacity_hint(&self) -> usize {236		#[cfg(feature = "exp-destruct")]237		fn cap_rest(rest: &Option<DestructRest>) -> usize {238			match rest {239				Some(DestructRest::Keep(_)) => 1,240				Some(DestructRest::Drop) => 0,241				None => 0,242			}243		}244		match self {245			Self::Full(_) => 1,246			#[cfg(feature = "exp-destruct")]247			Self::Skip => 0,248			#[cfg(feature = "exp-destruct")]249			Self::Array { start, rest, end } => {250				start.iter().map(Destruct::capacity_hint).sum::<usize>()251					+ end.iter().map(Destruct::capacity_hint).sum::<usize>()252					+ cap_rest(rest)253			}254			#[cfg(feature = "exp-destruct")]255			Self::Object { fields, rest } => {256				let mut out = 0;257				for (_, into, _) in fields {258					match into {259						Some(v) => out += v.capacity_hint(),260						// Field is destructured to default name261						None => out += 1,262					}263				}264				out + cap_rest(rest)265			}266		}267	}268}269270#[cfg_attr(feature = "structdump", derive(Codegen))]271#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]272#[derive(Debug, Clone, PartialEq, Trace)]273pub enum BindSpec {274	Field {275		into: Destruct,276		value: LocExpr,277	},278	Function {279		name: IStr,280		params: ParamsDesc,281		value: LocExpr,282	},283}284impl BindSpec {285	pub fn capacity_hint(&self) -> usize {286		match self {287			BindSpec::Field { into, .. } => into.capacity_hint(),288			BindSpec::Function { .. } => 1,289		}290	}291}292293#[cfg_attr(feature = "structdump", derive(Codegen))]294#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]295#[derive(Debug, PartialEq, Trace)]296pub struct IfSpecData(pub LocExpr);297298#[cfg_attr(feature = "structdump", derive(Codegen))]299#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]300#[derive(Debug, PartialEq, Trace)]301pub struct ForSpecData(pub Destruct, pub LocExpr);302303#[cfg_attr(feature = "structdump", derive(Codegen))]304#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]305#[derive(Debug, PartialEq, Trace)]306pub enum CompSpec {307	IfSpec(IfSpecData),308	ForSpec(ForSpecData),309}310311#[cfg_attr(feature = "structdump", derive(Codegen))]312#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]313#[derive(Debug, PartialEq, Trace)]314pub struct ObjComp {315	pub pre_locals: Vec<BindSpec>,316	pub field: FieldMember,317	pub post_locals: Vec<BindSpec>,318	pub compspecs: Vec<CompSpec>,319}320321#[cfg_attr(feature = "structdump", derive(Codegen))]322#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]323#[derive(Debug, PartialEq, Trace)]324pub enum ObjBody {325	MemberList(Vec<Member>),326	ObjComp(ObjComp),327}328329#[cfg_attr(feature = "structdump", derive(Codegen))]330#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]331#[derive(Debug, PartialEq, Eq, Clone, Copy, Trace)]332pub enum LiteralType {333	This,334	Super,335	Dollar,336	Null,337	True,338	False,339}340341#[cfg_attr(feature = "structdump", derive(Codegen))]342#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]343#[derive(Debug, PartialEq, Trace)]344pub struct SliceDesc {345	pub start: Option<LocExpr>,346	pub end: Option<LocExpr>,347	pub step: Option<LocExpr>,348}349350/// Syntax base351#[cfg_attr(feature = "structdump", derive(Codegen))]352#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]353#[derive(Debug, PartialEq, Trace)]354pub enum Expr {355	Literal(LiteralType),356357	/// String value: "hello"358	Str(IStr),359	/// Number: 1, 2.0, 2e+20360	Num(f64),361	/// Variable name: test362	Var(IStr),363364	/// Array of expressions: [1, 2, "Hello"]365	Arr(Vec<LocExpr>),366	/// Array comprehension:367	/// ```jsonnet368	///  ingredients: [369	///    { kind: kind, qty: 4 / 3 }370	///    for kind in [371	///      'Honey Syrup',372	///      'Lemon Juice',373	///      'Farmers Gin',374	///    ]375	///  ],376	/// ```377	ArrComp(LocExpr, Vec<CompSpec>),378379	/// Object: {a: 2}380	Obj(ObjBody),381	/// Object extension: var1 {b: 2}382	ObjExtend(LocExpr, ObjBody),383384	/// (obj)385	Parened(LocExpr),386387	/// -2388	UnaryOp(UnaryOpType, LocExpr),389	/// 2 - 2390	BinaryOp(LocExpr, BinaryOpType, LocExpr),391	/// assert 2 == 2 : "Math is broken"392	AssertExpr(AssertStmt, LocExpr),393	/// local a = 2; { b: a }394	LocalExpr(Vec<BindSpec>, LocExpr),395396	/// import "hello"397	Import(LocExpr),398	/// importStr "file.txt"399	ImportStr(LocExpr),400	/// importBin "file.txt"401	ImportBin(LocExpr),402	/// error "I'm broken"403	ErrorStmt(LocExpr),404	/// a(b, c)405	Apply(LocExpr, ArgsDesc, bool),406	/// a[b], a.b, a?.b407	Index {408		indexable: LocExpr,409		parts: Vec<IndexPart>,410	},411	/// function(x) x412	Function(ParamsDesc, LocExpr),413	/// if true == false then 1 else 2414	IfElse {415		cond: IfSpecData,416		cond_then: LocExpr,417		cond_else: Option<LocExpr>,418	},419	Slice(LocExpr, SliceDesc),420}421422#[cfg_attr(feature = "structdump", derive(Codegen))]423#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]424#[derive(Debug, PartialEq, Trace)]425pub struct IndexPart {426	pub value: LocExpr,427	#[cfg(feature = "exp-null-coaelse")]428	pub null_coaelse: bool,429}430431/// file, begin offset, end offset432#[cfg_attr(feature = "structdump", derive(Codegen))]433#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]434#[derive(Clone, PartialEq, Eq, Trace)]435#[trace(skip)]436#[repr(C)]437pub struct ExprLocation(pub Source, pub u32, pub u32);438impl ExprLocation {439	pub fn belongs_to(&self, other: &ExprLocation) -> bool {440		other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2441	}442}443444#[cfg(target_pointer_width = "64")]445static_assertions::assert_eq_size!(ExprLocation, [u8; 16]);446447impl Debug for ExprLocation {448	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {449		write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2)450	}451}452453/// Holds AST expression and its location in source file454#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]455#[cfg_attr(feature = "structdump", derive(Codegen))]456#[derive(Clone, PartialEq, Trace)]457pub struct LocExpr(pub Rc<Expr>, pub ExprLocation);458459#[cfg(target_pointer_width = "64")]460static_assertions::assert_eq_size!(LocExpr, [u8; 24]);461462impl Debug for LocExpr {463	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {464		if f.alternate() {465			write!(f, "{:#?}", self.0)?;466		} else {467			write!(f, "{:?}", self.0)?;468		}469		write!(f, " from {:?}", self.1)?;470		Ok(())471	}472}
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -337,22 +337,24 @@
 						unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}
 				--
 				a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}
-				indexable:(@) _ null_coaelse:("?" _ ensure_null_coaelse())? "."  _ index:id_loc(s) {Expr::Index{
-					indexable, index,
-					#[cfg(feature = "exp-null-coaelse")]
-					null_coaelse: null_coaelse.is_some(),
-				}}
-				indexable:(@) _ null_coaelse:("?" _ "." _ ensure_null_coaelse())? "[" _ index:expr(s) _ "]" {Expr::Index{
-					indexable, index,
-					#[cfg(feature = "exp-null-coaelse")]
-					null_coaelse: null_coaelse.is_some(),
-				}}
+				indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable, parts}}
 				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}
 				a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}
 				--
 				e:expr_basic(s) {e}
 				"(" _ e:expr(s) _ ")" {Expr::Parened(e)}
 			}
+		pub rule index_part(s: &ParserSettings) -> IndexPart
+		= n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart {
+			value,
+			#[cfg(feature = "exp-null-coaelse")]
+			null_coaelse: n.is_some(),
+		}}
+		/ n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart {
+			value,
+			#[cfg(feature = "exp-null-coaelse")]
+			null_coaelse: n.is_some(),
+		}}
 
 		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}
 	}