difftreelog
refactor(ir) flatten obj member
in: master
7 files changed
crates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -4,7 +4,7 @@
use jrsonnet_gcmodule::Acyclic;
use jrsonnet_parser::{
ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName,
- ForSpecData, IfElse, IfSpecData, ImportKind, Member, ObjBody, Param, ParamsDesc,
+ ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Param, ParamsDesc,
ParserSettings, Slice, SliceDesc, Source, SourcePath, Spanned,
};
use rustc_hash::FxHashMap;
@@ -102,31 +102,30 @@
}
fn in_obj(obj: &ObjBody, out: &mut FoundImports) {
match obj {
- ObjBody::MemberList(v) => {
- for member in v {
- match member {
- Member::Field(FieldMember {
- name,
- params,
- value,
- ..
- }) => {
- match name {
- FieldName::Fixed(_) => {}
- FieldName::Dyn(expr) => find_imports(expr, out),
- }
- if let Some(params) = params {
- in_params(params, out);
- }
- find_imports(value, out);
- }
- Member::BindStmt(_) => todo!(),
- Member::AssertStmt(assert) => {
- find_imports(&assert.0, out);
- if let Some(expr) = &assert.1 {
- find_imports(expr, out);
- }
- }
+ ObjBody::MemberList(obj) => {
+ for FieldMember {
+ name,
+ params,
+ value,
+ ..
+ } in &obj.fields
+ {
+ match name {
+ FieldName::Fixed(_) => {}
+ FieldName::Dyn(expr) => find_imports(expr, out),
+ }
+ if let Some(params) = params {
+ in_params(params, out);
+ }
+ find_imports(value, out);
+ }
+ for _ in &*obj.locals {
+ todo!()
+ }
+ for assert in &*obj.asserts {
+ find_imports(&assert.0, out);
+ if let Some(expr) = &assert.1 {
+ find_imports(expr, out);
}
}
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, FieldName,7 ForSpecData, IfSpecData, ImportKind, LiteralType, Member, ObjBody, ParamsDesc, Spanned,8};9use jrsonnet_types::ValType;10use rustc_hash::FxHashMap;1112use self::destructure::destruct;13use crate::{14 arr::ArrValue,15 bail,16 destructure::evaluate_dest,17 error::{suggest_object_fields, ErrorKind::*},18 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},19 function::{CallLocation, FuncDesc, FuncVal},20 gc::WithCapacityExt as _,21 in_frame,22 typed::Typed,23 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},24 with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,25 ResultExt, SupThis, Unbound, Val,26};27pub mod destructure;28pub mod operator;2930// This is the amount of bytes that need to be left on the stack before increasing the size.31// It must be at least as large as the stack required by any code that does not call32// `ensure_sufficient_stack`.33const RED_ZONE: usize = 100 * 1024; // 100k3435// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then36// on. This flag has performance relevant characteristics. Don't set it too high.37const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB3839/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations40/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit41/// from this.42///43/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.44#[inline]45pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}4849pub fn evaluate_trivial(expr: &Spanned<Expr>) -> Option<Val> {50 fn is_trivial(expr: &Spanned<Expr>) -> bool {51 match &**expr {52 Expr::Str(_)53 | Expr::Num(_)54 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,55 Expr::Arr(a) => a.iter().all(is_trivial),56 _ => false,57 }58 }59 Some(match &**expr {60 Expr::Str(s) => Val::string(s.clone()),61 Expr::Num(n) => {62 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))63 }64 Expr::Literal(LiteralType::False) => Val::Bool(false),65 Expr::Literal(LiteralType::True) => Val::Bool(true),66 Expr::Literal(LiteralType::Null) => Val::Null,67 Expr::Arr(n) => {68 if n.iter().any(|e| !is_trivial(e)) {69 return None;70 }71 Val::Arr(ArrValue::eager(72 n.iter()73 .map(evaluate_trivial)74 .map(|e| e.expect("checked trivial"))75 .collect(),76 ))77 }78 _ => return None,79 })80}8182pub fn evaluate_method(83 ctx: Context,84 name: IStr,85 params: ParamsDesc,86 body: Rc<Spanned<Expr>>,87) -> Val {88 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {89 name,90 ctx,91 params,92 body,93 })))94}9596pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {97 Ok(match field_name {98 FieldName::Fixed(n) => Some(n.clone()),99 FieldName::Dyn(expr) => in_frame(100 CallLocation::new(&expr.span()),101 || "evaluating field name".to_string(),102 || {103 let value = evaluate(ctx, expr)?;104 if matches!(value, Val::Null) {105 Ok(None)106 } else {107 Ok(Some(IStr::from_untyped(value)?))108 }109 },110 )?,111 })112}113114pub fn evaluate_comp(115 ctx: Context,116 specs: &[CompSpec],117 callback: &mut impl FnMut(Context) -> Result<()>,118) -> Result<()> {119 match specs.first() {120 None => callback(ctx)?,121 Some(CompSpec::IfSpec(IfSpecData(cond))) => {122 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {123 evaluate_comp(ctx, &specs[1..], callback)?;124 }125 }126 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {127 Val::Arr(list) => {128 for item in list.iter_lazy() {129 let fctx = Pending::new();130 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());131 destruct(var, item, fctx.clone(), &mut new_bindings)?;132 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);133134 evaluate_comp(ctx, &specs[1..], callback)?;135 }136 }137 #[cfg(feature = "exp-object-iteration")]138 Val::Obj(obj) => {139 for field in obj.fields(140 // TODO: Should there be ability to preserve iteration order?141 #[cfg(feature = "exp-preserve-order")]142 false,143 ) {144 let fctx = Pending::new();145 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());146 let obj = obj.clone();147 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![148 Thunk::evaluated(Val::string(field.clone())),149 Thunk!(move || obj.get(field).transpose().expect(150 "field exists, as field name was obtained from object.fields()",151 )),152 ])));153 destruct(var, value, fctx.clone(), &mut new_bindings)?;154 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);155156 evaluate_comp(ctx, &specs[1..], callback)?;157 }158 }159 _ => bail!(InComprehensionCanOnlyIterateOverArray),160 },161 }162 Ok(())163}164165trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}166impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}167168fn evaluate_object_locals(169 fctx: Context,170 locals: Rc<Vec<BindSpec>>,171) -> impl CloneableUnbound<Context> {172 #[derive(Trace, Clone)]173 struct UnboundLocals {174 fctx: Context,175 locals: Rc<Vec<BindSpec>>,176 }177 impl Unbound for UnboundLocals {178 type Bound = Context;179180 fn bind(&self, sup_this: SupThis) -> Result<Context> {181 let fctx = Context::new_future();182 let mut new_bindings =183 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());184 for b in self.locals.iter() {185 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;186 }187188 let ctx = self.fctx.clone();189190 let ctx = ctx191 .extend_bindings_sup_this(new_bindings, sup_this)192 .into_future(fctx);193194 Ok(ctx)195 }196 }197198 UnboundLocals { fctx, locals }199}200201pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(202 builder: &mut ObjValueBuilder,203 ctx: Context,204 uctx: B,205 field: &FieldMember,206) -> Result<()> {207 let name = evaluate_field_name(ctx, &field.name)?;208 let Some(name) = name else {209 return Ok(());210 };211212 match field {213 FieldMember {214 plus,215 params: None,216 visibility,217 value,218 ..219 } => {220 #[derive(Trace)]221 struct UnboundValue<B: Trace> {222 uctx: B,223 value: Rc<Spanned<Expr>>,224 name: IStr,225 }226 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {227 type Bound = Val;228 fn bind(&self, sup_this: SupThis) -> Result<Val> {229 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())230 }231 }232233 builder234 .field(name.clone())235 .with_add(*plus)236 .with_visibility(*visibility)237 .with_location(value.span())238 .bindable(UnboundValue {239 uctx,240 value: value.clone(),241 name,242 })?;243 }244 FieldMember {245 params: Some(params),246 visibility,247 value,248 ..249 } => {250 #[derive(Trace)]251 struct UnboundMethod<B: Trace> {252 uctx: B,253 value: Rc<Spanned<Expr>>,254 params: ParamsDesc,255 name: IStr,256 }257 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {258 type Bound = Val;259 fn bind(&self, sup_this: SupThis) -> Result<Val> {260 Ok(evaluate_method(261 self.uctx.bind(sup_this)?,262 self.name.clone(),263 self.params.clone(),264 self.value.clone(),265 ))266 }267 }268269 builder270 .field(name.clone())271 .with_visibility(*visibility)272 .with_location(value.span())273 .bindable(UnboundMethod {274 uctx,275 value: value.clone(),276 params: params.clone(),277 name,278 })?;279 }280 }281 Ok(())282}283284#[allow(clippy::too_many_lines)]285pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {286 let mut builder = ObjValueBuilder::new();287 let locals = Rc::new(288 members289 .iter()290 .filter_map(|m| match m {291 Member::BindStmt(bind) => Some(bind.clone()),292 _ => None,293 })294 .collect::<Vec<_>>(),295 );296297 // We have single context for all fields, so we can cache binds298 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));299300 for member in members {301 match member {302 Member::Field(field) => {303 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;304 }305 Member::AssertStmt(stmt) => {306 #[derive(Trace)]307 struct ObjectAssert<B: Trace> {308 uctx: B,309 assert: Rc<AssertStmt>,310 }311 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {312 fn run(&self, sup_this: SupThis) -> Result<()> {313 let ctx = self.uctx.bind(sup_this)?;314 evaluate_assert(ctx, &self.assert)315 }316 }317 builder.assert(ObjectAssert {318 uctx: uctx.clone(),319 assert: stmt.clone(),320 });321 }322 Member::BindStmt(_) => {323 // Already handled324 }325 }326 }327 Ok(builder.build())328}329330pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {331 Ok(match object {332 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,333 ObjBody::ObjComp(obj) => {334 let mut builder = ObjValueBuilder::new();335 let locals = Rc::new(336 obj.pre_locals337 .iter()338 .chain(obj.post_locals.iter())339 .cloned()340 .collect::<Vec<_>>(),341 );342 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {343 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());344345 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)346 })?;347348 builder.build()349 }350 })351}352353pub fn evaluate_apply(354 ctx: Context,355 value: &Spanned<Expr>,356 args: &ArgsDesc,357 loc: CallLocation<'_>,358 tailstrict: bool,359) -> Result<Val> {360 let value = evaluate(ctx.clone(), value)?;361 Ok(match value {362 Val::Func(f) => {363 let body = || f.evaluate(ctx, loc, args, tailstrict);364 if tailstrict {365 body()?366 } else {367 in_frame(loc, || format!("function <{}> call", f.name()), body)?368 }369 }370 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),371 })372}373374pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {375 let value = &assertion.0;376 let msg = &assertion.1;377 let assertion_result = in_frame(378 CallLocation::new(&value.span()),379 || "assertion condition".to_owned(),380 || bool::from_untyped(evaluate(ctx.clone(), value)?),381 )?;382 if !assertion_result {383 in_frame(384 CallLocation::new(&value.span()),385 || "assertion failure".to_owned(),386 || {387 if let Some(msg) = msg {388 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));389 }390 bail!(AssertionFailed(Val::Null.to_string()?));391 },392 )?;393 }394 Ok(())395}396397pub fn evaluate_named(ctx: Context, expr: &Spanned<Expr>, name: IStr) -> Result<Val> {398 use Expr::*;399 Ok(match &**expr {400 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),401 _ => evaluate(ctx, expr)?,402 })403}404405#[allow(clippy::too_many_lines)]406pub fn evaluate(ctx: Context, expr: &Spanned<Expr>) -> Result<Val> {407 use Expr::*;408409 if let Some(trivial) = evaluate_trivial(expr) {410 return Ok(trivial);411 }412 let loc = expr.span();413 Ok(match &**expr {414 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),415 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),416 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),417 Literal(LiteralType::True) => Val::Bool(true),418 Literal(LiteralType::False) => Val::Bool(false),419 Literal(LiteralType::Null) => Val::Null,420 Str(v) => Val::string(v.clone()),421 Num(v) => Val::try_num(*v)?,422 // I have tried to remove special behavior from super by implementing standalone-super423 // expresion, but looks like this case still needs special treatment.424 //425 // Note that other jsonnet implementations will fail on `if value in (super)` expression,426 // because the standalone super literal is not supported, that is because in other427 // implementations `in super` treated differently from `in smth_else`.428 BinaryOp(bin)429 if matches!(&*bin.rhs, Expr::Literal(LiteralType::Super))430 && bin.op == BinaryOpType::In =>431 {432 let sup_this = ctx.try_sup_this()?;433 // In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence.434 // In jrsonnet, however, this wasn't true, this was kept here for compatibility.435 if !sup_this.has_super() {436 return Ok(Val::Bool(false));437 }438 let field = evaluate(ctx, &bin.lhs)?;439 Val::Bool(sup_this.field_in_super(field.to_string()?))440 }441 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,442 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,443 Var(name) => in_frame(444 CallLocation::new(&loc),445 || format!("local <{name}> access"),446 || ctx.binding(name.clone())?.evaluate(),447 )?,448 Index { indexable, parts } => ensure_sufficient_stack(|| {449 let mut parts = parts.iter();450 let mut indexable = if matches!(&***indexable, Expr::Literal(LiteralType::Super)) {451 let part = parts.next().expect("at least part should exist");452 // sup_this existence check might also be skipped here for null-coalesce...453 // But I believe this might cause errors.454 let sup_this = ctx.try_sup_this()?;455 if !sup_this.has_super() {456 #[cfg(feature = "exp-null-coaelse")]457 if part.null_coaelse {458 return Ok(Val::Null);459 }460 bail!(NoSuperFound)461 }462 let name = evaluate(ctx.clone(), &part.value)?;463464 let Val::Str(name) = name else {465 bail!(ValueIndexMustBeTypeGot(466 ValType::Obj,467 ValType::Str,468 name.value_type(),469 ))470 };471472 let name = name.into_flat();473 match sup_this474 .get_super(name.clone())475 .with_description_src(&part.value, || format!("field <{name}> access"))?476 {477 Some(v) => v,478 #[cfg(feature = "exp-null-coaelse")]479 None if part.null_coaelse => return Ok(Val::Null),480 None => {481 let suggestions = suggest_object_fields(482 &sup_this.standalone_super().expect("super exists"),483 name.clone(),484 );485486 bail!(NoSuchField(name, suggestions))487 }488 }489 } else {490 evaluate(ctx.clone(), indexable)?491 };492493 for part in parts {494 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {495 (Val::Obj(v), Val::Str(key)) => match v496 .get(key.clone().into_flat())497 .with_description_src(&part.value, || format!("field <{key}> access"))?498 {499 Some(v) => v,500 #[cfg(feature = "exp-null-coaelse")]501 None if part.null_coaelse => return Ok(Val::Null),502 None => {503 let suggestions = suggest_object_fields(&v, key.clone().into_flat());504505 return Err(Error::from(NoSuchField(506 key.clone().into_flat(),507 suggestions,508 )))509 .with_description_src(&part.value, || format!("field <{key}> access"));510 }511 },512 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(513 ValType::Obj,514 ValType::Str,515 n.value_type(),516 )),517 (Val::Arr(v), Val::Num(n)) => {518 let n = n.get();519 if n.fract() > f64::EPSILON {520 bail!(FractionalIndex)521 }522 if n < 0.0 {523 bail!(ArrayBoundsError(n as isize, v.len()));524 }525 v.get(n as usize)?526 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?527 }528 (Val::Arr(_), Val::Str(n)) => {529 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))530 }531 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(532 ValType::Arr,533 ValType::Num,534 n.value_type(),535 )),536537 (Val::Str(s), Val::Num(n)) => Val::Str({538 let n = n.get();539 if n.fract() > f64::EPSILON {540 bail!(FractionalIndex)541 }542 if n < 0.0 {543 bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));544 }545 let v: IStr = s546 .clone()547 .into_flat()548 .chars()549 .skip(n as usize)550 .take(1)551 .collect::<String>()552 .into();553 if v.is_empty() {554 bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))555 }556 StrValue::Flat(v)557 }),558 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(559 ValType::Str,560 ValType::Num,561 n.value_type(),562 )),563 #[cfg(feature = "exp-null-coaelse")]564 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),565 (v, _) => bail!(CantIndexInto(v.value_type())),566 };567 }568 Ok(indexable)569 })?,570 LocalExpr(bindings, returned) => {571 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =572 FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());573 let fctx = Context::new_future();574 for b in bindings {575 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;576 }577 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);578 evaluate(ctx, &returned.clone())?579 }580 Arr(items) => {581 if items.is_empty() {582 Val::Arr(ArrValue::empty())583 } else {584 Val::Arr(ArrValue::expr(ctx, items.clone()))585 }586 }587 ArrComp(expr, comp_specs) => {588 let mut out = Vec::new();589 evaluate_comp(ctx, comp_specs, &mut |ctx| {590 let expr = expr.clone();591 out.push(Thunk!(move || evaluate(ctx, &expr)));592 Ok(())593 })?;594 Val::Arr(ArrValue::lazy(out))595 }596 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),597 ObjExtend(a, b) => evaluate_add_op(598 &evaluate(ctx.clone(), a)?,599 &Val::Obj(evaluate_object(ctx, b)?),600 )?,601 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {602 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)603 })?,604 Function(params, body) => {605 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())606 }607 AssertExpr(assert) => {608 evaluate_assert(ctx.clone(), &assert.assert)?;609 evaluate(ctx, &assert.rest)?610 }611 ErrorStmt(e) => in_frame(612 CallLocation::new(&loc),613 || "error statement".to_owned(),614 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),615 )?,616 IfElse (if_else)617 // {618 // cond,619 // cond_then,620 // cond_else,621 // }622 => {623 if in_frame(624 CallLocation::new(&loc),625 || "if condition".to_owned(),626 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),627 )? {628 evaluate(ctx, &if_else.cond_then)?629 } else {630 match &if_else.cond_else {631 Some(v) => evaluate(ctx, v)?,632 None => Val::Null,633 }634 }635 }636 Slice(slice) => {637 fn parse_idx<T: Typed>(638 loc: CallLocation<'_>,639 ctx: Context,640 expr: Option<&Spanned<Expr>>,641 desc: &'static str,642 ) -> Result<Option<T>> {643 if let Some(value) = expr {644 Ok(in_frame(645 loc,646 || format!("slice {desc}"),647 || <Option<T>>::from_untyped(evaluate(ctx, value)?),648 )?)649 } else {650 Ok(None)651 }652 }653654 let indexable = evaluate(ctx.clone(), &slice.value)?;655 let loc = CallLocation::new(&loc);656657 let start = parse_idx(loc, ctx.clone(), slice.slice.start.as_ref(), "start")?;658 let end = parse_idx(loc, ctx.clone(), slice.slice.end.as_ref(), "end")?;659 let step = parse_idx(loc, ctx, slice.slice.step.as_ref(), "step")?;660661 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?662 }663 Import(kind, path) => {664 let Expr::Str(path) = &***path else {665 bail!("computed imports are not supported")666 };667 let tmp = loc.clone().0;668 with_state(|s| {669 let resolved_path = s.resolve_from(tmp.source_path(), path)?;670 Ok(match kind {671 ImportKind::Normal => in_frame(672 CallLocation::new(&loc),673 || format!("import {:?}", path.clone()),674 || s.import_resolved(resolved_path),675 )?,676 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),677 ImportKind::Bin => {678 Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?))679 }680 }) as Result<Val>681 })?682 }683 })684}crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -47,10 +47,10 @@
}
#[derive(Debug, PartialEq, Acyclic)]
-pub enum Member {
+pub(crate) enum Member {
Field(FieldMember),
BindStmt(BindSpec),
- AssertStmt(Rc<AssertStmt>),
+ AssertStmt(AssertStmt),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
@@ -240,7 +240,7 @@
}
}
-#[derive(Debug, Clone, PartialEq, Acyclic)]
+#[derive(Debug, PartialEq, Acyclic)]
pub enum BindSpec {
Field {
into: Destruct,
@@ -275,15 +275,21 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct ObjComp {
- pub pre_locals: Vec<BindSpec>,
+ pub locals: Rc<Vec<BindSpec>>,
pub field: Rc<FieldMember>,
- pub post_locals: Vec<BindSpec>,
pub compspecs: Vec<CompSpec>,
}
#[derive(Debug, PartialEq, Acyclic)]
+pub struct ObjMembers {
+ pub locals: Rc<Vec<BindSpec>>,
+ pub asserts: Rc<Vec<AssertStmt>>,
+ pub fields: Vec<FieldMember>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
pub enum ObjBody {
- MemberList(Vec<Member>),
+ MemberList(ObjMembers),
ObjComp(ObjComp),
}
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -139,8 +139,8 @@
/ obj:destruct_object(s) {obj}
pub rule bind(s: &ParserSettings) -> expr::BindSpec
- = into:destruct(s) _ "=" _ expr:expr(s) {expr::BindSpec::Field{into, value: Rc::new(expr)}}
- / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(expr)}}
+ = into:destruct(s) _ "=" _ value:expr(s) {expr::BindSpec::Field{into, value: Rc::new(value)}}
+ / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(value)}}
pub rule assertion(s: &ParserSettings) -> expr::AssertStmt
= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }
@@ -207,20 +207,35 @@
= keyword("local") _ bind:bind(s) {bind}
pub rule member(s: &ParserSettings) -> expr::Member
= bind:obj_local(s) {expr::Member::BindStmt(bind)}
- / assertion:assertion(s) {expr::Member::AssertStmt(Rc::new(assertion))}
+ / assertion:assertion(s) {expr::Member::AssertStmt(assertion)}
/ field:field(s) {expr::Member::Field(field)}
pub rule objinside(s: &ParserSettings) -> expr::ObjBody
= pre_locals:(b: obj_local(s) comma() {b})* &"[" field:field(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
let mut compspecs = vec![CompSpec::ForSpec(forspec)];
compspecs.extend(others.unwrap_or_default());
+ let mut locals = pre_locals;
+ locals.extend(post_locals);
expr::ObjBody::ObjComp(expr::ObjComp{
- pre_locals,
+ locals: Rc::new(locals),
field: Rc::new(field),
- post_locals,
compspecs,
})
}
- / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}
+ / members:(member(s) ** comma()) comma()? {
+ let mut locals = Vec::new();
+ let mut asserts = Vec::new();
+ let mut fields = Vec::new();
+ for member in members {
+ match member {
+ Member::Field(field_member) => fields.push(field_member),
+ Member::BindStmt(bind_spec) => locals.push(bind_spec),
+ Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),
+ }
+ }
+ expr::ObjBody::MemberList(ObjMembers {
+ locals: Rc::new(locals), asserts: Rc::new(asserts), fields
+ })
+ }
pub rule ifspec(s: &ParserSettings) -> IfSpecData
= keyword("if") _ expr:expr(s) {IfSpecData(expr)}
pub rule forspec(s: &ParserSettings) -> ForSpecData
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
@@ -7,12 +7,16 @@
lhs: ObjExtend(
Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:0-2,
MemberList(
- [
- BindStmt(
+ ObjMembers {
+ locals: [
Field {
into: Full(
"x",
@@ -21,8 +25,9 @@
1.0,
) from virtual:<test>:15-16,
},
- ),
- Field(
+ ],
+ asserts: [],
+ fields: [
FieldMember {
name: Fixed(
"x",
@@ -34,14 +39,18 @@
"x",
) from virtual:<test>:21-22,
},
- ),
- ],
+ ],
+ },
),
) from virtual:<test>:0-24,
op: Add,
rhs: Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:27-29,
},
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
@@ -4,6 +4,10 @@
---
Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:0-2
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
@@ -4,8 +4,10 @@
---
Obj(
MemberList(
- [
- Field(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [
FieldMember {
name: Fixed(
"a",
@@ -17,7 +19,7 @@
1.0,
) from virtual:<test>:3-4,
},
- ),
- ],
+ ],
+ },
),
) from virtual:<test>:0-5