--- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,18 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" [[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -340,6 +352,26 @@ ] [[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -576,6 +608,7 @@ dependencies = [ "annotate-snippets", "anyhow", + "educe", "hi-doc", "jrsonnet-gcmodule", "jrsonnet-interner", --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -57,6 +57,7 @@ num-bigint = { workspace = true, features = ["serde"], optional = true } stacker = "0.1.23" +educe = { version = "0.6.0", default-features = false, features = ["Clone", "Debug", "Eq", "Hash", "PartialEq"] } [build-dependencies] rustversion = "1.0.22" --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -1,32 +1,27 @@ use std::fmt::Debug; +use educe::Educe; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; use rustc_hash::FxHashMap; use crate::{ error::ErrorKind::*, gc::WithCapacityExt as _, map::LayeredHashMap, ObjValue, Pending, Result, - Thunk, Val, + SupThis, Thunk, Val, }; +/// Context keeps information about current lexical code location +/// +/// This information includes local variables, top-level object (`$`), current object (`this`), and super object (`super`) +#[derive(Debug, Trace, Clone, Educe)] +#[educe(PartialEq)] +pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] Cc); -#[derive(Trace)] -struct ContextInternals { +#[derive(Debug, Trace)] +struct ContextInternal { dollar: Option, - sup: Option, - this: Option, + sup_this: Option, bindings: LayeredHashMap, -} -impl Debug for ContextInternals { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Context").finish() - } } - -/// Context keeps information about current lexical code location -/// -/// This information includes local variables, top-level object (`$`), current object (`this`), and super object (`super`) -#[derive(Debug, Clone, Trace)] -pub struct Context(Cc); impl Context { pub fn new_future() -> Pending { Pending::new() @@ -36,12 +31,35 @@ self.0.dollar.as_ref() } + pub fn try_dollar(&self) -> Result { + self.0 + .dollar + .clone() + .ok_or_else(|| CantUseSelfSupOutsideOfObject.into()) + } + pub fn this(&self) -> Option<&ObjValue> { - self.0.this.as_ref() + self.0.sup_this.as_ref().map(SupThis::this) + } + + pub fn try_this(&self) -> Result { + self.0 + .sup_this + .as_ref() + .ok_or_else(|| CantUseSelfSupOutsideOfObject.into()) + .map(SupThis::this) + .cloned() + } + + pub fn sup_this(&self) -> Option<&SupThis> { + self.0.sup_this.as_ref() } - pub fn super_obj(&self) -> Option<&ObjValue> { - self.0.sup.as_ref() + pub fn try_sup_this(&self) -> Result { + self.0 + .sup_this + .clone() + .ok_or_else(|| CantUseSelfSupOutsideOfObject.into()) } pub fn binding(&self, name: IStr) -> Result> { @@ -83,41 +101,52 @@ pub fn with_var(self, name: impl Into, value: Val) -> Self { let mut new_bindings = FxHashMap::with_capacity(1); new_bindings.insert(name.into(), Thunk::evaluated(value)); - self.extend(new_bindings, None, None, None) + self.extend_bindings(new_bindings) } #[must_use] - pub fn extend( + pub fn extend_bindings_sup_this( self, new_bindings: FxHashMap>, - new_dollar: Option, - new_sup: Option, - new_this: Option, + sup_this: SupThis, ) -> Self { - let ctx = &self.0; - let dollar = new_dollar.or_else(|| ctx.dollar.clone()); - let this = new_this.or_else(|| ctx.this.clone()); - let sup = new_sup.or_else(|| ctx.sup.clone()); + let ctx = &self; + let dollar = ctx + .0 + .dollar + .clone() + .or_else(|| Some(sup_this.this().clone())); let bindings = if new_bindings.is_empty() { - ctx.bindings.clone() + ctx.0.bindings.clone() } else { - ctx.bindings.clone().extend(new_bindings) + ctx.0.bindings.clone().extend(new_bindings) }; - Self(Cc::new(ContextInternals { + Self(Cc::new(ContextInternal { dollar, - sup, - this, + sup_this: Some(sup_this), bindings, })) } -} - -impl PartialEq for Context { - fn eq(&self, other: &Self) -> bool { - Cc::ptr_eq(&self.0, &other.0) + #[must_use] + pub fn extend_bindings(self, new_bindings: FxHashMap>) -> Self { + if new_bindings.is_empty() { + return self; + } + let ctx = &self; + let bindings = if new_bindings.is_empty() { + ctx.0.bindings.clone() + } else { + ctx.0.bindings.clone().extend(new_bindings) + }; + Self(Cc::new(ContextInternal { + dollar: ctx.0.dollar.clone(), + sup_this: ctx.0.sup_this.clone(), + bindings, + })) } } +#[derive(Default)] pub struct ContextBuilder { bindings: FxHashMap>, extend: Option, @@ -127,20 +156,25 @@ pub fn new() -> Self { Self::with_capacity(0) } + pub fn with_capacity(capacity: usize) -> Self { Self { bindings: FxHashMap::with_capacity(capacity), extend: None, } } + pub fn extend(parent: Context) -> Self { Self { bindings: FxHashMap::new(), extend: Some(parent), } } + /// # Panics - /// If `name` is already bound + /// + /// If `name` is already bound. Makes no sense to bind same local multiple times, + /// unless it is separate context layers. pub fn bind(&mut self, name: impl Into, value: Thunk) -> &mut Self { let old = self.bindings.insert(name.into(), value); assert!(old.is_none(), "variable bound twice in single context call"); @@ -148,14 +182,12 @@ } pub fn build(self) -> Context { if let Some(parent) = self.extend { - // TODO: replace self.extend with Result, and remove `state` field - parent.extend(self.bindings, None, None, None) + parent.extend_bindings(self.bindings) } else { - Context(Cc::new(ContextInternals { + Context(Cc::new(ContextInternal { bindings: LayeredHashMap::new(self.bindings), dollar: None, - sup: None, - this: None, + sup_this: None, })) } } --- a/crates/jrsonnet-evaluator/src/dynamic.rs +++ b/crates/jrsonnet-evaluator/src/dynamic.rs @@ -1,11 +1,13 @@ -use std::cell::OnceCell; +use std::ptr::addr_of; +use std::{cell::OnceCell, hash::Hasher}; +use educe::Educe; use jrsonnet_gcmodule::{Cc, Trace}; use crate::{bail, error::ErrorKind::InfiniteRecursionDetected, val::ThunkValue, Result}; -// TODO: Replace with OnceCell once in std -#[derive(Clone, Trace)] +#[derive(Trace, Educe)] +#[educe(Clone)] pub struct Pending(pub Cc>); impl Pending { pub fn new() -> Self { @@ -25,7 +27,7 @@ .expect("wrapper is filled already"); } } -impl Pending { +impl Pending { /// # Panics /// If wrapper is not yet filled pub fn unwrap(&self) -> T { @@ -52,3 +54,7 @@ Self::new() } } + +pub fn identity_hash(v: &Cc, hasher: &mut H) { + hasher.write_usize(addr_of!(**v) as usize); +} --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -119,10 +119,8 @@ #[error("binary operation {1} {0} {2} is not implemented")] BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType), - #[error("no top level object in this context")] - NoTopLevelObjectFound, - #[error("self is only usable inside objects")] - CantUseSelfOutsideOfObject, + #[error("self/super/$ are only usable inside objects")] + CantUseSelfSupOutsideOfObject, #[error("no super found")] NoSuperFound, --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -11,18 +11,7 @@ use self::destructure::destruct; use crate::{ - arr::ArrValue, - bail, - destructure::evaluate_dest, - error::{suggest_object_fields, ErrorKind::*}, - evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, - function::{CallLocation, FuncDesc, FuncVal}, - gc::WithCapacityExt as _, - in_frame, - typed::Typed, - val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, - Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, - Unbound, Val, + Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, SupThis, Unbound, Val, arr::ArrValue, bail, destructure::evaluate_dest, error::{ErrorKind::*, suggest_object_fields}, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal}, gc::WithCapacityExt as _, in_frame, typed::Typed, val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, with_state }; pub mod destructure; pub mod operator; @@ -126,10 +115,7 @@ let fctx = Pending::new(); let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint()); destruct(var, item, fctx.clone(), &mut new_bindings)?; - let ctx = ctx - .clone() - .extend(new_bindings, None, None, None) - .into_future(fctx); + let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx); evaluate_comp(ctx, &specs[1..], callback)?; } @@ -169,18 +155,18 @@ impl CloneableUnbound for V where V: Unbound + Clone {} fn evaluate_object_locals( - fctx: Pending, + fctx: Context, locals: Rc>, ) -> impl CloneableUnbound { #[derive(Trace, Clone)] struct UnboundLocals { - fctx: Pending, + fctx: Context, locals: Rc>, } impl Unbound for UnboundLocals { type Bound = Context; - fn bind(&self, sup: Option, this: Option) -> Result { + fn bind(&self, sup_this: SupThis) -> Result { let fctx = Context::new_future(); let mut new_bindings = FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum()); @@ -188,11 +174,10 @@ evaluate_dest(b, fctx.clone(), &mut new_bindings)?; } - let ctx = self.fctx.unwrap(); - let new_dollar = ctx.dollar().cloned().or_else(|| this.clone()); + let ctx = self.fctx.clone(); let ctx = ctx - .extend(new_bindings, new_dollar, sup, this) + .extend_bindings_sup_this(new_bindings, sup_this) .into_future(fctx); Ok(ctx) @@ -229,8 +214,8 @@ } impl> Unbound for UnboundValue { type Bound = Val; - fn bind(&self, sup: Option, this: Option) -> Result { - evaluate_named(self.uctx.bind(sup, this)?, &self.value, self.name.clone()) + fn bind(&self, sup_this: SupThis) -> Result { + evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone()) } } @@ -260,9 +245,9 @@ } impl> Unbound for UnboundMethod { type Bound = Val; - fn bind(&self, sup: Option, this: Option) -> Result { + fn bind(&self, sup_this: SupThis) -> Result { Ok(evaluate_method( - self.uctx.bind(sup, this)?, + self.uctx.bind(sup_this)?, self.name.clone(), self.params.clone(), self.value.clone(), @@ -298,10 +283,8 @@ .collect::>(), ); - let fctx = Context::new_future(); - // We have single context for all fields, so we can cache binds - let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals)); + let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals)); for member in members { match member { @@ -315,8 +298,8 @@ assert: AssertStmt, } impl> ObjectAssertion for ObjectAssert { - fn run(&self, sup: Option, this: Option) -> Result<()> { - let ctx = self.uctx.bind(sup, this)?; + fn run(&self, sup_this: SupThis) -> Result<()> { + let ctx = self.uctx.bind(sup_this)?; evaluate_assert(ctx, &self.assert) } } @@ -330,9 +313,7 @@ } } } - let this = builder.build(); - fctx.fill(ctx.extend(FxHashMap::new(), None, None, Some(this.clone()))); - Ok(this) + Ok(builder.build()) } pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result { @@ -347,22 +328,13 @@ .cloned() .collect::>(), ); - let mut ctxs = vec![]; evaluate_comp(ctx, &obj.compspecs, &mut |ctx| { - let fctx = Context::new_future(); - ctxs.push((ctx.clone(), fctx.clone())); - let uctx = evaluate_object_locals(fctx, locals.clone()); + let uctx = evaluate_object_locals(ctx.clone(), locals.clone()); evaluate_field_member(&mut builder, ctx, uctx, &obj.field) })?; - let this = builder.build(); - for (ctx, fctx) in ctxs { - let _ctx = ctx - .extend(FxHashMap::new(), None, None, Some(this.clone())) - .into_future(fctx); - } - this + builder.build() } }) } @@ -428,19 +400,9 @@ } let loc = expr.span(); Ok(match expr.expr() { - Literal(LiteralType::This) => { - Val::Obj(ctx.this().ok_or(CantUseSelfOutsideOfObject)?.clone()) - } - Literal(LiteralType::Super) => Val::Obj( - ctx.super_obj().ok_or(NoSuperFound)?.with_this( - ctx.this() - .expect("if super exists - then this should too") - .clone(), - ), - ), - Literal(LiteralType::Dollar) => { - Val::Obj(ctx.dollar().ok_or(NoTopLevelObjectFound)?.clone()) - } + Literal(LiteralType::This) => Val::Obj(ctx.try_this()?), + Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?), + Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?), Literal(LiteralType::True) => Val::Bool(true), Literal(LiteralType::False) => Val::Bool(false), Literal(LiteralType::Null) => Val::Null, @@ -452,15 +414,18 @@ // // Note that other jsonnet implementations will fail on `if value in (super)` expression, // because the standalone super literal is not supported, that is because in other - // implementations `in super` treated differently from in `smth_else`. + // implementations `in super` treated differently from `in smth_else`. BinaryOp(field, BinaryOpType::In, e) if matches!(e.expr(), Expr::Literal(LiteralType::Super)) => { - let Some(super_obj) = ctx.super_obj() else { + let sup_this = ctx.try_sup_this()?; + // In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence. + // In jrsonnet, however, this wasn't true, this was kept here for compatibility. + if !sup_this.has_super() { return Ok(Val::Bool(false)); - }; - let field = evaluate(ctx.clone(), field)?; - Val::Bool(super_obj.has_field_ex(field.to_string()?, true)) + } + let field = evaluate(ctx, field)?; + Val::Bool(sup_this.field_in_super(field.to_string()?)) } BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?, @@ -473,13 +438,16 @@ let mut parts = parts.iter(); let mut indexable = if matches!(indexable.expr(), Expr::Literal(LiteralType::Super)) { let part = parts.next().expect("at least part should exist"); - let Some(super_obj) = ctx.super_obj() else { + // sup_this existence check might also be skipped here for null-coalesce... + // But I believe this might cause errors. + let sup_this = ctx.try_sup_this()?; + if !sup_this.has_super() { #[cfg(feature = "exp-null-coaelse")] if part.null_coaelse { return Ok(Val::Null); } bail!(NoSuperFound) - }; + } let name = evaluate(ctx.clone(), &part.value)?; let Val::Str(name) = name else { @@ -490,19 +458,19 @@ )) }; - 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()) + match sup_this + .get_super(name.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()); + let suggestions = suggest_object_fields( + &sup_this.standalone_super().expect("super exists"), + name.clone(), + ); bail!(NoSuchField(name, suggestions)) } @@ -589,7 +557,7 @@ for b in bindings { evaluate_dest(b, fctx.clone(), &mut new_bindings)?; } - let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx); + let ctx = ctx.extend_bindings(new_bindings).into_future(fctx); evaluate(ctx, &returned.clone())? } Arr(items) => { @@ -652,7 +620,7 @@ Slice(value, desc) => { fn parse_idx( loc: CallLocation<'_>, - ctx: &Context, + ctx: Context, expr: Option<&LocExpr>, desc: &'static str, ) -> Result> { @@ -660,7 +628,7 @@ Ok(in_frame( loc, || format!("slice {desc}"), - || >::from_untyped(evaluate(ctx.clone(), value)?), + || >::from_untyped(evaluate(ctx, value)?), )?) } else { Ok(None) @@ -670,9 +638,9 @@ let indexable = evaluate(ctx.clone(), value)?; let loc = CallLocation::new(&loc); - let start = parse_idx(loc, &ctx, desc.start.as_ref(), "start")?; - let end = parse_idx(loc, &ctx, desc.end.as_ref(), "end")?; - let step = parse_idx(loc, &ctx, desc.step.as_ref(), "step")?; + let start = parse_idx(loc, ctx.clone(), desc.start.as_ref(), "start")?; + let end = parse_idx(loc, ctx.clone(), desc.end.as_ref(), "end")?; + let step = parse_idx(loc, ctx, desc.step.as_ref(), "step")?; IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)? } @@ -681,18 +649,21 @@ bail!("computed imports are not supported") }; let tmp = loc.clone().0; - let s = ctx.state(); - let resolved_path = s.resolve_from(tmp.source_path(), path)?; - match i { - Import(_) => in_frame( - CallLocation::new(&loc), - || format!("import {:?}", path.clone()), - || s.import_resolved(resolved_path), - )?, - ImportStr(_) => Val::string(s.import_resolved_str(resolved_path)?), - ImportBin(_) => Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?)), - _ => unreachable!(), - } + with_state(|s| { + let resolved_path = s.resolve_from(tmp.source_path(), path)?; + Ok(match i { + Import(_) => in_frame( + CallLocation::new(&loc), + || format!("import {:?}", path.clone()), + || s.import_resolved(resolved_path), + )?, + ImportStr(_) => Val::string(s.import_resolved_str(resolved_path)?), + ImportBin(_) => { + Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?)) + } + _ => unreachable!(), + }) as Result + })? } }) } --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -40,7 +40,7 @@ } /// Represents Jsonnet function defined in code. -#[derive(Debug, PartialEq, Trace)] +#[derive(Debug, Trace, PartialEq)] pub struct FuncDesc { /// # Example /// --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -129,11 +129,11 @@ } Ok(body_ctx - .extend(passed_args, None, None, None) - .extend(defaults, None, None, None) + .extend_bindings(passed_args) + .extend_bindings(defaults) .into_future(fctx)) } else { - let body_ctx = body_ctx.extend(passed_args, None, None, None); + let body_ctx = body_ctx.extend_bindings(passed_args); Ok(body_ctx) } } @@ -257,7 +257,5 @@ } } - Ok(body_ctx - .extend(bindings, None, None, None) - .into_future(fctx)) + Ok(body_ctx.extend_bindings(bindings).into_future(fctx)) } --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -28,8 +28,10 @@ any::Any, cell::{RefCell, RefMut}, collections::hash_map::Entry, + clone::Clone, fmt::{self, Debug}, rc::Rc, + marker::PhantomData, }; pub use ctx::*; @@ -65,7 +67,7 @@ /// Type of value after object context is bound type Bound; /// Create value bound to specified object context - fn bind(&self, sup: Option, this: Option) -> Result; + fn bind(&self, sup_this: SupThis) -> Result; } /// Object fields may, or may not depend on `this`/`super`, this enum allows cheaper reuse of object-independent fields for native code @@ -85,9 +87,9 @@ } impl MaybeUnbound { /// Attach object context to value, if required - pub fn evaluate(&self, sup: Option, this: Option) -> Result { + pub fn evaluate(&self, sup_this: SupThis) -> Result { match self { - Self::Unbound(v) => v.0.bind(sup, this), + Self::Unbound(v) => v.0.bind(sup_this), Self::Bound(v) => Ok(v.evaluate()?), } } @@ -105,8 +107,8 @@ /// Initialize default file context. /// Has default implementation, which calls `populate`. /// Prefer to always implement `populate` instead. - fn initialize(&self, state: State, for_file: Source) -> Context { - let mut builder = ContextBuilder::with_capacity(state, self.reserve_vars()); + fn initialize(&self, for_file: Source) -> Context { + let mut builder = ContextBuilder::with_capacity(self.reserve_vars()); self.populate(for_file, &mut builder); builder.build() } @@ -130,11 +132,11 @@ where T: ContextInitializer, { - fn initialize(&self, state: State, for_file: Source) -> Context { + fn initialize(&self, for_file: Source) -> Context { if let Some(ctx) = self { - ctx.initialize(state, for_file) + ctx.initialize(for_file) } else { - ().initialize(state, for_file) + ().initialize(for_file) } } @@ -237,7 +239,40 @@ #[derive(Clone, Trace)] pub struct State(Cc); +thread_local! { + pub static DEFAULT_STATE: State = State::builder().build(); + pub static STATE: RefCell> = const {RefCell::new(None)}; +} +pub struct StateEnterGuard(PhantomData<()>); +impl Drop for StateEnterGuard { + fn drop(&mut self) { + STATE.with_borrow_mut(|v| *v = None); + } +} + +pub fn with_state(v: impl FnOnce(State) -> V) -> V { + if let Some(state) = STATE.with_borrow(Clone::clone) { + v(state) + } else { + let s = DEFAULT_STATE.with(Clone::clone); + v(s) + } +} + impl State { + pub fn enter(&self) -> StateEnterGuard { + self.try_enter().expect("entered state already exists") + } + pub fn try_enter(&self) -> Option { + STATE.with_borrow_mut(|v| { + if v.is_none() { + *v = Some(self.clone()); + Some(StateEnterGuard(PhantomData)) + } else { + None + } + }) + } /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise pub fn import_resolved_str(&self, path: SourcePath) -> Result { let mut file_cache = self.file_cache(); @@ -359,7 +394,7 @@ /// Creates context with all passed global variables pub fn create_default_context(&self, source: Source) -> Context { - self.context_initializer().initialize(self.clone(), source) + self.context_initializer().initialize(source) } /// Creates context with all passed global variables, calling custom modifier @@ -370,7 +405,6 @@ ) -> Context { let default_initializer = self.context_initializer(); let mut builder = ContextBuilder::with_capacity( - self.clone(), default_initializer.reserve_vars() + context_initializer.reserve_vars(), ); default_initializer.populate(source.clone(), &mut builder); --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -4,14 +4,14 @@ use crate::{gc::WithCapacityExt as _, Thunk, Val}; -#[derive(Trace)] +#[derive(Trace, Debug)] #[trace(tracking(force))] pub struct LayeredHashMapInternals { parent: Option, current: FxHashMap>, } -#[derive(Trace)] +#[derive(Trace, Debug)] pub struct LayeredHashMap(Cc); impl LayeredHashMap { --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -1,12 +1,9 @@ use std::{ - any::Any, - cell::RefCell, - fmt::Debug, - hash::{Hash, Hasher}, - ptr::addr_of, + any::Any, cell::{Cell, RefCell}, collections::hash_map::Entry, fmt::{self, Debug}, hash::{Hash, Hasher} }; use jrsonnet_gcmodule::{cc_dyn, Cc, Trace, Weak}; +use educe::Educe; use jrsonnet_interner::IStr; use jrsonnet_parser::{Span, Visibility}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -14,10 +11,11 @@ use crate::{ arr::{PickObjectKeyValues, PickObjectValues}, bail, - error::{suggest_object_fields, Error, ErrorKind::*}, + error::{suggest_object_fields, ErrorKind::*}, function::{CallLocation, FuncVal}, gc::WithCapacityExt as _, in_frame, + identity_hash, operator::evaluate_add_op, val::ArrValue, CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val, @@ -43,12 +41,10 @@ #[derive(Clone, Copy, Default, Debug, Trace)] pub struct SuperDepth(()); impl SuperDepth { - pub const fn deeper(self) -> Self { - Self(()) - } + pub(super) fn deepen(self) {} } - #[derive(Clone, Copy)] + #[derive(Clone, Copy, Debug)] pub struct FieldSortKey(()); impl FieldSortKey { pub const fn new(_: SuperDepth, _: FieldIndex) -> Self { @@ -74,8 +70,8 @@ #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct SuperDepth(u32); impl SuperDepth { - pub fn deeper(self) -> Self { - Self(self.0 + 1) + pub(super) fn deepen(&mut self) { + *self.0 += 1 } } @@ -120,7 +116,7 @@ } } impl Debug for ObjFieldFlags { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ObjFieldFlags") .field("add", &self.add()) .field("visibility", &self.visibility()) @@ -140,34 +136,29 @@ cc_dyn!(CcObjectAssertion, ObjectAssertion); pub trait ObjectAssertion: Trace { - fn run(&self, super_obj: Option, this: Option) -> Result<()>; + fn run(&self, sup_this: SupThis) -> Result<()>; } // Field => This -#[derive(Trace)] +#[derive(Trace, Debug)] enum CacheValue { - Cached(Val), - NotFound, + Cached(Result>), Pending, - Errored(Error), } #[allow(clippy::module_name_repetitions)] #[derive(Trace)] #[trace(tracking(force))] pub struct OopObject { - sup: Option, // this: Option, assertions: Cc>, - assertions_ran: RefCell>, this_entries: Cc>, value_cache: RefCell), CacheValue>>, } impl Debug for OopObject { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("OopObject") - .field("sup", &self.sup) // .field("assertions", &self.assertions) // .field("assertions_ran", &self.assertions_ran) .field("this_entries", &self.this_entries) @@ -178,32 +169,36 @@ type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a; -pub trait ObjectLike: Trace + Any + Debug { - fn extend_from(&self, sup: ObjValue) -> ObjValue; - /// When using standalone super in object, `this.super_obj.with_this(this)` is executed - fn with_this(&self, me: ObjValue, this: ObjValue) -> ObjValue { - ObjValue::new(ThisOverride { inner: me, this }) - } - fn this(&self) -> Option { - None - } - fn len(&self) -> usize; - fn is_empty(&self) -> bool; - // If callback returns false, iteration stops - fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool; +#[derive(Trace, Clone)] +pub enum ValueProcess { + None, + SuperPlus, +} + +pub trait ObjectCore: Trace + Any + Debug { + // If callback returns false, iteration stops, and this call returns false. + fn enum_fields_core( + &self, + super_depth: &mut SuperDepth, + handler: &mut EnumFieldsHandler<'_>, + ) -> bool; fn has_field_include_hidden(&self, name: IStr) -> bool; - fn has_field(&self, name: IStr) -> bool; - fn get_for(&self, key: IStr, this: ObjValue) -> Result>; - fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result>; + fn get_for(&self, key: IStr, sup_this: SupThis) -> Result>; + // fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result>; fn field_visibility(&self, field: IStr) -> Option; - fn run_assertions_raw(&self, this: ObjValue) -> Result<()>; + fn run_assertions_raw(&self, sup_this: SupThis) -> Result<()>; } #[derive(Clone, Trace)] -pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak); +pub struct WeakObjValue(#[trace(skip)] Weak); +impl Debug for WeakObjValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("WeakObjValue").finish() + } +} impl PartialEq for WeakObjValue { fn eq(&self, other: &Self) -> bool { @@ -222,50 +217,97 @@ cc_dyn!( #[derive(Clone, Debug)] - ObjValue, ObjectLike, + ObjCore, ObjectCore, pub fn new() {...} ); +#[derive(Trace, Educe)] +#[educe(Debug)] +struct ObjValueInner { + cores: Vec, + assertions_ran: Cell, + value_cache: RefCell>, +} -#[derive(Debug, Trace)] -struct EmptyObject; -impl ObjectLike for EmptyObject { - fn extend_from(&self, sup: ObjValue) -> ObjValue { - // obj + {} == obj - sup +thread_local! { + static RUNNING_ASSERTIONS: RefCell> = RefCell::default(); +} +fn is_asserting(obj: &ObjValue) -> bool { + RUNNING_ASSERTIONS.with_borrow(|v| v.contains(obj)) +} +/// Returns false if already asserting +fn start_asserting(obj: &ObjValue) -> bool { + RUNNING_ASSERTIONS.with_borrow_mut(|v| v.insert(obj.clone())) +} +fn finish_asserting(obj: &ObjValue) { + RUNNING_ASSERTIONS.with_borrow_mut(|v| { + let r = v.remove(obj); + debug_assert!( + r, + "finish_asserting was called before start_asserting or twice" + ); + }); +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Clone, Trace, Debug, Educe)] +#[educe(PartialEq, Hash, Eq)] +pub struct ObjValue( + #[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc, +); + +#[derive(Trace, Debug)] +struct StandaloneSuperCore { + sup: CoreIdx, + this: ObjValue, +} +impl ObjectCore for StandaloneSuperCore { + fn enum_fields_core( + &self, + super_depth: &mut SuperDepth, + handler: &mut EnumFieldsHandler<'_>, + ) -> bool { + self.this + .enum_fields_internal(super_depth, handler, self.sup) } - fn this(&self) -> Option { - None + fn has_field_include_hidden(&self, name: IStr) -> bool { + self.this.has_field_include_hidden_idx(name, self.sup) } - fn len(&self) -> usize { - 0 + fn get_for(&self, key: IStr, _sup_this: SupThis) -> Result> { + let v = self.this.get_idx(key, self.sup)?; + Ok(v.map(|v| (v, ValueProcess::None))) } - fn is_empty(&self) -> bool { - true + fn field_visibility(&self, field: IStr) -> Option { + self.this.field_visibility_idx(field, self.sup) } - fn enum_fields(&self, _depth: SuperDepth, _handler: &mut EnumFieldsHandler<'_>) -> bool { - false + fn run_assertions_raw(&self, _sup_this: SupThis) -> Result<()> { + self.this.run_assertions() } +} - fn has_field_include_hidden(&self, _name: IStr) -> bool { - false +#[derive(Debug, Trace)] +struct EmptyObject; +impl ObjectCore for EmptyObject { + fn enum_fields_core( + &self, + _super_depth: &mut SuperDepth, + _handler: &mut EnumFieldsHandler<'_>, + ) -> bool { + true } - fn has_field(&self, _name: IStr) -> bool { + fn has_field_include_hidden(&self, _name: IStr) -> bool { false } - fn get_for(&self, _key: IStr, _this: ObjValue) -> Result> { - Ok(None) - } - fn get_for_uncached(&self, _key: IStr, _this: ObjValue) -> Result> { + fn get_for(&self, _key: IStr, _sup_this: SupThis) -> Result> { Ok(None) } - fn run_assertions_raw(&self, _this: ObjValue) -> Result<()> { + fn run_assertions_raw(&self, _sup_this: SupThis) -> Result<()> { Ok(()) } @@ -274,65 +316,79 @@ } } -#[derive(Trace, Debug)] -struct ThisOverride { - inner: ObjValue, - this: ObjValue, +#[derive(Hash, PartialEq, Eq, Trace, Clone, Copy, Debug)] +struct CoreIdx { + idx: usize, } -impl ObjectLike for ThisOverride { - fn with_this(&self, _me: ObjValue, this: ObjValue) -> ObjValue { - ObjValue::new(Self { - inner: self.inner.clone(), - this, - }) +impl CoreIdx { + fn super_exists(self) -> bool { + self.idx != 0 } - - fn extend_from(&self, sup: ObjValue) -> ObjValue { - self.inner.extend_from(sup).with_this(self.this.clone()) +} +#[derive(Trace, Clone, PartialEq, Eq, Hash, Debug)] +pub struct SupThis { + sup: CoreIdx, + this: ObjValue, +} +impl SupThis { + pub fn has_super(&self) -> bool { + self.sup.super_exists() } - - fn this(&self) -> Option { - Some(self.this.clone()) + /// Implementation of `"field" in super` operation, + /// works faster than standalone super path. + /// + /// In case of no `super` existence, returns false. + pub fn field_in_super(&self, field: IStr) -> bool { + self.this.has_field_include_hidden_idx(field, self.sup) } - - fn len(&self) -> usize { - self.inner.len() + /// Implementation of `super.field` operation, + /// works faster than standalone super path. + /// + /// In case of no `super` existence, returns `NoSuperFound` + pub fn get_super(&self, field: IStr) -> Result> { + if !self.sup.super_exists() { + bail!(NoSuperFound); + } + self.this.get_idx(field, self.sup) } - - fn is_empty(&self) -> bool { - self.inner.is_empty() + /// `super` with `self` overriden for top-level lookups. + /// Exists when super appears outside of `super.field`/`"field" in super` expressions + /// Exclusive to jrsonnet. + /// + /// Might return `NoSuperFound` error. + pub fn standalone_super(&self) -> Result { + if !self.sup.super_exists() { + bail!(NoSuperFound) + } + Ok(ObjValue::new(StandaloneSuperCore { + sup: self.sup, + this: self.this.clone(), + })) } - - fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool { - self.inner.enum_fields(depth, handler) + pub fn this(&self) -> &ObjValue { + &self.this } - - fn has_field_include_hidden(&self, name: IStr) -> bool { - self.inner.has_field_include_hidden(name) - } - - fn has_field(&self, name: IStr) -> bool { - self.inner.has_field(name) + pub fn downgrade(self) -> WeakSupThis { + WeakSupThis { + sup: self.sup, + this: self.this.downgrade(), + } } - - fn get_for(&self, key: IStr, this: ObjValue) -> Result> { - self.inner.get_for(key, this) - } - - fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result> { - self.inner.get_raw(key, this) - } - - fn field_visibility(&self, field: IStr) -> Option { - self.inner.field_visibility(field) - } - - fn run_assertions_raw(&self, this: ObjValue) -> Result<()> { - self.inner.run_assertions_raw(this) - } } +#[derive(Trace, PartialEq, Eq, Hash, Debug)] +pub struct WeakSupThis { + sup: CoreIdx, + this: WeakObjValue, +} impl ObjValue { + pub fn new(v: impl ObjectCore) -> Self { + Self(Cc::new(ObjValueInner { + cores: vec![ObjCore::new(v)], + assertions_ran: Cell::new(false), + value_cache: RefCell::new(FxHashMap::new()), + })) + } pub fn new_empty() -> Self { Self::new(EmptyObject) } @@ -363,27 +419,77 @@ #[must_use] pub fn extend_from(&self, sup: Self) -> Self { - self.0.extend_from(sup) + let mut cores = sup.0.cores.clone(); + cores.extend(self.0.cores.iter().cloned()); + ObjValue(Cc::new(ObjValueInner { + cores, + value_cache: RefCell::default(), + assertions_ran: Cell::new(false), + })) } - #[must_use] - pub fn with_this(&self, this: Self) -> Self { - self.0.with_this(self.clone(), this) - } + // #[must_use] + // pub fn with_this(&self, this: Self) -> Self { + // self.0.with_this(self.clone(), this) + // } + /// Returns amount of visible object fields + /// If object only contains hidden fields - may return zero. pub fn len(&self) -> usize { - self.0.len() + self.fields_visibility() + .iter() + .filter(|(_, (visible, _))| *visible) + .count() } pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.len() == 0 + } + /// For each field, calls callback. + /// If callback returns false - ends iteration prematurely. + /// + /// Returns false if ended prematurely + pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool { + let mut super_depth = SuperDepth::default(); + self.enum_fields_internal( + &mut super_depth, + handler, + CoreIdx { + idx: self.0.cores.len(), + }, + ) } - pub fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool { - self.0.enum_fields(depth, handler) + fn enum_fields_internal( + &self, + super_depth: &mut SuperDepth, + handler: &mut EnumFieldsHandler<'_>, + idx: CoreIdx, + ) -> bool { + for core in self.0.cores[..idx.idx].iter() { + if !core.0.enum_fields_core(super_depth, handler) { + return false; + } + super_depth.deepen(); + } + true } pub fn has_field_include_hidden(&self, name: IStr) -> bool { - self.0.has_field_include_hidden(name) + self.has_field_include_hidden_idx( + name, + CoreIdx { + idx: self.0.cores.len(), + }, + ) + } + fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool { + self.0.cores[..core.idx] + .iter() + .rev() + .any(|v| v.0.has_field_include_hidden(name.clone())) } pub fn has_field(&self, name: IStr) -> bool { - self.0.has_field(name) + match self.field_visibility(name) { + Some(Visibility::Unhide | Visibility::Normal) => true, + Some(Visibility::Hidden) | None => false, + } } pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool { if include_hidden { @@ -392,14 +498,78 @@ self.has_field(name) } } + pub fn get(&self, key: IStr) -> Result> { + self.get_idx( + key, + CoreIdx { + idx: self.0.cores.len(), + }, + ) + } - pub fn get(&self, key: IStr) -> Result> { - self.run_assertions()?; - self.get_for(key, self.0.this().unwrap_or_else(|| self.clone())) + fn get_idx(&self, key: IStr, core: CoreIdx) -> Result> { + let cache_key = (key.clone(), core); + { + let mut cache = self.0.value_cache.borrow_mut(); + // entry_ref candidate? + match cache.entry(cache_key.clone()) { + Entry::Occupied(v) => match v.get() { + CacheValue::Cached(v) => return v.clone(), + CacheValue::Pending => { + if !is_asserting(self) { + bail!(InfiniteRecursionDetected); + } + } + }, + Entry::Vacant(v) => { + v.insert(CacheValue::Pending); + } + }; + } + let result = self.get_idx_uncached(key, core); + { + let mut cache = self.0.value_cache.borrow_mut(); + cache.insert(cache_key, CacheValue::Cached(result.clone())); + } + result } + fn get_idx_uncached(&self, key: IStr, core: CoreIdx) -> Result> { + self.run_assertions()?; + let mut add_stack = Vec::with_capacity(2); + for (sup, core) in self.0.cores[..core.idx].iter().enumerate().rev() { + let sup_this = SupThis { + sup: CoreIdx { idx: sup }, + this: self.clone(), + }; + if let Some((val, proc)) = core.0.get_for(key.clone(), sup_this)? { + match proc { + ValueProcess::None if add_stack.is_empty() => return Ok(Some(val)), + ValueProcess::None => { + add_stack.push(val); + break; + } + ValueProcess::SuperPlus => { + add_stack.push(val); + } + } + } + } + if add_stack.is_empty() { + // None of layers had this field + return Ok(None); + } else if add_stack.len() == 1 { + // A layer had this field, but it wanted this field to be added with super. + // However, no super had this field, fail-safe + return Ok(Some(add_stack.pop().expect("single element on stack"))); + } + let mut values = add_stack.into_iter().rev(); + let init = values.next().expect("at least 2 elements"); - pub fn get_for(&self, key: IStr, this: Self) -> Result> { - self.0.get_for(key, this) + values + .try_fold(init, |a, b| evaluate_add_op(&a, &b)) + .map(Some) + + // self.0.get_raw(key, this) } pub fn get_or_bail(&self, key: IStr) -> Result { @@ -408,22 +578,48 @@ bail!(NoSuchField(key, suggestions)) }; Ok(value) - } - - fn get_raw(&self, key: IStr, this: Self) -> Result> { - self.0.get_for_uncached(key, this) } fn field_visibility(&self, field: IStr) -> Option { - self.0.field_visibility(field) + self.field_visibility_idx( + field, + CoreIdx { + idx: self.0.cores.len(), + }, + ) } + fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option { + let mut exists = false; + for ele in self.0.cores[..core.idx].iter().rev() { + let vis = ele.0.field_visibility(field.clone()); + match vis { + Some(Visibility::Unhide | Visibility::Hidden) => return vis, + Some(Visibility::Normal) => exists = true, + None => {} + } + } + exists.then_some(Visibility::Normal) + } pub fn run_assertions(&self) -> Result<()> { - // FIXME: Should it use `self.0.this()` in case of standalone super? - self.run_assertions_raw(self.clone()) - } - fn run_assertions_raw(&self, this: Self) -> Result<()> { - self.0.run_assertions_raw(this) + if self.0.assertions_ran.get() { + return Ok(()); + } + if !start_asserting(self) { + return Ok(()); + } + for (idx, ele) in self.0.cores.iter().enumerate() { + let sup_this = SupThis { + sup: CoreIdx { idx }, + this: self.clone(), + }; + ele.0.run_assertions_raw(sup_this).inspect_err(|_e| { + finish_asserting(self); + })?; + } + finish_asserting(self); + self.0.assertions_ran.set(true); + Ok(()) } pub fn iter( @@ -462,24 +658,22 @@ } fn fields_visibility(&self) -> FxHashMap { let mut out = FxHashMap::default(); - self.enum_fields( - SuperDepth::default(), - &mut |depth, index, name, visibility| { - let new_sort_key = FieldSortKey::new(depth, index); - let entry = out.entry(name); - let (visible, _) = entry.or_insert((true, new_sort_key)); - match visibility { - Visibility::Normal => {} - Visibility::Hidden => { - *visible = false; - } - Visibility::Unhide => { - *visible = true; - } - }; - false - }, - ); + self.enum_fields(&mut |depth, index, name, visibility| { + dbg!(&name, visibility); + let new_sort_key = FieldSortKey::new(depth, index); + let entry = out.entry(name); + let (visible, _) = entry.or_insert((true, new_sort_key)); + match visibility { + Visibility::Normal => {} + Visibility::Hidden => { + *visible = false; + } + Visibility::Unhide => { + *visible = true; + } + }; + false + }); out } pub fn fields_ex( @@ -515,8 +709,8 @@ return fields; } - let mut fields: Vec<_> = self - .fields_visibility() + let mut fields: Vec<_> = dbg!(self + .fields_visibility()) .into_iter() .filter(|(_, (visible, _))| include_hidden || *visible) .map(|(k, _)| k) @@ -580,210 +774,65 @@ impl OopObject { pub fn new( - sup: Option, this_entries: Cc>, assertions: Cc>, ) -> Self { Self { - sup, - // this: None, - assertions, - assertions_ran: RefCell::new(FxHashSet::new()), this_entries, value_cache: RefCell::new(FxHashMap::new()), + assertions, } } - - fn evaluate_this(&self, v: &ObjMember, real_this: ObjValue) -> Result { - v.invoke.evaluate(self.sup.clone(), Some(real_this)) - } - - // FIXME: Duplication between ObjValue and OopObject - fn fields_visibility(&self) -> FxHashMap { - let mut out = FxHashMap::default(); - self.enum_fields( - SuperDepth::default(), - &mut |depth, index, name, visibility| { - let new_sort_key = FieldSortKey::new(depth, index); - let entry = out.entry(name); - let (visible, _) = entry.or_insert((true, new_sort_key)); - match visibility { - Visibility::Normal => {} - Visibility::Hidden => { - *visible = false; - } - Visibility::Unhide => { - *visible = true; - } - }; - false - }, - ); - out - } } -impl ObjectLike for OopObject { - fn extend_from(&self, sup: ObjValue) -> ObjValue { - ObjValue::new(match &self.sup { - None => Self::new( - Some(sup), - self.this_entries.clone(), - self.assertions.clone(), - ), - Some(v) => Self::new( - Some(v.extend_from(sup)), - self.this_entries.clone(), - self.assertions.clone(), - ), - }) - } - - fn len(&self) -> usize { - // Maybe it will be better to not compute sort key here? - self.fields_visibility() - .into_iter() - .filter(|(_, (visible, _))| *visible) - .count() - } - - /// Returns false only if there is any visible entry. - /// - /// Note that object with hidden fields `{a:: 1}` will be reported as empty here. - fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Run callback for every field found in object - /// - /// Returns true if ended prematurely - fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool { - if let Some(s) = &self.sup { - if s.enum_fields(depth.deeper(), handler) { - return true; - } - } +impl ObjectCore for OopObject { + fn enum_fields_core( + &self, + super_depth: &mut SuperDepth, + handler: &mut EnumFieldsHandler<'_>, + ) -> bool { for (name, member) in self.this_entries.iter() { if handler( - depth, + *super_depth, member.original_index, name.clone(), member.flags.visibility(), ) { - return true; + return false; } } - false + true } fn has_field_include_hidden(&self, name: IStr) -> bool { - if self.this_entries.contains_key(&name) { - true - } else if let Some(super_obj) = &self.sup { - super_obj.has_field_include_hidden(name) - } else { - false - } + self.this_entries.contains_key(&name) } - fn has_field(&self, name: IStr) -> bool { - self.field_visibility(name) - .map_or(false, |v| v.is_visible()) - } - fn get_for(&self, key: IStr, this: ObjValue) -> Result> { - let cache_key = (key.clone(), Some(this.clone().downgrade())); - if let Some(v) = self.value_cache.borrow().get(&cache_key) { - return Ok(match v { - CacheValue::Cached(v) => Some(v.clone()), - CacheValue::NotFound => None, - CacheValue::Pending => bail!(InfiniteRecursionDetected), - CacheValue::Errored(e) => return Err(e.clone()), - }); - } - self.value_cache - .borrow_mut() - .insert(cache_key.clone(), CacheValue::Pending); - let value = self.get_for_uncached(key, this).inspect_err(|e| { - self.value_cache - .borrow_mut() - .insert(cache_key.clone(), CacheValue::Errored(e.clone())); - })?; - self.value_cache.borrow_mut().insert( - cache_key, - value - .as_ref() - .map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())), - ); - Ok(value) - } - fn get_for_uncached(&self, key: IStr, real_this: ObjValue) -> Result> { - match (self.this_entries.get(&key), &self.sup) { - (Some(k), None) => Ok(Some(self.evaluate_this(k, real_this)?)), - (Some(k), Some(super_obj)) => { - let our = self.evaluate_this(k, real_this.clone())?; + fn get_for(&self, key: IStr, sup_this: SupThis) -> Result> { + match self.this_entries.get(&key) { + Some(k) => Ok(Some(( + k.invoke.evaluate(sup_this)?, if k.flags.add() { - super_obj - .get_raw(key, real_this)? - .map_or(Ok(Some(our.clone())), |v| { - Ok(Some(evaluate_add_op(&v, &our)?)) - }) + ValueProcess::SuperPlus } else { - Ok(Some(our)) - } - } - (None, Some(super_obj)) => super_obj.get_raw(key, real_this), - (None, None) => Ok(None), + ValueProcess::None + }, + ))), + None => Ok(None), } } fn field_visibility(&self, name: IStr) -> Option { - if let Some(m) = self.this_entries.get(&name) { - Some(match &m.flags.visibility() { - Visibility::Normal => self - .sup - .as_ref() - .and_then(|super_obj| super_obj.field_visibility(name)) - .unwrap_or(Visibility::Normal), - v => *v, - }) - } else if let Some(super_obj) = &self.sup { - super_obj.field_visibility(name) - } else { - None - } + Some(self.this_entries.get(&name)?.flags.visibility()) } - fn run_assertions_raw(&self, real_this: ObjValue) -> Result<()> { + fn run_assertions_raw(&self, sup_this: SupThis) -> Result<()> { if self.assertions.is_empty() { - if let Some(super_obj) = &self.sup { - super_obj.run_assertions_raw(real_this)?; - } return Ok(()); } - if self.assertions_ran.borrow_mut().insert(real_this.clone()) { - for assertion in self.assertions.iter() { - if let Err(e) = assertion.0.run(self.sup.clone(), Some(real_this.clone())) { - self.assertions_ran.borrow_mut().remove(&real_this); - return Err(e); - } - } - if let Some(super_obj) = &self.sup { - super_obj.run_assertions_raw(real_this)?; - } + for assertion in self.assertions.iter() { + assertion.0.run(sup_this.clone())?; } Ok(()) - } -} - -impl PartialEq for ObjValue { - fn eq(&self, other: &Self) -> bool { - Cc::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for ObjValue {} -impl Hash for ObjValue { - fn hash(&self, hasher: &mut H) { - hasher.write_usize(addr_of!(*self.0).expose_provenance() as usize); } } @@ -845,11 +894,8 @@ if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() { return ObjValue::new_empty(); } - ObjValue::new(OopObject::new( - self.sup, - Cc::new(self.map), - Cc::new(self.assertions), - )) + let res = ObjValue::new(OopObject::new(Cc::new(self.map), Cc::new(self.assertions))); + self.sup.map(|sup| res.extend_from(sup)).unwrap_or(res) } } impl Default for ObjValueBuilder { --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -23,7 +23,7 @@ gc::WithCapacityExt as _, manifest::{ManifestFormat, ToStringFormat}, typed::{BoundedUsize, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER}, - ObjValue, Result, Unbound, WeakObjValue, + ObjValue, Result, SupThis, Unbound, WeakSupThis, }; pub trait ThunkValue: Trace { @@ -167,8 +167,6 @@ Self::evaluated(T::default()) } } - -type CacheKey = (Option, Option); #[derive(Trace, Clone)] pub struct CachedUnbound @@ -176,7 +174,7 @@ I: Unbound, T: Trace, { - cache: Cc>>, + cache: Cc>>, value: I, } impl, T: Trace> CachedUnbound { @@ -189,17 +187,14 @@ } impl, T: Clone + Trace> Unbound for CachedUnbound { type Bound = T; - fn bind(&self, sup: Option, this: Option) -> Result { - let cache_key = ( - sup.as_ref().map(|s| s.clone().downgrade()), - this.as_ref().map(|t| t.clone().downgrade()), - ); + fn bind(&self, sup_this: SupThis) -> Result { + let cache_key = sup_this.clone().downgrade(); { if let Some(t) = self.cache.borrow().get(&cache_key) { return Ok(t.clone()); } } - let bound = self.value.bind(sup, this)?; + let bound = self.value.bind(sup_this)?; { let mut cache = self.cache.borrow_mut(); --- a/tests/cpp_test_suite_golden_override/error.static_error_self.jsonnet.golden +++ b/tests/cpp_test_suite_golden_override/error.static_error_self.jsonnet.golden @@ -1,2 +1,2 @@ -self is only usable inside objects +self/super/$ are only usable inside objects elem <0> evaluation \ No newline at end of file --- a/tests/cpp_test_suite_golden_override/error.static_error_super.jsonnet.golden +++ b/tests/cpp_test_suite_golden_override/error.static_error_super.jsonnet.golden @@ -1,2 +1,2 @@ -no super found +self/super/$ are only usable inside objects elem <0> evaluation \ No newline at end of file --- /dev/null +++ b/tests/golden/issue195.jsonnet @@ -0,0 +1 @@ +{ x: 42 } { y: { "false": "x" in super } } --- /dev/null +++ b/tests/golden/issue195.jsonnet.golden @@ -0,0 +1,6 @@ +{ + "x": 42, + "y": { + "false": false + } +} \ No newline at end of file --- a/tests/tests/cpp_test_suite.rs +++ b/tests/tests/cpp_test_suite.rs @@ -30,6 +30,8 @@ .import_resolver(FileImportResolver::default()); let s = s.build(); + let _entered = s.enter(); + let trace_format = CompactFormat { resolver: PathResolver::FileName, max_trace: 20, @@ -61,7 +63,7 @@ Val::Obj(o.build()) }), ); - v = apply_tla(s, &args, v).expect("failed to apply tla"); + v = apply_tla(&args, v).expect("failed to apply tla"); } match v.manifest(JsonFormat::default()) { --- a/tests/tests/golden.rs +++ b/tests/tests/golden.rs @@ -21,6 +21,8 @@ .import_resolver(FileImportResolver::default()); let s = s.build(); + let _entered = s.enter(); + let trace_format = CompactFormat { resolver: PathResolver::FileName, max_trace: 20,