--- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -126,20 +126,29 @@ /// During import, this trait will be called to create initial context for file. /// It may initialize global variables, stdlib for example. pub trait ContextInitializer: Trace { + /// For which size the builder should be preallocated + fn reserve_vars(&self) -> usize { + 0 + } /// Initialize default file context. - fn initialize(&self, state: State, for_file: Source) -> 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()); + self.populate(for_file, &mut builder); + builder.build() + } + /// For composability: extend builder. May panic if this initialization is not supported, + /// and the context may only be created via `initialize`. + fn populate(&self, for_file: Source, builder: &mut ContextBuilder); /// Allows upcasting from abstract to concrete context initializer. /// jrsonnet by itself doesn't use this method, it is allowed for it to panic. fn as_any(&self) -> &dyn Any; } /// Context initializer which adds nothing. -#[derive(Trace)] -pub struct DummyContextInitializer; -impl ContextInitializer for DummyContextInitializer { - fn initialize(&self, state: State, _for_file: Source) -> Context { - ContextBuilder::new(state).build() - } +impl ContextInitializer for () { + fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {} fn as_any(&self) -> &dyn Any { self } @@ -157,7 +166,7 @@ impl Default for EvaluationSettings { fn default() -> Self { Self { - context_initializer: tb!(DummyContextInitializer), + context_initializer: tb!(()), import_resolver: tb!(DummyImportResolver), } } @@ -396,6 +405,46 @@ pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> { self.0.settings.borrow_mut() } + pub fn add_global(&self, name: IStr, value: Thunk) { + #[derive(Trace)] + struct GlobalsCtx { + globals: RefCell>>, + inner: TraceBox, + } + impl ContextInitializer for GlobalsCtx { + fn reserve_vars(&self) -> usize { + self.inner.reserve_vars() + self.globals.borrow().len() + } + fn populate(&self, for_file: Source, builder: &mut ContextBuilder) { + self.inner.populate(for_file, builder); + for (name, val) in self.globals.borrow().iter() { + builder.bind(name.clone(), val.clone()); + } + } + + fn as_any(&self) -> &dyn Any { + self + } + } + let mut settings = self.settings_mut(); + let initializer = &mut settings.context_initializer; + match initializer.as_any().downcast_ref::() { + Some(glob) => { + glob.globals.borrow_mut().insert(name, value); + } + None => { + let inner = std::mem::replace(&mut settings.context_initializer, tb!(())); + settings.context_initializer = tb!(GlobalsCtx { + globals: { + let mut out = GcHashMap::with_capacity(1); + out.insert(name, value); + RefCell::new(out) + }, + inner + }) + } + } + } } /// Raw methods evaluate passed values but don't perform TLA execution --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -7,10 +7,10 @@ use jrsonnet_evaluator::{ error::{ErrorKind::*, Result}, function::{builtin::Builtin, CallLocation, FuncVal, TlaArg}, - gc::{GcHashMap, TraceBox}, + gc::TraceBox, tb, trace::PathResolver, - Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val, + ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val, }; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_parser::Source; @@ -231,8 +231,6 @@ pub ext_vars: HashMap, /// Used for `std.native` pub ext_natives: HashMap>>, - /// Helper to add globals without implementing custom ContextInitializer - pub globals: GcHashMap>, /// Used for `std.trace` pub trace_printer: Box, /// Used for `std.thisFile` @@ -246,10 +244,13 @@ #[derive(Trace, Clone)] pub struct ContextInitializer { - // When we don't need to support legacy-this-file, we can reuse same context for all files + /// When we don't need to support legacy-this-file, we can reuse same context for all files #[cfg(not(feature = "legacy-this-file"))] - context: Context, - // Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it + context: jrsonnet_evaluator::Context, + /// For `populate` + #[cfg(not(feature = "legacy-this-file"))] + stdlib_thunk: Thunk, + /// Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it #[cfg(feature = "legacy-this-file")] stdlib_obj: ObjValue, settings: Rc>, @@ -259,23 +260,24 @@ let settings = Settings { ext_vars: Default::default(), ext_natives: Default::default(), - globals: Default::default(), trace_printer: Box::new(StdTracePrinter::new(resolver.clone())), path_resolver: resolver, }; let settings = Rc::new(RefCell::new(settings)); + let stdlib_obj = stdlib_uncached(settings.clone()); + #[cfg(not(feature = "legacy-this-file"))] + let stdlib_thunk = Thunk::evaluated(Val::Obj(stdlib_obj)); Self { #[cfg(not(feature = "legacy-this-file"))] context: { let mut context = ContextBuilder::with_capacity(_s, 1); - context.bind( - "std".into(), - Thunk::evaluated(Val::Obj(stdlib_uncached(settings.clone()))), - ); + context.bind("std".into(), stdlib_thunk.clone()); context.build() }, + #[cfg(not(feature = "legacy-this-file"))] + stdlib_thunk, #[cfg(feature = "legacy-this-file")] - stdlib_obj: stdlib_uncached(settings.clone()), + stdlib_obj, settings, } } @@ -321,28 +323,24 @@ } } impl jrsonnet_evaluator::ContextInitializer for ContextInitializer { + fn reserve_vars(&self) -> usize { + 1 + } #[cfg(not(feature = "legacy-this-file"))] fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context { - let out = self.context.clone(); - let globals = &self.settings().globals; - if globals.is_empty() { - return out; - } - - let mut out = ContextBuilder::extend(out); - for (k, v) in globals.iter() { - out.bind(k.clone(), v.clone()); - } - out.build() + self.context.clone() + } + #[cfg(not(feature = "legacy-this-file"))] + fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) { + builder.bind("std".into(), self.stdlib_thunk.clone()); } #[cfg(feature = "legacy-this-file")] - fn initialize(&self, s: State, source: Source) -> Context { + fn populate(&self, source: Source, builder: &mut ContextBuilder) { use jrsonnet_evaluator::val::StrValue; - let mut builder = ObjValueBuilder::new(); - builder.with_super(self.stdlib_obj.clone()); - builder - .member("thisFile".into()) + let mut std = ObjValueBuilder::new(); + std.with_super(self.stdlib_obj.clone()); + std.member("thisFile".into()) .hide() .value(Val::Str(StrValue::Flat( match source.source_path().path() { @@ -351,17 +349,12 @@ }, ))) .expect("this object builder is empty"); - let stdlib_with_this_file = builder.build(); + let stdlib_with_this_file = std.build(); - let mut context = ContextBuilder::with_capacity(s, 1); - context.bind( + builder.bind( "std".into(), Thunk::evaluated(Val::Obj(stdlib_with_this_file)), ); - for (k, v) in self.settings().globals.iter() { - context.bind(k.clone(), v.clone()); - } - context.build() } fn as_any(&self) -> &dyn std::any::Any { self @@ -371,22 +364,11 @@ pub trait StateExt { /// This method was previously implemented in jrsonnet-evaluator itself fn with_stdlib(&self); - fn add_global(&self, name: IStr, value: Thunk); } impl StateExt for State { fn with_stdlib(&self) { let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback()); self.settings_mut().context_initializer = tb!(initializer) - } - fn add_global(&self, name: IStr, value: Thunk) { - self.settings() - .context_initializer - .as_any() - .downcast_ref::() - .expect("not standard context initializer") - .settings_mut() - .globals - .insert(name, value); } } --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1683574088, - "narHash": "sha256-RjE7UXfyYBV3vkpjL5irZOF+4ZgTQlvEWYJsFL2Hig0=", + "lastModified": 1689162265, + "narHash": "sha256-kdW79sfwX2TTX8yFBNUsEYOG+gQuAOHU+WcUtxMUnlc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "05b1a97381588ba98d98f8725b2137fce0ab45cb", + "rev": "1941c7d8f1219c615a1d6dae826e0d6fab89acca", "type": "github" }, "original": { @@ -50,11 +50,11 @@ ] }, "locked": { - "lastModified": 1683512408, - "narHash": "sha256-QMJGp/37En+d5YocJuSU89GL14bBYkIJQ6mqhRfqkkc=", + "lastModified": 1689129196, + "narHash": "sha256-/z/Al4sFcIh5oPQWA9MclQmJR9g3RO8UDiHGaj/T9R8=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "75b07756c3feb22cf230e75fb064c1b4c725b9bc", + "rev": "db8d909c9526d4406579ee7343bf2d7de3d15eac", "type": "github" }, "original": { --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ inherit system; overlays = [ rust-overlay.overlays.default ]; }; - rust = ((pkgs.rustChannelOf { date = "2023-05-07"; channel = "nightly"; }).default.override { + rust = ((pkgs.rustChannelOf { date = "2023-06-26"; channel = "nightly"; }).default.override { extensions = [ "rust-src" "miri" "rust-analyzer" ]; }); in --- a/tests/tests/common.rs +++ b/tests/tests/common.rs @@ -5,7 +5,6 @@ function::{builtin, FuncVal}, throw, ObjValueBuilder, State, Thunk, Val, }; -use jrsonnet_stdlib::StateExt; #[macro_export] macro_rules! ensure_eq {