git.delta.rocks / jrsonnet / refs/commits / 3e1d3979f00f

difftreelog

feat wire jrsonnet-web for experimental features

mqyzspnqYaroslav Bolyukin2026-05-06parent: #7324ad9.patch.diff
in: master

11 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2695,6 +2695,7 @@
  "jrsonnet-stdlib",
  "jrsonnet-types",
  "js-sys",
+ "num-bigint",
  "rustc-hash 2.1.2",
  "url",
  "wasm-bindgen",
modifiedbindings/jrsonnet-web/Cargo.tomldiffbeforeafterboth
--- a/bindings/jrsonnet-web/Cargo.toml
+++ b/bindings/jrsonnet-web/Cargo.toml
@@ -9,6 +9,19 @@
 repository.workspace = true
 version.workspace = true
 
+[features]
+experimental = ["exp-preserve-order", "exp-bigint"]
+exp-preserve-order = [
+  "jrsonnet-evaluator/exp-preserve-order",
+  "jrsonnet-stdlib/exp-preserve-order",
+]
+exp-bigint = [
+  "dep:num-bigint",
+  "jrsonnet-evaluator/exp-bigint",
+  "jrsonnet-stdlib/exp-bigint",
+  "jrsonnet-types/exp-bigint",
+]
+
 [dependencies]
 console_error_panic_hook.workspace = true
 getrandom = { workspace = true, features = ["wasm_js"] }
@@ -19,6 +32,7 @@
 jrsonnet-stdlib.workspace = true
 jrsonnet-types.workspace = true
 js-sys.workspace = true
+num-bigint = { workspace = true, optional = true }
 rustc-hash.workspace = true
 url.workspace = true
 wasm-bindgen.workspace = true
