difftreelog
feat immutable obj core list
in: master
7 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -163,6 +163,15 @@
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
+name = "bitmaps"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -682,6 +691,20 @@
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
+name = "im-rc"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
+dependencies = [
+ "bitmaps",
+ "rand_core 0.6.4",
+ "rand_xoshiro",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
name = "indexmap"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -793,6 +816,7 @@
"anyhow",
"educe",
"hi-doc",
+ "im-rc",
"jrsonnet-gcmodule",
"jrsonnet-interner",
"jrsonnet-ir",
@@ -837,18 +861,19 @@
[[package]]
name = "jrsonnet-gcmodule"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f95b976a79e4000bb9e07ff0709dca0ea27bcf1952d4c17d91fb7364d6145683"
+checksum = "8a6a63a6e55ba82764e483d7f8a181f25db95a8f25da8ae6520e95a5fe39c6a6"
dependencies = [
+ "im-rc",
"jrsonnet-gcmodule-derive",
]
[[package]]
name = "jrsonnet-gcmodule-derive"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d928626220a310ff0cec815e80cf7fe104697184352ca21c40534e0b0d72d9"
+checksum = "095fe3c4c0acf32de80205a8a479ef63c216b9efb0024dec9eb7fe1c5ef1f1a1"
dependencies = [
"proc-macro2",
"quote",
@@ -1319,7 +1344,7 @@
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
- "rand_core",
+ "rand_core 0.9.5",
]
[[package]]
@@ -1329,11 +1354,17 @@
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.9.5",
]
[[package]]
name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
@@ -1342,6 +1373,15 @@
]
[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
name = "random_color"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1587,6 +1627,16 @@
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
+name = "sized-chunks"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
+dependencies = [
+ "bitmaps",
+ "typenum",
+]
+
+[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,7 +22,7 @@
jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre98" }
jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre98" }
jrsonnet-formatter = { path = "./crates/jrsonnet-formatter", version = "0.5.0-pre98" }
-jrsonnet-gcmodule = { version = "0.4.2" }
+jrsonnet-gcmodule = { version = "0.4.3", features = ["im-rc"] }
# Diagnostics.
# hi-doc is my library, which handles text formatting very well, but isn't polished enough yet
# Previous implementation was based on annotate-snippets, which I don't like for many reasons.
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -76,6 +76,7 @@
"Hash",
"PartialEq",
] }
+im-rc = "15.1.0"
[build-dependencies]
rustversion = "1.0.22"
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -144,9 +144,7 @@
Thunk!(move || {
let full = full.evaluate()?;
let mut builder = ObjValueBuilder::new();
- builder
- .reserve_cores(1)
- .extend_with_core(full.as_standalone());
+ builder.extend_with_core(full.as_standalone());
builder.with_fields_omitted(captured_fields);
Ok(Val::Obj(builder.build()))
}),
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::{6 ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams, FieldMember,7 FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, Spanned,8 function::ParamName,9};10use jrsonnet_types::ValType;11use rustc_hash::FxHashMap;1213use self::destructure::destruct;14use crate::{15 Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt,16 SupThis, Unbound, Val,17 arr::ArrValue,18 bail,19 destructure::evaluate_dest,20 error::{ErrorKind::*, suggest_object_fields},21 evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},22 function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},23 gc::WithCapacityExt as _,24 in_frame,25 typed::{FromUntyped, IntoUntyped as _, Typed},26 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},27 with_state,28};29pub mod destructure;30pub mod operator;3132// This is the amount of bytes that need to be left on the stack before increasing the size.33// It must be at least as large as the stack required by any code that does not call34// `ensure_sufficient_stack`.35const RED_ZONE: usize = 100 * 1024; // 100k3637// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then38// on. This flag has performance relevant characteristics. Don't set it too high.39const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB4041/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations42/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit43/// from this.44///45/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.46#[inline]47pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {48 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)49}5051pub fn evaluate_trivial(expr: &Expr) -> Option<Val> {52 fn is_trivial(expr: &Expr) -> bool {53 match expr {54 Expr::Str(_)55 | Expr::Num(_)56 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,57 Expr::Arr(a) => a.iter().all(is_trivial),58 _ => false,59 }60 }61 Some(match expr {62 Expr::Str(s) => Val::string(s.clone()),63 Expr::Num(n) => {64 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))65 }66 Expr::Literal(LiteralType::False) => Val::Bool(false),67 Expr::Literal(LiteralType::True) => Val::Bool(true),68 Expr::Literal(LiteralType::Null) => Val::Null,69 Expr::Arr(n) => {70 if n.iter().any(|e| !is_trivial(e)) {71 return None;72 }73 Val::Arr(74 n.iter()75 .map(evaluate_trivial)76 .map(|e| e.expect("checked trivial"))77 .collect(),78 )79 }80 _ => return None,81 })82}8384pub fn evaluate_method(ctx: Context, name: IStr, params: ExprParams, body: Rc<Expr>) -> Val {85 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {86 name,87 ctx,88 params,89 body,90 })))91}9293pub fn evaluate_field_name(ctx: Context, field_name: &Spanned<FieldName>) -> Result<Option<IStr>> {94 Ok(match &field_name.value {95 FieldName::Fixed(n) => Some(n.clone()),96 FieldName::Dyn(expr) => in_frame(97 CallLocation::new(&field_name.span),98 || "evaluating field name".to_string(),99 || {100 let v = evaluate(ctx, expr)?;101 Ok(if matches!(v, Val::Null) {102 None103 } else {104 Some(IStr::from_untyped(v)?)105 })106 },107 )?,108 })109}110111pub fn evaluate_comp(112 ctx: Context,113 specs: &[CompSpec],114 mut guaranteed_reserve: usize,115 callback: &mut impl FnMut(Context, usize) -> Result<()>,116) -> Result<()> {117 match specs.first() {118 None => callback(ctx, guaranteed_reserve)?,119 Some(CompSpec::IfSpec(IfSpecData { cond, span: _ })) => {120 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {121 evaluate_comp(ctx, &specs[1..], 0, callback)?;122 }123 }124 Some(CompSpec::ForSpec(ForSpecData {125 destruct: into,126 over,127 })) => {128 match evaluate(ctx.clone(), over)? {129 Val::Arr(list) => {130 guaranteed_reserve = guaranteed_reserve.max(1) * list.len();131 for (i, item) in list.iter_lazy().enumerate() {132 let fctx = Pending::new();133 let mut new_bindings = FxHashMap::with_capacity(into.binds_len());134 destruct(into, item, fctx.clone(), &mut new_bindings)?;135 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);136137 let specs = &specs[1..];138 evaluate_comp(139 ctx,140 specs,141 if i == 0 || !specs.is_empty() {142 guaranteed_reserve143 } else {144 0145 },146 callback,147 )?;148 }149 }150 #[cfg(feature = "exp-object-iteration")]151 Val::Obj(obj) => {152 let fields = obj.fields(153 // TODO: Should there be ability to preserve iteration order?154 #[cfg(feature = "exp-preserve-order")]155 false,156 );157 guaranteed_reserve = guaranteed_reserve.max(1) * fields.len();158 for field in fields {159 let fctx = Pending::new();160 let mut new_bindings = FxHashMap::with_capacity(into.binds_len());161 let obj = obj.clone();162 let value = Thunk::evaluated(Val::arr(vec![163 Thunk::evaluated(Val::string(field.clone())),164 obj.get_lazy(field).transpose().expect(165 "field exists, as field name was obtained from object.fields()",166 ),167 ]));168 destruct(into, value, fctx.clone(), &mut new_bindings)?;169 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);170171 evaluate_comp(ctx, &specs[1..], callback)?;172 }173 }174 _ => bail!(InComprehensionCanOnlyIterateOverArray),175 }176 }177 }178 Ok(())179}180181fn evaluate_arr_comp(ctx: Context, expr: &Rc<Expr>, comp_specs: &[CompSpec]) -> Result<ArrValue> {182 'eager: {183 let mut out = Vec::new();184185 if evaluate_comp(ctx.clone(), comp_specs, 0, &mut |ctx, reserve| {186 if reserve != 0 {187 out.reserve(reserve);188 }189 out.push(evaluate(ctx, expr)?);190 Ok(())191 })192 .is_err()193 {194 break 'eager;195 }196197 return Ok(ArrValue::new(out));198 };199 let mut out = Vec::new();200 evaluate_comp(ctx, comp_specs, 0, &mut |ctx, reserve| {201 if reserve != 0 {202 out.reserve(reserve);203 }204 let expr = expr.clone();205 out.push(Thunk!(move || evaluate(ctx, &expr)));206 Ok(())207 })?;208 Ok(ArrValue::new(out))209}210211trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}212impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}213214fn evaluate_object_locals(215 fctx: Context,216 locals: Rc<Vec<BindSpec>>,217) -> impl CloneableUnbound<Context> {218 #[derive(Trace, Clone)]219 struct UnboundLocals {220 fctx: Context,221 locals: Rc<Vec<BindSpec>>,222 }223 impl Unbound for UnboundLocals {224 type Bound = Context;225226 fn bind(&self, sup_this: SupThis) -> Result<Context> {227 let fctx = Context::new_future();228 let mut new_bindings =229 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::binds_len).sum());230 for b in self.locals.iter() {231 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;232 }233234 let ctx = self.fctx.clone();235236 let ctx = ctx237 .extend_bindings_sup_this(new_bindings, sup_this)238 .into_future(fctx);239240 Ok(ctx)241 }242 }243244 UnboundLocals { fctx, locals }245}246247pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(248 builder: &mut ObjValueBuilder,249 ctx: Context,250 uctx: B,251 field: &FieldMember,252) -> Result<()> {253 let name = evaluate_field_name(ctx, &field.name)?;254 let Some(name) = name else {255 return Ok(());256 };257258 match field {259 FieldMember {260 plus,261 params: None,262 visibility,263 value,264 ..265 } => {266 #[derive(Trace)]267 struct UnboundValue<B: Trace> {268 uctx: B,269 value: Rc<Expr>,270 name: IStr,271 }272 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {273 type Bound = Val;274 fn bind(&self, sup_this: SupThis) -> Result<Val> {275 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())276 }277 }278279 builder280 .field(name.clone())281 .with_add(*plus)282 .with_visibility(*visibility)283 .with_location(field.name.span.clone())284 .bindable(UnboundValue {285 uctx,286 value: value.clone(),287 name,288 })?;289 }290 FieldMember {291 params: Some(params),292 visibility,293 value,294 ..295 } => {296 #[derive(Trace)]297 struct UnboundMethod<B: Trace> {298 uctx: B,299 value: Rc<Expr>,300 params: ExprParams,301 name: IStr,302 }303 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {304 type Bound = Val;305 fn bind(&self, sup_this: SupThis) -> Result<Val> {306 Ok(evaluate_method(307 self.uctx.bind(sup_this)?,308 self.name.clone(),309 self.params.clone(),310 self.value.clone(),311 ))312 }313 }314315 builder316 .field(name.clone())317 .with_visibility(*visibility)318 // .with_location(value.span())319 .bindable(UnboundMethod {320 uctx,321 value: value.clone(),322 params: params.clone(),323 name,324 })?;325 }326 }327 Ok(())328}329330#[derive(Trace, Clone)]331struct DirectUnbound(Context);332impl Unbound for DirectUnbound {333 type Bound = Context;334 fn bind(&self, sup_this: SupThis) -> Result<Context> {335 Ok(self336 .0337 .clone()338 .extend_bindings_sup_this(FxHashMap::new(), sup_this))339 }340}341342#[allow(clippy::too_many_lines)]343pub fn evaluate_member_list_object(344 super_obj: Option<ObjValue>,345 ctx: Context,346 members: &ObjMembers,347) -> Result<ObjValue> {348 #[derive(Trace)]349 struct ObjectAssert<B: Trace> {350 uctx: B,351 asserts: Rc<Vec<AssertStmt>>,352 }353 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {354 fn run(&self, sup_this: SupThis) -> Result<()> {355 let ctx = self.uctx.bind(sup_this)?;356 for assert in &*self.asserts {357 evaluate_assert(ctx.clone(), assert)?;358 }359 Ok(())360 }361 }362363 let mut builder = ObjValueBuilder::new();364 if let Some(super_obj) = super_obj {365 builder.with_super(super_obj);366 }367368 if members.locals.is_empty() {369 // We can use the same context for all field evaluation, it doesn't depends on locals, only on this/super370 let uctx = DirectUnbound(ctx.clone());371 for field in &members.fields {372 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;373 }374 if !members.asserts.is_empty() {375 builder.assert(ObjectAssert {376 uctx,377 asserts: members.asserts.clone(),378 });379 }380 } else {381 let locals = members.locals.clone();382 // We have single context for all fields, so we can cache them together383 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));384 for field in &members.fields {385 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;386 }387 if !members.asserts.is_empty() {388 builder.assert(ObjectAssert {389 uctx,390 asserts: members.asserts.clone(),391 });392 }393 }394395 Ok(builder.build())396}397398pub fn evaluate_object(399 super_obj: Option<ObjValue>,400 ctx: Context,401 object: &ObjBody,402) -> Result<ObjValue> {403 Ok(match object {404 ObjBody::MemberList(members) => evaluate_member_list_object(super_obj, ctx, members)?,405 ObjBody::ObjComp(obj) => {406 let mut builder = ObjValueBuilder::new();407 if let Some(super_obj) = super_obj {408 builder.with_super(super_obj);409 }410 let locals = obj.locals.clone();411 evaluate_comp(ctx, &obj.compspecs, 0, &mut |ctx, reserve| {412 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());413 builder.reserve_cores(reserve);414415 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)416 })?;417418 builder.build()419 }420 })421}422423pub fn evaluate_apply(424 ctx: Context,425 value: &Expr,426 args: &ArgsDesc,427 loc: CallLocation<'_>,428 tailstrict: bool,429) -> Result<Val> {430 let value = evaluate(ctx.clone(), value)?;431 Ok(match value {432 Val::Func(f) => {433 let name = f.name();434 let unnamed = args435 .unnamed436 .iter()437 .cloned()438 .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))439 .collect::<Result<Vec<_>>>()?;440 let named = args441 .values442 .iter()443 .cloned()444 .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))445 .collect::<Result<Vec<_>>>()?;446 let prepare = PreparedFuncVal::new(f, args.unnamed.len(), &args.names)447 .with_description_src(loc, || format!("function <{name}> call"))?;448 let body = || prepare.call(loc, &unnamed, &named);449 if tailstrict {450 body()?451 } else {452 in_frame(loc, || format!("function <{name}> call"), body)?453 }454 }455 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),456 })457}458459pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {460 let value = &assertion.0;461 let msg = &assertion.1;462 let assertion_result = in_frame(463 CallLocation::new(&value.span),464 || "assertion condition".to_owned(),465 || bool::from_untyped(evaluate(ctx.clone(), value)?),466 )?;467 if !assertion_result {468 in_frame(469 CallLocation::new(&value.span),470 || "assertion failure".to_owned(),471 || {472 if let Some(msg) = msg {473 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));474 }475 bail!(AssertionFailed(Val::Null.to_string()?));476 },477 )?;478 }479 Ok(())480}481482pub fn evaluate_named_param(ctx: Context, expr: &Expr, name: ParamName) -> Result<Val> {483 match name {484 ParamName::Named(name) => evaluate_named(ctx, expr, name),485 ParamName::Unnamed => evaluate(ctx, expr),486 }487}488489pub fn evaluate_named(ctx: Context, expr: &Expr, name: IStr) -> Result<Val> {490 use Expr::*;491 Ok(match expr {492 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),493 _ => evaluate(ctx, expr)?,494 })495}496497pub fn evaluate_thunk(ctx: Context, expr: Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {498 Ok(if tailstrict {499 Thunk::evaluated(evaluate(ctx, &expr)?)500 } else {501 Thunk!(move || { evaluate(ctx, &expr) })502 })503}504#[allow(clippy::too_many_lines)]505pub fn evaluate(ctx: Context, expr: &Expr) -> Result<Val> {506 use Expr::*;507508 Ok(match expr {509 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),510 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),511 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),512 Literal(LiteralType::True) => Val::Bool(true),513 Literal(LiteralType::False) => Val::Bool(false),514 Literal(LiteralType::Null) => Val::Null,515 Str(v) => Val::string(v.clone()),516 Num(v) => Val::try_num(*v)?,517 // I have tried to remove special behavior from super by implementing standalone-super518 // expresion, but looks like this case still needs special treatment.519 //520 // Note that other jsonnet implementations will fail on `if value in (super)` expression,521 // because the standalone super literal is not supported, that is because in other522 // implementations `in super` treated differently from `in smth_else`.523 BinaryOp(bin)524 if matches!(&bin.rhs, Expr::Literal(LiteralType::Super))525 && bin.op == BinaryOpType::In =>526 {527 let sup_this = ctx.try_sup_this()?;528 // In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence.529 // In jrsonnet, however, this wasn't true, this was kept here for compatibility.530 if !sup_this.has_super() {531 return Ok(Val::Bool(false));532 }533 let field = evaluate(ctx, &bin.lhs)?;534 Val::Bool(sup_this.field_in_super(field.to_string()?))535 }536 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,537 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,538 Var(name) => in_frame(539 CallLocation::new(&name.span),540 || format!("local <{}> access", &**name),541 || ctx.binding((**name).clone())?.evaluate(),542 )?,543 Index { indexable, parts } => ensure_sufficient_stack(|| {544 let mut parts = parts.iter();545 let mut indexable = if matches!(&**indexable, Expr::Literal(LiteralType::Super)) {546 let part = parts.next().expect("at least part should exist");547 // sup_this existence check might also be skipped here for null-coalesce...548 // But I believe this might cause errors.549 let sup_this = ctx.try_sup_this()?;550 if !sup_this.has_super() {551 #[cfg(feature = "exp-null-coaelse")]552 if part.null_coaelse {553 return Ok(Val::Null);554 }555 bail!(NoSuperFound)556 }557 let name = evaluate(ctx.clone(), &part.value)?;558559 let Val::Str(name) = name else {560 bail!(ValueIndexMustBeTypeGot(561 ValType::Obj,562 ValType::Str,563 name.value_type(),564 ))565 };566567 let name = name.into_flat();568 match sup_this569 .get_super(name.clone())570 .with_description_src(&part.span, || format!("field <{name}> access"))?571 {572 Some(v) => v,573 #[cfg(feature = "exp-null-coaelse")]574 None if part.null_coaelse => return Ok(Val::Null),575 None => {576 let suggestions = suggest_object_fields(577 &sup_this.standalone_super().expect("super exists"),578 name.clone(),579 );580581 bail!(NoSuchField(name, suggestions))582 }583 }584 } else {585 evaluate(ctx.clone(), indexable)?586 };587588 for part in parts {589 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {590 (Val::Obj(v), Val::Str(key)) => match v591 .get(key.clone().into_flat())592 .with_description_src(&part.span, || format!("field <{key}> access"))?593 {594 Some(v) => v,595 #[cfg(feature = "exp-null-coaelse")]596 None if part.null_coaelse => return Ok(Val::Null),597 None => {598 let suggestions = suggest_object_fields(&v, key.into_flat());599600 return Err(Error::from(NoSuchField(601 key.clone().into_flat(),602 suggestions,603 )))604 .with_description_src(&part.span, || format!("field <{key}> access"));605 }606 },607 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(608 ValType::Obj,609 ValType::Str,610 n.value_type(),611 )),612 (Val::Arr(v), Val::Num(n)) => {613 let n = n.get();614 if n.fract() > f64::EPSILON {615 bail!(FractionalIndex)616 }617 if n < 0.0 {618 #[expect(619 clippy::cast_possible_truncation,620 reason = "it would be truncated anyway"621 )]622 let n = n as isize;623 bail!(ArrayBoundsError(n, v.len()));624 }625 #[expect(626 clippy::cast_possible_truncation,627 clippy::cast_sign_loss,628 reason = "n is checked postive"629 )]630 v.get(n as usize)?631 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?632 }633 (Val::Arr(_), Val::Str(n)) => {634 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))635 }636 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(637 ValType::Arr,638 ValType::Num,639 n.value_type(),640 )),641642 (Val::Str(s), Val::Num(n)) => Val::Str({643 let n = n.get();644 if n.fract() > f64::EPSILON {645 bail!(FractionalIndex)646 }647 if n < 0.0 {648 #[expect(649 clippy::cast_possible_truncation,650 reason = "it would be truncated anyway"651 )]652 let n = n as isize;653 bail!(ArrayBoundsError(n, s.into_flat().chars().count()));654 }655 #[expect(656 clippy::cast_sign_loss,657 clippy::cast_possible_truncation,658 reason = "n is positive, overflow will truncate as expected"659 )]660 let n = n as usize;661 let v: IStr = s662 .clone()663 .into_flat()664 .chars()665 .skip(n)666 .take(1)667 .collect::<String>()668 .into();669 if v.is_empty() {670 bail!(StringBoundsError(n, s.into_flat().chars().count()))671 }672 StrValue::Flat(v)673 }),674 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(675 ValType::Str,676 ValType::Num,677 n.value_type(),678 )),679 #[cfg(feature = "exp-null-coaelse")]680 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),681 (v, _) => bail!(CantIndexInto(v.value_type())),682 };683 }684 Ok(indexable)685 })?,686 LocalExpr(bindings, returned) => {687 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =688 FxHashMap::with_capacity(bindings.iter().map(BindSpec::binds_len).sum());689 let fctx = Context::new_future();690 for b in bindings {691 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;692 }693 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);694 evaluate(ctx, returned)?695 }696 Arr(items) => {697 if items.is_empty() {698 Val::arr(())699 } else {700 Val::Arr(ArrValue::expr(ctx, items.clone()))701 }702 }703 ArrComp(expr, comp_specs) => Val::Arr(evaluate_arr_comp(ctx, expr, comp_specs)?),704 Obj(body) => Val::Obj(evaluate_object(None, ctx, body)?),705 ObjExtend(a, b) => {706 let base = evaluate(ctx.clone(), a)?;707 match base {708 Val::Obj(base_obj) => Val::Obj(evaluate_object(Some(base_obj), ctx, b)?),709 _ => bail!("ObjExtend lhs should be an object value"),710 }711 }712 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {713 evaluate_apply(ctx, value, args, CallLocation::new(&args.span), *tailstrict)714 })?,715 Function(params, body) => {716 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())717 }718 AssertExpr(assert) => {719 evaluate_assert(ctx.clone(), &assert.assert)?;720 evaluate(ctx, &assert.rest)?721 }722 ErrorStmt(s, e) => in_frame(723 CallLocation::new(s),724 || "error statement".to_owned(),725 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),726 )?,727 IfElse(if_else) => {728 if in_frame(729 CallLocation::new(&if_else.cond.span),730 || "if condition".to_owned(),731 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.cond)?),732 )? {733 evaluate(ctx, &if_else.cond_then)?734 } else {735 match &if_else.cond_else {736 Some(v) => evaluate(ctx, v)?,737 None => Val::Null,738 }739 }740 }741 Slice(slice) => {742 fn parse_idx<T: Typed + FromUntyped>(743 ctx: Context,744 expr: Option<&Spanned<Expr>>,745 desc: &'static str,746 ) -> Result<Option<T>> {747 if let Some(value) = expr {748 Ok(in_frame(749 CallLocation::new(&value.span),750 || format!("slice {desc}"),751 || <Option<T>>::from_untyped(evaluate(ctx, value)?),752 )?)753 } else {754 Ok(None)755 }756 }757758 let indexable = evaluate(ctx.clone(), &slice.value)?;759760 let start = parse_idx(ctx.clone(), slice.slice.start.as_ref(), "start")?;761 let end = parse_idx(ctx.clone(), slice.slice.end.as_ref(), "end")?;762 let step = parse_idx(ctx, slice.slice.step.as_ref(), "step")?;763764 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?765 }766 Import(kind, path) => {767 let Expr::Str(path) = &**path else {768 bail!("computed imports are not supported")769 };770 with_state(|s| {771 let span = &kind.span;772 let resolved_path = s.resolve_from(span.0.source_path(), path)?;773 Ok(match &**kind {774 ImportKind::Normal => in_frame(775 CallLocation::new(span),776 || format!("import {:?}", path.clone()),777 || s.import_resolved(resolved_path),778 )?,779 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),780 ImportKind::Bin => Val::arr(s.import_resolved_bin(resolved_path)?),781 }) as Result<Val>782 })?783 }784 })785}crates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -11,6 +11,7 @@
};
use educe::Educe;
+use im_rc::{Vector, vector};
use jrsonnet_gcmodule::{Acyclic, Cc, Trace, Weak, cc_dyn};
use jrsonnet_interner::IStr;
use jrsonnet_ir::Span;
@@ -235,10 +236,11 @@
CcObjectCore, ObjectCore,
pub fn new() {...}
);
+
#[derive(Trace, Educe)]
#[educe(Debug)]
struct ObjValueInner {
- cores: Vec<CcObjectCore>,
+ cores: Vector<CcObjectCore>,
assertions_ran: Cell<bool>,
has_assertions: bool,
value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,
@@ -266,7 +268,7 @@
thread_local! {
static EMPTY_OBJ: ObjValue = ObjValue(Cc::new(ObjValueInner {
- cores: vec![],
+ cores: vector![],
assertions_ran: Cell::new(true),
has_assertions: false,
value_cache: RefCell::default(),
@@ -428,7 +430,7 @@
bail!(NoSuperFound)
}
let mut out = ObjValue::builder();
- out.reserve_cores(1).extend_with_core(StandaloneSuperCore {
+ out.extend_with_core(StandaloneSuperCore {
sup: self.sup,
this: self.this.clone(),
});
@@ -484,9 +486,7 @@
#[must_use]
pub fn extend_from(&self, sup: Self) -> Self {
- let mut cores = Vec::with_capacity(sup.0.cores.len() + self.0.cores.len());
- cores.extend(sup.0.cores.iter().cloned());
- cores.extend(self.0.cores.iter().cloned());
+ let cores = sup.0.cores.clone() + self.0.cores.clone();
let has_assertions = sup.0.has_assertions || self.0.has_assertions;
ObjValue(Cc::new(ObjValueInner {
cores,
@@ -521,13 +521,27 @@
},
)
}
+
+ fn iter_cores(&self, idx: CoreIdx) -> impl Iterator<Item = &CcObjectCore> {
+ self.0.cores.iter().take(idx.idx).rev()
+ }
+ fn iter_cores_enumerate(&self, idx: CoreIdx) -> impl Iterator<Item = (CoreIdx, &CcObjectCore)> {
+ self.0
+ .cores
+ .iter()
+ .take(idx.idx)
+ .enumerate()
+ .rev()
+ .map(|(idx, o)| (CoreIdx { idx }, o))
+ }
+
fn enum_fields_idx(
&self,
super_depth: &mut SuperDepth,
handler: &mut EnumFieldsHandler<'_>,
idx: CoreIdx,
) -> bool {
- for core in self.0.cores[..idx.idx].iter().rev() {
+ for core in self.iter_cores(idx) {
if !core.0.enum_fields_core(super_depth, handler) {
return false;
}
@@ -546,7 +560,7 @@
}
fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {
let mut skip = Saturating(0usize);
- for ele in self.0.cores[..core.idx].iter().rev() {
+ for ele in self.iter_cores(core) {
match ele.0.has_field_include_hidden_core(name.clone()) {
HasFieldIncludeHidden::Exists => {
if skip.0 == 0 {
@@ -616,9 +630,9 @@
let mut first_add = None;
let mut add_stack: Vec<Val> = Vec::new();
let mut skip = Saturating(0);
- for (sup, core) in self.0.cores[..core.idx].iter().enumerate().rev() {
+ for (sup, core) in self.iter_cores_enumerate(core) {
let sup_this = SupThis {
- sup: CoreIdx { idx: sup },
+ sup,
this: self.clone(),
};
match core.0.get_for_core(key.clone(), sup_this, skip.0 != 0)? {
@@ -686,7 +700,7 @@
fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {
let mut exists = false;
let mut skip = Saturating(0usize);
- for ele in self.0.cores[..core.idx].iter().rev() {
+ for ele in self.iter_cores(core) {
let vis = ele.0.field_visibility_core(field.clone());
match vis {
FieldVisibility::Found(vis @ (Visibility::Unhide | Visibility::Hidden)) => {
crates/jrsonnet-evaluator/src/obj/oop.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj/oop.rs
+++ b/crates/jrsonnet-evaluator/src/obj/oop.rs
@@ -4,6 +4,7 @@
ops::ControlFlow,
};
+use im_rc::Vector;
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_ir::IStr;
use rustc_hash::{FxHashMap, FxHashSet};
@@ -117,7 +118,7 @@
#[allow(clippy::module_name_repetitions)]
pub struct ObjValueBuilder {
- sup: Vec<CcObjectCore>,
+ sup: Vector<CcObjectCore>,
has_assertions: bool,
new: OopObject,
@@ -129,7 +130,7 @@
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
- sup: vec![],
+ sup: Vector::new(),
has_assertions: false,
new: OopObject::new(FxHashMap::with_capacity(capacity), None),
next_field_index: FieldIndex::default(),
@@ -137,14 +138,10 @@
}
pub fn reserve_fields(&mut self, capacity: usize) {
self.new.this_entries.reserve(capacity);
- }
- pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {
- self.sup.reserve_exact(capacity);
- self
}
pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {
self.has_assertions |= super_obj.0.has_assertions;
- self.sup.clone_from(&super_obj.0.cores);
+ self.sup = super_obj.0.cores.clone();
self
}
@@ -181,19 +178,20 @@
pub fn extend_with_core(&mut self, core: impl ObjectCore) {
self.commit();
- self.sup.push(CcObjectCore::new(core));
+ self.sup.push_back(CcObjectCore::new(core));
}
fn commit(&mut self) {
if !self.new.is_empty() {
- self.sup.push(CcObjectCore::new(mem::take(&mut self.new)));
+ self.sup
+ .push_back(CcObjectCore::new(mem::take(&mut self.new)));
}
self.next_field_index = FieldIndex::default();
}
pub fn with_fields_omitted(&mut self, omit: FxHashSet<IStr>) {
self.commit();
- self.sup.push(CcObjectCore::new(OmitFieldsCore {
+ self.sup.push_back(CcObjectCore::new(OmitFieldsCore {
omit,
prev_layers: self.sup.len(),
}));