modifiedbindings/jrsonnet-web/src/lib.rsdiffbeforeafterboth
before · bindings/jrsonnet-web/src/lib.rs
1#![allow(clippy::future_not_send, reason = "we work with js promises anyway")]23use std::{cell::RefCell, result::Result};45use jrsonnet_evaluator::{6	IStr, NumValue, ObjValue, Result as JrResult, SourcePath, SourceUrl, State, StateBuilder, Val,7	async_import::{ResolvedImportResolver, async_import},8	error,9	function::builtin::{NativeCallback, NativeCallbackHandler},10	manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},11	tla::{TlaArg, apply_tla},12	trace::PathResolver,13	val::ArrValue,14	with_state,15};16use jrsonnet_formatter::FormatOptions;17use jrsonnet_gcmodule::Trace;18use jrsonnet_stdlib::{IniFormat, TomlFormat, XmlJsonmlFormat, YamlFormat};19use jrsonnet_types::ValType;20use js_sys::Reflect::get;21use rustc_hash::FxHashMap;22use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};2324#[wasm_bindgen]25#[derive(Clone, Copy)]26pub enum ValKind {27	Null,28	Bool,29	Num,30	Str,31	Arr,32	Obj,33	Func,34}3536thread_local! {37	static ERR_FACTORY: RefCell<Option<js_sys::Function>> = const { RefCell::new(None) };38}39#[wasm_bindgen(js_name = setErrorFactory)]40pub fn set_error_factory(f: js_sys::Function) {41	ERR_FACTORY.with(|c| *c.borrow_mut() = Some(f));42}43fn make_jrsonnet_error(message: &str, frames: js_sys::Array, cause: &JsValue) -> JsValue {44	ERR_FACTORY.with(|c| {45		c.borrow().as_ref().map_or_else(46			|| js_sys::Error::new(message).into(),47			|f| {48				let args = js_sys::Array::new();49				args.push(&JsValue::from_str(message));50				args.push(&frames);51				args.push(cause);52				f.apply(&JsValue::NULL, &args)53					.unwrap_or_else(|e| js_sys::Error::new(&format!("{e:?}")).into())54			},55		)56	})57}5859fn js_error_message(e: &JsValue) -> String {60	e.dyn_ref::<js_sys::Error>().map_or_else(61		|| e.as_string().unwrap_or_else(|| format!("{e:?}")),62		|err| String::from(err.message()),63	)64}6566fn unwrap_val_ref(value: &JsValue) -> Result<<WasmVal as RefFromWasmAbi>::Anchor, JsValue> {67	let ptr = get(value, &JsValue::from_str("__wbg_ptr"))68		.ok()69		.and_then(|v| v.as_f64())70		.ok_or_else(|| JsValue::from_str("expected a Val instance"))? as u32;71	if ptr == 0 {72		return Err(JsValue::from_str("Val has been freed"));73	}74	Ok(unsafe { <WasmVal as RefFromWasmAbi>::ref_from_abi(ptr) })75}7677fn js_resolver_error(prefix: &str, e: JsValue) -> JsValue {78	let msg = format!("{prefix}: {}", js_error_message(&e));79	let frames = js_sys::Array::new();80	let frame = js_sys::Object::new();81	let _ = js_sys::Reflect::set(82		&frame,83		&JsValue::from_str("desc"),84		&JsValue::from_str(prefix),85	);86	frames.push(&frame);87	make_jrsonnet_error(&msg, frames, &e)88}8990fn jrsonnet_js_error(e: &jrsonnet_evaluator::Error) -> JsValue {91	let msg = e.error().to_string();92	// let msg = format.format(e).unwrap_or_else(|_| e.to_string());93	let frames = js_sys::Array::new();94	for el in &e.trace().0 {95		let frame = js_sys::Object::new();96		let _ = js_sys::Reflect::set(97			&frame,98			&JsValue::from_str("desc"),99			&JsValue::from_str(&el.desc),100		);101		if let Some(loc) = &el.location {102			let path = loc.0.source_path().to_string();103			let _ = js_sys::Reflect::set(104				&frame,105				&JsValue::from_str("path"),106				&JsValue::from_str(&path),107			);108			let mapped = loc.0.map_source_locations(&[loc.1, loc.2]);109			let _ = js_sys::Reflect::set(110				&frame,111				&JsValue::from_str("line"),112				&JsValue::from(mapped[0].line),113			);114			let _ = js_sys::Reflect::set(115				&frame,116				&JsValue::from_str("column"),117				&JsValue::from(mapped[0].column),118			);119		}120		frames.push(&frame);121	}122	make_jrsonnet_error(&msg, frames, &JsValue::UNDEFINED)123}124125impl From<ValType> for ValKind {126	fn from(v: ValType) -> Self {127		match v {128			ValType::Null => Self::Null,129			ValType::Bool => Self::Bool,130			ValType::Num => Self::Num,131			ValType::Str => Self::Str,132			ValType::Arr => Self::Arr,133			ValType::Obj => Self::Obj,134			ValType::Func => Self::Func,135		}136	}137}138139#[wasm_bindgen(js_name = Val)]140pub struct WasmVal {141	val: Val,142	state: Option<State>,143}144145impl WasmVal {146	fn new(val: Val) -> Self {147		Self { val, state: None }148	}149	fn with_state(val: Val, state: State) -> Self {150		Self {151			val,152			state: Some(state),153		}154	}155	fn run<R>(&self, f: impl FnOnce(&Val) -> R) -> R {156		if let Some(state) = &self.state {157			let _guard = state.try_enter();158			f(&self.val)159		} else {160			f(&self.val)161		}162	}163	fn manifest_with(&self, format: impl ManifestFormat) -> Result<String, JsValue> {164		self.run(|v| v.manifest(format))165			.map_err(|e| jrsonnet_js_error(&e))166	}167}168169#[wasm_bindgen(js_class = Val)]170impl WasmVal {171	pub fn null() -> Self {172		Self::new(Val::Null)173	}174	pub fn bool(b: bool) -> Self {175		Self::new(Val::Bool(b))176	}177	pub fn num(n: f64) -> Result<Self, JsError> {178		let n = NumValue::new(n)179			.ok_or_else(|| JsError::new("only finite numbers are supported by jsonnet"))?;180		Ok(Self::new(Val::num(n)))181	}182	pub fn string(s: String) -> Self {183		Self::new(Val::string(s))184	}185	pub fn arr(items: Vec<WasmVal>) -> Self {186		Self::new(Val::arr(187			items.into_iter().map(|v| v.val).collect::<Vec<_>>(),188		))189	}190	pub fn func(191		params: Vec<String>,192193		#[wasm_bindgen(unchecked_param_type = "(...args: Val[]) => Val")]194		callback: js_sys::Function,195	) -> Self {196		#[allow(deprecated)]197		Self::new(Val::function(NativeCallback::new(198			params,199			JsHandler { func: callback },200		)))201	}202203	#[wasm_bindgen(getter)]204	pub fn kind(&self) -> ValKind {205		self.val.value_type().into()206	}207	#[wasm_bindgen(js_name = asBool)]208	pub fn as_bool(&self) -> Option<bool> {209		self.val.as_bool()210	}211	#[wasm_bindgen(js_name = asNum)]212	pub fn as_num(&self) -> Option<f64> {213		self.val.as_num()214	}215	#[wasm_bindgen(js_name = asString)]216	pub fn as_string(&self) -> Option<String> {217		self.val.as_str().map(|s| s.to_string())218	}219	#[wasm_bindgen(js_name = asArr)]220	pub fn as_arr(&self) -> Option<WasmArrValue> {221		self.val.as_arr().map(|arr| WasmArrValue {222			arr,223			state: self.state.clone(),224		})225	}226	#[wasm_bindgen(js_name = asObj)]227	pub fn as_obj(&self) -> Option<WasmObjValue> {228		self.val.as_obj().map(|obj| WasmObjValue {229			obj,230			state: self.state.clone(),231		})232	}233234	#[wasm_bindgen(js_name = applyTla)]235	pub fn apply_tla(236		&self,237		#[wasm_bindgen(unchecked_param_type = "Record<string, Val>")] args: &js_sys::Object,238	) -> Result<WasmVal, JsValue> {239		let mut map: FxHashMap<IStr, TlaArg> = FxHashMap::default();240		for entry in js_sys::Object::entries(args).iter() {241			let pair: js_sys::Array = entry242				.dyn_into()243				.map_err(|_| JsValue::from_str("expected [key, value] entry"))?;244			let key = pair245				.get(0)246				.as_string()247				.ok_or_else(|| JsValue::from_str("TLA arg key must be a string"))?;248			let value = unwrap_val_ref(&pair.get(1))?;249			map.insert(key.into(), TlaArg::Val(value.val.clone()));250		}251		let val = self.val.clone();252		self.run(|_| apply_tla(&map, val))253			.map(|v| WasmVal {254				val: v,255				state: self.state.clone(),256			})257			.map_err(|e| jrsonnet_js_error(&e))258	}259260	#[wasm_bindgen(js_name = manifestJson)]261	pub fn manifest_json(&self, indent: u32) -> Result<String, JsValue> {262		self.manifest_with(JsonFormat::cli(indent as usize))263	}264	#[wasm_bindgen(js_name = manifestToString)]265	pub fn manifest_to_string(&self) -> Result<String, JsValue> {266		self.manifest_with(ToStringFormat)267	}268	#[wasm_bindgen(js_name = manifestString)]269	pub fn manifest_string(&self) -> Result<String, JsValue> {270		self.manifest_with(StringFormat)271	}272	#[wasm_bindgen(js_name = manifestYaml)]273	pub fn manifest_yaml(&self, indent: u32, quote_keys: bool) -> Result<String, JsValue> {274		self.manifest_with(YamlFormat::std_to_yaml(indent != 0, quote_keys))275	}276	#[wasm_bindgen(js_name = manifestYamlStream)]277	pub fn manifest_yaml_stream(278		&self,279		indent: u32,280		quote_keys: bool,281		c_document_end: bool,282	) -> Result<String, JsValue> {283		self.manifest_with(YamlStreamFormat::std_yaml_stream(284			YamlFormat::std_to_yaml(indent != 0, quote_keys),285			c_document_end,286		))287	}288	#[wasm_bindgen(js_name = manifestXmlJsonml)]289	pub fn manifest_xml_jsonml(&self) -> Result<String, JsValue> {290		self.manifest_with(XmlJsonmlFormat::std_to_xml())291	}292	#[wasm_bindgen(js_name = manifestToml)]293	pub fn manifest_toml(&self, indent: u32) -> Result<String, JsValue> {294		self.manifest_with(TomlFormat::std_to_toml(" ".repeat(indent as usize)))295	}296	#[wasm_bindgen(js_name = manifestIni)]297	pub fn manifest_ini(&self) -> Result<String, JsValue> {298		self.manifest_with(IniFormat::std())299	}300}301302#[wasm_bindgen(js_name = ArrValue)]303pub struct WasmArrValue {304	arr: ArrValue,305	state: Option<State>,306}307308#[wasm_bindgen(js_class = ArrValue)]309impl WasmArrValue {310	#[wasm_bindgen(getter)]311	pub fn length(&self) -> u32 {312		self.arr.len()313	}314	pub fn at(&self, index: u32) -> Result<Option<WasmVal>, JsValue> {315		let result = self.state.as_ref().map_or_else(316			|| self.arr.get(index),317			|state| {318				let _guard = state.try_enter();319				self.arr.get(index)320			},321		);322		result323			.map(|opt: Option<Val>| {324				opt.map(|v| WasmVal {325					val: v,326					state: self.state.clone(),327				})328			})329			.map_err(|e| jrsonnet_js_error(&e))330	}331}332333#[wasm_bindgen(js_name = ObjValue)]334pub struct WasmObjValue {335	obj: ObjValue,336	state: Option<State>,337}338339#[wasm_bindgen(js_class = ObjValue)]340impl WasmObjValue {341	pub fn keys(&self) -> Vec<String> {342		self.obj343			.fields()344			.into_iter()345			.map(|s| s.to_string())346			.collect()347	}348	pub fn get(&self, key: String) -> Result<Option<WasmVal>, JsValue> {349		let result = if let Some(state) = &self.state {350			let _guard = state.try_enter();351			self.obj.get(key.into())352		} else {353			self.obj.get(key.into())354		};355		result356			.map(|opt: Option<Val>| {357				opt.map(|v| WasmVal {358					val: v,359					state: self.state.clone(),360				})361			})362			.map_err(|e| jrsonnet_js_error(&e))363	}364}365366#[derive(Trace)]367struct JsHandler {368	#[trace(skip)]369	func: js_sys::Function,370}371372#[wasm_bindgen(inline_js = r"373export function js_invoke_val_callback(cb, args) {374	return cb.apply(null, args);375}376")]377extern "C" {378	#[wasm_bindgen(catch)]379	fn js_invoke_val_callback(380		cb: &js_sys::Function,381		args: &js_sys::Array,382	) -> Result<WasmVal, JsValue>;383}384385impl NativeCallbackHandler for JsHandler {386	fn call(&self, args: &[Val]) -> JrResult<Val> {387		let js_args = js_sys::Array::new();388		let state = with_state(|s| s);389		for arg in args {390			js_args.push(&JsValue::from(WasmVal::with_state(391				arg.clone(),392				state.clone(),393			)));394		}395		let result = js_invoke_val_callback(&self.func, &js_args).map_err(|e| {396			let msg = e397				.as_string()398				.or_else(|| {399					e.dyn_ref::<js_sys::Error>()400						.map(|err| String::from(err.message()))401				})402				.unwrap_or_else(|| format!("{e:?}"));403			error!("js callback threw: {msg}")404		})?;405		Ok(result.val)406	}407}408409#[wasm_bindgen(js_name = State)]410pub struct WasmState {411	state: State,412	resolver: Option<JsAsyncResolver>,413}414#[wasm_bindgen(js_class = State)]415impl WasmState {416	#[wasm_bindgen(constructor)]417	pub fn new(resolver: Option<ImportResolverJs>) -> Self {418		console_error_panic_hook::set_once();419		let mut state = StateBuilder::default();420		state.import_resolver(ResolvedImportResolver::new());421		let std = jrsonnet_stdlib::ContextInitializer::new(PathResolver::Absolute);422		state.context_initializer(std);423		let state = state.build();424		Self {425			state,426			resolver: resolver.map(|js| JsAsyncResolver { js }),427		}428	}429430	#[wasm_bindgen(js_name = evaluateSnippet)]431	pub fn evaluate_snippet(&self, name: &str, snippet: &str) -> Result<WasmVal, JsValue> {432		let _guard = self.state.enter();433		self.state434			.evaluate_snippet(name, snippet)435			.map(|v| WasmVal::with_state(v, self.state.clone()))436			.map_err(|e| jrsonnet_js_error(&e))437	}438439	#[wasm_bindgen(js_name = evaluateFile)]440	pub async fn evaluate_file(&self, path: String) -> Result<WasmVal, JsValue> {441		self.evaluate_file_from_impl(None, path).await442	}443444	#[wasm_bindgen(js_name = evaluateFileFrom)]445	pub async fn evaluate_file_from(&self, from: String, path: String) -> Result<WasmVal, JsValue> {446		self.evaluate_file_from_impl(Some(from), path).await447	}448}449450impl WasmState {451	async fn evaluate_file_from_impl(452		&self,453		from: Option<String>,454		path: String,455	) -> Result<WasmVal, JsValue> {456		let resolver = self457			.resolver458			.clone()459			.ok_or_else(|| JsValue::from_str("file evaluation requires an ImportResolver"))?;460		let from = match from {461			Some(s) => {462				let url = url::Url::parse(&s).map_err(|e| JsValue::from_str(&e.to_string()))?;463				SourcePath::new(SourceUrl::new(url))464			}465			None => SourcePath::default(),466		};467		let path = async_import(self.state.clone(), resolver, &from, &path.as_str()).await?;468		let _guard = self.state.enter();469		self.state470			.import_resolved(path)471			.map(|v| WasmVal::with_state(v, self.state.clone()))472			.map_err(|e| jrsonnet_js_error(&e))473	}474}475476#[wasm_bindgen]477extern "C" {478	#[wasm_bindgen(typescript_type = "ImportResolver")]479	#[derive(Clone)]480	pub type ImportResolverJs;481482	#[wasm_bindgen(catch, method, structural, js_name = resolveFrom)]483	fn resolve_from(484		this: &ImportResolverJs,485		from: Option<String>,486		path: &str,487	) -> Result<js_sys::Promise, JsValue>;488489	#[wasm_bindgen(catch, method, structural, js_name = loadFileContents)]490	fn load_file_contents(491		this: &ImportResolverJs,492		resolved: &str,493	) -> Result<js_sys::Promise, JsValue>;494}495496#[wasm_bindgen(typescript_custom_section)]497const TS_IMPORT_RESOLVER: &'static str = r"498export interface ImportResolver {499	resolveFrom(from: string | undefined, path: string): Promise<string>;500	loadFileContents(resolved: string): Promise<Uint8Array>;501}502";503504#[derive(Clone)]505struct JsAsyncResolver {506	js: ImportResolverJs,507}508509impl jrsonnet_evaluator::async_import::AsyncImportResolver for JsAsyncResolver {510	type Error = JsValue;511512	async fn resolve_from(513		&self,514		from: &SourcePath,515		path: &dyn jrsonnet_evaluator::AsPathLike,516	) -> Result<SourcePath, JsValue> {517		let from_js = (!from.is_default()).then(|| from.to_string());518		let path_str = path.as_path().as_ref().to_string_lossy().into_owned();519		let promise = self520			.js521			.resolve_from(from_js, &path_str)522			.map_err(|e| js_resolver_error("resolveFrom", e))?;523		let resolved_js = wasm_bindgen_futures::JsFuture::from(promise)524			.await525			.map_err(|e| js_resolver_error("resolveFrom", e))?;526		let resolved_str = resolved_js527			.as_string()528			.ok_or_else(|| JsValue::from_str("resolveFrom must return string"))?;529		let url = url::Url::parse(&resolved_str).map_err(|e| JsValue::from_str(&e.to_string()))?;530		Ok(SourcePath::new(SourceUrl::new(url)))531	}532533	async fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>, JsValue> {534		let resolved_str = resolved.to_string();535		let promise = self536			.js537			.load_file_contents(&resolved_str)538			.map_err(|e| js_resolver_error("loadFileContents", e))?;539		let bytes_js = wasm_bindgen_futures::JsFuture::from(promise)540			.await541			.map_err(|e| js_resolver_error("loadFileContents", e))?;542		let arr = bytes_js543			.dyn_into::<js_sys::Uint8Array>()544			.map_err(|_| JsValue::from_str("loadFileContents must return Uint8Array"))?;545		Ok(arr.to_vec())546	}547}548549#[wasm_bindgen(js_name = FormatOptions)]550pub struct WasmFormatOptions {551	indent: u8,552}553#[wasm_bindgen(js_class = FormatOptions)]554impl WasmFormatOptions {555	#[wasm_bindgen(constructor)]556	pub fn new() -> Self {557		Self { indent: 0 }558	}559560	fn build(&self) -> FormatOptions {561		FormatOptions {562			indent: self.indent,563		}564	}565}566567impl Default for WasmFormatOptions {568	fn default() -> Self {569		Self::new()570	}571}572573#[wasm_bindgen]574pub fn format(src: &str, opts: &WasmFormatOptions) -> Result<String, String> {575	match jrsonnet_formatter::format(src, &opts.build()) {576		Ok(v) => Ok(v),577		Err(e) => {578			let e = e.build();579			Err(hi_doc::source_to_ansi(&e))580		}581	}582}
modifiedcrates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -13,6 +13,12 @@
 workspace = true
 
 [features]
+experimental = [
+  "exp-preserve-order",
+  "exp-bigint",
+  "exp-null-coaelse",
+  "exp-regex",
+]
 exp-preserve-order = [
   "jrsonnet-evaluator/exp-preserve-order",
   "jrsonnet-stdlib/exp-preserve-order",
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -26,6 +26,13 @@
 # Use PEG parser
 peg-parser = ["dep:jrsonnet-peg-parser"]
 
+experimental = [
+  "exp-preserve-order",
+  "exp-destruct",
+  "exp-object-iteration",
+  "exp-bigint",
+  "exp-null-coaelse",
+]
 # Allows to preserve field order in objects
 exp-preserve-order = []
 # Implements field destructuring
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -42,7 +42,9 @@
 use jrsonnet_gcmodule::{Cc, Trace, cc_dyn};
 pub use jrsonnet_interner::{IBytes, IStr};
 use jrsonnet_ir::Expr;
-pub use jrsonnet_ir::{NumValue, Source, SourcePath, SourceUrl, SourceVirtual, Span};
+pub use jrsonnet_ir::{
+	NumValue, Source, SourceDefaultIgnoreJpath, SourcePath, SourceUrl, SourceVirtual, Span,
+};
 #[doc(hidden)]
 pub use jrsonnet_macros;
 
modifiedcrates/jrsonnet-ir-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/Cargo.toml
+++ b/crates/jrsonnet-ir-parser/Cargo.toml
@@ -10,6 +10,8 @@
 version.workspace = true
 
 [features]
+default = []
+experimental = ["exp-null-coaelse", "exp-destruct"]
 exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
 exp-destruct = ["jrsonnet-ir/exp-destruct"]
 
modifiedcrates/jrsonnet-ir/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-ir/Cargo.toml
+++ b/crates/jrsonnet-ir/Cargo.toml
@@ -12,6 +12,7 @@
 
 [features]
 default = []
+experimental = ["exp-destruct", "exp-null-coaelse"]
 exp-destruct = []
 exp-null-coaelse = []
 
modifiedcrates/jrsonnet-peg-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/Cargo.toml
+++ b/crates/jrsonnet-peg-parser/Cargo.toml
@@ -22,5 +22,6 @@
 
 [features]
 default = []
+experimental = ["exp-destruct", "exp-null-coaelse"]
 exp-destruct = ["jrsonnet-ir/exp-destruct"]
 exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
modifiedcrates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -13,6 +13,12 @@
 workspace = true
 
 [features]
+experimental = [
+  "exp-preserve-order",
+  "exp-bigint",
+  "exp-null-coaelse",
+  "exp-regex",
+]
 # Add order preservation flag to some functions
 exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
 # Bigint type
modifiedcrates/jrsonnet-types/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-types/Cargo.toml
+++ b/crates/jrsonnet-types/Cargo.toml
@@ -18,4 +18,5 @@
 peg.workspace = true
 
 [features]
+experimental = ["exp-bigint"]
 exp-bigint = []