git.delta.rocks / jrsonnet / refs/commits / aa97bc0e09e5

difftreelog

test update for split stdlib

Yaroslav Bolyukin2022-08-07parent: #4c21363.patch.diff
in: master

100 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -517,18 +517,18 @@
 
 [[package]]
 name = "serde"
-version = "1.0.137"
+version = "1.0.142"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.137"
+version = "1.0.142"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -620,6 +620,16 @@
 ]
 
 [[package]]
+name = "tests"
+version = "0.1.0"
+dependencies = [
+ "jrsonnet-evaluator",
+ "jrsonnet-gcmodule",
+ "jrsonnet-stdlib",
+ "serde",
+]
+
+[[package]]
 name = "textwrap"
 version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace]
-members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet"]
+members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
 
 [profile.test]
 opt-level = 1
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -139,29 +139,47 @@
 	}
 }
 
-#[derive(Default)]
 pub struct ContextBuilder {
 	bindings: GcHashMap<IStr, Thunk<Val>>,
+	extend: Option<Context>,
 }
+
 impl ContextBuilder {
 	pub fn new() -> Self {
-		Self::default()
+		Self::with_capacity(0)
 	}
 	pub fn with_capacity(capacity: usize) -> Self {
 		Self {
 			bindings: GcHashMap::with_capacity(capacity),
+			extend: None,
 		}
 	}
+	pub fn extend(parent: Context) -> Self {
+		Self {
+			bindings: GcHashMap::new(),
+			extend: Some(parent),
+		}
+	}
 	pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &mut Self {
 		self.bindings.insert(name, value);
 		self
 	}
 	pub fn build(self) -> Context {
-		Context(Cc::new(ContextInternals {
-			bindings: LayeredHashMap::new(self.bindings),
-			dollar: None,
-			sup: None,
-			this: None,
-		}))
+		if let Some(parent) = self.extend {
+			parent.extend(self.bindings, None, None, None)
+		} else {
+			Context(Cc::new(ContextInternals {
+				bindings: LayeredHashMap::new(self.bindings),
+				dollar: None,
+				sup: None,
+				this: None,
+			}))
+		}
+	}
+}
+
+impl Default for ContextBuilder {
+	fn default() -> Self {
+		Self::new()
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -2,6 +2,7 @@
 
 use jrsonnet_gcmodule::{Cc, Trace};
 
+// TODO: Replace with OnceCell once in std
 #[derive(Clone, Trace)]
 pub struct Pending<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
 impl<T: Trace + 'static> Pending<T> {
deletedcrates/jrsonnet-evaluator/tests/as_native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/as_native.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use jrsonnet_evaluator::{error::Result, State};
-
-mod common;
-
-#[test]
-fn as_native() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-
-	let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#.into())?;
-	let func = val.as_func().expect("this is function");
-
-	let native = func.into_native::<((u32, u32), u32)>();
-
-	ensure_eq!(native(s.clone(), 1, 2)?, 3);
-	ensure_eq!(native(s, 3, 4)?, 7);
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/builtin.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-mod common;
-
-use jrsonnet_evaluator::{
-	error::Result,
-	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
-	tb,
-	typed::Typed,
-	State, Val,
-};
-use jrsonnet_gcmodule::Cc;
-
-#[builtin]
-fn a() -> Result<u32> {
-	Ok(1)
-}
-
-#[test]
-fn basic_function() -> Result<()> {
-	let s = State::default();
-	let a: a = a {};
-	let v = u32::from_untyped(
-		a.call(
-			s.clone(),
-			s.create_default_context(),
-			CallLocation::native(),
-			&(),
-		)?,
-		s,
-	)?;
-
-	ensure_eq!(v, 1);
-	Ok(())
-}
-
-#[builtin]
-fn native_add(a: u32, b: u32) -> Result<u32> {
-	Ok(a + b)
-}
-
-#[test]
-fn call_from_code() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	s.settings_mut().globals.insert(
-		"nativeAdd".into(),
-		Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
-	);
-
-	let v = s.evaluate_snippet(
-		"snip".to_owned(),
-		"
-            assert nativeAdd(1, 2) == 3;
-            assert nativeAdd(100, 200) == 300;
-            null
-        "
-		.into(),
-	)?;
-	ensure_val_eq!(s, v, Val::Null);
-	Ok(())
-}
-
-#[builtin(fields(
-    a: u32
-))]
-fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
-	Ok(this.a + b)
-}
-
-#[builtin]
-fn curry_add(a: u32) -> Result<FuncVal> {
-	Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
-}
-
-#[test]
-fn nonstatic_builtin() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	s.settings_mut().globals.insert(
-		"curryAdd".into(),
-		Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
-	);
-
-	let v = s.evaluate_snippet(
-		"snip".to_owned(),
-		"
-            local a = curryAdd(1);
-            local b = curryAdd(4);
-
-            assert a(2) == 3;
-            assert a(200) == 201;
-
-            assert b(2) == 6;
-            assert b(200) == 204;
-            null
-        "
-		.into(),
-	)?;
-	ensure_val_eq!(s, v, Val::Null);
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/common.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use jrsonnet_evaluator::{
-	error::Result,
-	function::{builtin, FuncVal},
-	throw_runtime, ObjValueBuilder, State, Thunk, Val,
-};
-
-#[macro_export]
-macro_rules! ensure_eq {
-	($a:expr, $b:expr $(,)?) => {{
-		let a = &$a;
-		let b = &$b;
-		if a != b {
-			::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
-		}
-	}};
-}
-
-#[macro_export]
-macro_rules! ensure {
-	($v:expr $(,)?) => {
-		if !$v {
-			::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v))
-		}
-	};
-}
-
-#[macro_export]
-macro_rules! ensure_val_eq {
-	($s:expr, $a:expr, $b:expr) => {{
-		if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
-			::jrsonnet_evaluator::throw_runtime!(
-				"assertion failed: a != b\na={:#?}\nb={:#?}",
-				$a.to_json(
-					$s.clone(),
-					2,
-					#[cfg(feature = "exp-preserve-order")]
-					false
-				)?,
-				$b.to_json(
-					$s.clone(),
-					2,
-					#[cfg(feature = "exp-preserve-order")]
-					false
-				)?,
-			)
-		}
-	}};
-}
-
-#[builtin]
-fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
-	match lazy.evaluate(s) {
-		Ok(_) => {
-			throw_runtime!("expected argument to throw on evaluation, but it returned instead")
-		}
-		Err(e) => {
-			let error = format!("{}", e.error());
-			ensure_eq!(message, error);
-		}
-	}
-	Ok(true)
-}
-
-#[allow(dead_code)]
-pub fn with_test(s: &State) {
-	let mut bobj = ObjValueBuilder::new();
-	bobj.member("assertThrow".into())
-		.hide()
-		.value(
-			s.clone(),
-			Val::Func(FuncVal::StaticBuiltin(assert_throw::INST)),
-		)
-		.expect("no error");
-
-	s.settings_mut()
-		.globals
-		.insert("test".into(), Val::Obj(bobj.build()));
-}
deletedcrates/jrsonnet-evaluator/tests/golden.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use std::{
-	fs, io,
-	path::{Path, PathBuf},
-};
-
-use jrsonnet_evaluator::{
-	trace::{CompactFormat, PathResolver},
-	FileImportResolver, State,
-};
-
-mod common;
-
-fn run(root: &Path, file: &Path) -> String {
-	let s = State::default();
-	s.set_trace_format(Box::new(CompactFormat {
-		resolver: PathResolver::Relative(root.to_owned()),
-		padding: 3,
-	}));
-	s.with_stdlib();
-	common::with_test(&s);
-	s.set_import_resolver(Box::new(FileImportResolver::default()));
-
-	let v = match s.import(file.to_owned()) {
-		Ok(v) => v,
-		Err(e) => return s.stringify_err(&e),
-	};
-	match v.to_json(
-		s.clone(),
-		3,
-		#[cfg(feature = "exp-preserve-order")]
-		false,
-	) {
-		Ok(v) => v.to_string(),
-		Err(e) => s.stringify_err(&e),
-	}
-}
-
-#[test]
-fn test() -> io::Result<()> {
-	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-	root.push("tests/golden");
-
-	for entry in fs::read_dir(&root)? {
-		let entry = entry?;
-		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
-			continue;
-		}
-
-		let result = run(&root, &entry.path());
-
-		let mut golden_path = entry.path();
-		golden_path.set_extension("jsonnet.golden");
-
-		if !golden_path.exists() {
-			fs::write(golden_path, &result)?;
-		} else {
-			let golden = fs::read_to_string(golden_path)?;
-
-			assert_eq!(
-				result,
-				golden,
-				"golden didn't match for {}",
-				entry.path().display()
-			)
-		}
-	}
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/golden/array_comp.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-[[a, b] for a in [1, 2, 3] for b in [4, 5, 6]]
deletedcrates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet.golden
+++ /dev/null
@@ -1,38 +0,0 @@
-[
-   [
-      1,
-      4
-   ],
-   [
-      1,
-      5
-   ],
-   [
-      1,
-      6
-   ],
-   [
-      2,
-      4
-   ],
-   [
-      2,
-      5
-   ],
-   [
-      2,
-      6
-   ],
-   [
-      3,
-      4
-   ],
-   [
-      3,
-      5
-   ],
-   [
-      3,
-      6
-   ]
-]
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.manifestJsonEx({ a: 3, b: 4, c: 6 }, '')
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet.golden
+++ /dev/null
@@ -1 +0,0 @@
-"{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}"
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.manifestJsonMinified({ a: 3, b: 4, c: 6 })
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet.golden
+++ /dev/null
@@ -1 +0,0 @@
-"{\"a\":3,\"b\":4,\"c\":6}"
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet.golden
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-   "a": -1,
-   "b": 1,
-   "c": 3.141,
-   "d": [ ]
-}
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/issue23.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-import 'issue23.jsonnet'
deletedcrates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-infinite recursion detected
-   issue23.jsonnet:1:1-26: import "issue23.jsonnet"
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/issue40.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet
+++ /dev/null
@@ -1,9 +0,0 @@
-local conf = {
-  n: '',
-};
-
-local result = conf {
-  assert std.isNumber(self.n) : 'is number',
-};
-
-std.manifestJsonEx(result, '')
deletedcrates/jrsonnet-evaluator/tests/golden/issue40.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-assert failed: is number
-   issue40.jsonnet:6:10-31: assertion failure
-   issue40.jsonnet:9:1-32:  function <builtin_manifest_json_ex> call
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-sta
deletedcrates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-variable is not defined: sta
-There is variable with similar name present: std
-   missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/object_comp.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-{ local t = 'a', ['h' + i + '_' + z]: if 'h' + (i - 1) + '_' + z in self then t + 1 else 0 + t for i in [1, 2, 3] for z in [2, 3, 4] if z != i }
deletedcrates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet.golden
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-   "h1_2": "0a",
-   "h1_3": "0a",
-   "h1_4": "0a",
-   "h2_3": "a1",
-   "h2_4": "a1",
-   "h3_2": "0a",
-   "h3_4": "a1"
-}
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet
+++ /dev/null
@@ -1,2 +0,0 @@
-// Test that test.assertThrow will return error, if body is not errored
-test.assertThrow(1, '1')
deletedcrates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-runtime error: expected argument to throw on evaluation, but it returned instead
-   test_assertThrow.jsonnet:2:1-26: function <assert_throw> call
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/sanity.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/sanity.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val};
-
-mod common;
-
-#[test]
-fn assert_positive() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-
-	let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null".into())?;
-	ensure_val_eq!(s, v, Val::Null);
-	let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)".into())?;
-	ensure_val_eq!(s, v, Val::Bool(true));
-
-	Ok(())
-}
-
-#[test]
-fn assert_negative() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-
-	{
-		let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null".into()) {
-			Ok(_) => throw_runtime!("assertion should fail"),
-			Err(e) => e,
-		};
-		let e = s.stringify_err(&e);
-		ensure!(e.starts_with("assert failed: fail\n"));
-	}
-	{
-		let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)".into()) {
-			Ok(_) => throw_runtime!("assertion should fail"),
-			Err(e) => e,
-		};
-		let e = s.stringify_err(&e);
-		ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))
-	}
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/suite.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::{
-	fs, io,
-	path::{Path, PathBuf},
-};
-
-use jrsonnet_evaluator::{
-	trace::{CompactFormat, PathResolver},
-	FileImportResolver, State, Val,
-};
-
-mod common;
-
-fn run(root: &Path, file: &Path) {
-	let s = State::default();
-	s.set_trace_format(Box::new(CompactFormat {
-		resolver: PathResolver::Relative(root.to_owned()),
-		padding: 3,
-	}));
-	s.with_stdlib();
-	common::with_test(&s);
-	s.set_import_resolver(Box::new(FileImportResolver::default()));
-
-	match s.import(file.to_owned()) {
-		Ok(Val::Bool(true)) => {}
-		Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()),
-		Ok(_) => panic!("test {} returned wrong type as result", file.display()),
-		Err(e) => panic!("test {} failed:\n{}", file.display(), s.stringify_err(&e)),
-	};
-}
-
-#[test]
-fn test() -> io::Result<()> {
-	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-	root.push("tests/suite");
-
-	for entry in fs::read_dir(&root)? {
-		let entry = entry?;
-		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
-			continue;
-		}
-
-		run(&root, &entry.path());
-	}
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_ascii.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_ascii.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(std.asciiUpper('aBc😀'), 'ABC😀') &&
-std.assertEqual(std.asciiLower('aBc😀'), 'abc😀') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_base64.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_base64.jsonnet
+++ /dev/null
@@ -1,2 +0,0 @@
-std.assertEqual(std.base64('test'), 'dGVzdA==') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_chars.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_chars.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local c = '😎';
-std.assertEqual({ c: std.codepoint(c), l: std.length(c) }, { c: 128526, l: 1 }) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_constant.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_constant.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local std2 = std; local std = std2 { primitiveEquals(a, b):: false };
-// In jsonnet, this expression was failing because of being desugared to std.primitiveEquals(1, 1)
-std.assertEqual(1 == 1, true)
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_count.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_count.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(std.count([], ''), 0) &&
-std.assertEqual(std.count(['a', 'b', 'a'], 'd'), 0) &&
-std.assertEqual(std.count(['a', 'b', 'a'], 'a'), 2) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_join.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_join.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(std.join([0, 0], [[1, 2], [3, 4], [5, 6]]), [1, 2, 0, 0, 3, 4, 0, 0, 5, 6]) &&
-std.assertEqual(std.join(',', ['1', '2', '3', '4']), '1,2,3,4') &&
-std.assertEqual(std.join(',', ['1', null, '2', null, '3']), '1,2,3') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_member.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_member.jsonnet
+++ /dev/null
@@ -1,7 +0,0 @@
-!std.member('', '') &&
-std.member('abc', 'a') &&
-!std.member('abc', 'd') &&
-!std.member([], '') &&
-std.member(['a', 'b', 'c'], 'a') &&
-!std.member(['a', 'b', 'c'], 'd') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/function_args.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/function_args.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(local a = function(b, c=2) b + c; a(2), 4) &&
-std.assertEqual(local a = function(b, c='Dear') b + c + d, d = 'World'; a('Hello'), 'HelloDearWorld') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/function_context.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/function_context.jsonnet
+++ /dev/null
@@ -1,10 +0,0 @@
-local k = {
-  t(name=self.h): [self.h, name],
-  h: 3,
-};
-local f = {
-  t: k.t(),
-  h: 4,
-};
-std.assertEqual(f.t[0], f.t[1]) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/function_lazy_args.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/function_lazy_args.jsonnet
+++ /dev/null
@@ -1,5 +0,0 @@
-local fun(a) = 2;
-std.assertEqual(fun(error '3'), 2) &&
-// But in tailstrict mode arguments are evaluated eagerly
-test.assertThrow(fun(error '3') tailstrict, 'runtime error: 3') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/local.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/local.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(local a = 2; local b = 3; a + b, 5) &&
-std.assertEqual(local a = 1, b = a + 1; a + b, 3) &&
-std.assertEqual(local a = 1; local a = 2; a, 2) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/math.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/math.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(2 + 2 * 2, 6) &&
-std.assertEqual(3 + (2 + 2 * 2), 9) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_assertion.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_assertion.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual({ assert 'a' in self : 'missing a' } + { a: 2 }, { a: 2 }) &&
-test.assertThrow({ assert 'a' in self : 'missing a', b: 1 }.b, 'assert failed: missing a') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_comp_self.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_comp_self.jsonnet
+++ /dev/null
@@ -1,8 +0,0 @@
-std.assertEqual(std.objectFields({
-  a: {
-    [name]: name
-    for name in std.objectFields(self)
-  },
-  b: 2,
-  c: 3,
-}.a), ['a', 'b', 'c'])
deletedcrates/jrsonnet-evaluator/tests/suite/object_context.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_context.jsonnet
+++ /dev/null
@@ -1,13 +0,0 @@
-// `self` assigned to `me` was lost when being
-// referenced from field
-std.assertEqual({
-  local me = self,
-  a: 3,
-  b: me.a,
-}.b, 3) &&
-std.assertEqual({
-  local me = self,
-  a: 3,
-  b(): me.a,
-}.b(), 3) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_fields.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_fields.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-local a = 'a', b = null;
-std.assertEqual({ [a]: 2 }, { a: 2 }) &&
-std.assertEqual({ [b]: 2 }, {}) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_inheritance.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_inheritance.jsonnet
+++ /dev/null
@@ -1,17 +0,0 @@
-std.assertEqual({ a: self.b } + { b: 3 }, { a: 3, b: 3 }) &&
-std.assertEqual(
-  {
-    name: 'Alice',
-    welcome: 'Hello ' + self.name + '!',
-  },
-  { name: 'Alice', welcome: 'Hello Alice!' },
-) &&
-std.assertEqual(
-  {
-    name: 'Alice',
-    welcome: 'Hello ' + self.name + '!',
-  } + {
-    name: 'Bob',
-  }, { name: 'Bob', welcome: 'Hello Bob!' }
-) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_locals.jsonnetdiffbeforeafterboth
before · crates/jrsonnet-evaluator/tests/suite/object_locals.jsonnet
1std.assertEqual({ local a = 3, b: a }, { b: 3 }) &&2std.assertEqual({ local a = 3, local c = a, b: c }, { b: 3 }) &&3std.assertEqual({ local a = function(b) { [b]: 4 }, test: a('test') }, { test: { test: 4 } }) &&4true
deletedcrates/jrsonnet-evaluator/tests/suite/object_super_standalone.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_super_standalone.jsonnet
+++ /dev/null
@@ -1,11 +0,0 @@
-local obj = {
-  a: 1,
-  b: 2,
-  c: 3,
-};
-local test = obj + {
-  fields: std.objectFields(super),
-  d: 5,
-};
-std.assertEqual(test.fields, ['a', 'b', 'c']) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/sjsonnet_issue_127.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/sjsonnet_issue_127.jsonnet
+++ /dev/null
@@ -1,6 +0,0 @@
-local myFunc = function(a)
-  if (a) then "a" else "b";
-
-local b = "aaa";
-
-std.assertEqual(myFunc(b == [] || b == ['e']), "b")
deletedcrates/jrsonnet-evaluator/tests/suite/string_concat.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/string_concat.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual('Hello' + 'World', 'HelloWorld') &&
-std.assertEqual('Hello' * 3, 'HelloHelloHello') &&
-std.assertEqual('Hello' + 'World' * 3, 'HelloWorldWorldWorld') &&
-true
deletedcrates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/typed_obj.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-mod common;
-
-use std::fmt::Debug;
-
-use jrsonnet_evaluator::{error::Result, typed::Typed, State};
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct A {
-	a: u32,
-	b: u16,
-}
-
-fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
-	let untyped = T::into_untyped(value.clone(), s.clone())?;
-	let value2 = T::from_untyped(untyped.clone(), s.clone())?;
-	ensure_eq!(value, value2);
-	let untyped2 = T::into_untyped(value2, s.clone())?;
-	ensure_val_eq!(s, untyped, untyped2);
-
-	Ok(())
-}
-
-#[test]
-fn simple_object() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let a = A::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(a, A { a: 1, b: 2 });
-	test_roundtrip(a, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct B {
-	a: u32,
-	#[typed(rename = "c")]
-	b: u16,
-}
-
-#[test]
-fn renamed_field() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let b = B::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(b, B { a: 1, b: 2 });
-	ensure_eq!(
-		&B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"a": 1, "c": 2}"#,
-	);
-	test_roundtrip(b, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct ObjectKind {
-	#[typed(rename = "apiVersion")]
-	api_version: String,
-	#[typed(rename = "kind")]
-	kind: String,
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct Object {
-	#[typed(flatten)]
-	kind: ObjectKind,
-	b: u16,
-}
-
-#[test]
-fn flattened_object() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let obj = Object::from_untyped(
-		s.evaluate_snippet(
-			"snip".to_owned(),
-			"{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
-		)?,
-		s.clone(),
-	)?;
-	ensure_eq!(
-		obj,
-		Object {
-			kind: ObjectKind {
-				api_version: "ver".into(),
-				kind: "kind".into(),
-			},
-			b: 2
-		}
-	);
-	ensure_eq!(
-		&Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
-	);
-	test_roundtrip(obj, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct C {
-	a: Option<u32>,
-	b: u16,
-}
-
-#[test]
-fn optional_field_some() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let c = C::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(c, C { a: Some(1), b: 2 });
-	ensure_eq!(
-		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"a": 1, "b": 2}"#,
-	);
-	test_roundtrip(c, s)?;
-	Ok(())
-}
-
-#[test]
-fn optional_field_none() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let c = C::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{b: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(c, C { a: None, b: 2 });
-	ensure_eq!(
-		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"b": 2}"#,
-	);
-	test_roundtrip(c, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct D {
-	#[typed(flatten(ok))]
-	e: Option<E>,
-	b: u16,
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct E {
-	v: u32,
-}
-
-#[test]
-fn flatten_optional_some() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let d = D::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(
-		d,
-		D {
-			e: Some(E { v: 1 }),
-			b: 2
-		}
-	);
-	ensure_eq!(
-		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"b": 2, "v": 1}"#,
-	);
-	test_roundtrip(d, s)?;
-	Ok(())
-}
-
-#[test]
-fn flatten_optional_none() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let d = D::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(d, D { e: None, b: 2 });
-	ensure_eq!(
-		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"b": 2}"#,
-	);
-	test_roundtrip(d, s)?;
-	Ok(())
-}
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -362,6 +362,7 @@
 pub mod tests {
 	use std::borrow::Cow;
 
+	use jrsonnet_interner::IStr;
 	use BinaryOpType::*;
 
 	use super::{expr::*, parse};
@@ -372,7 +373,7 @@
 			parse(
 				$s,
 				&ParserSettings {
-					file_name: Source::new_virtual(Cow::Borrowed("<test>")),
+					file_name: Source::new_virtual(Cow::Borrowed("<test>"), IStr::empty()),
 				},
 			)
 			.unwrap()
@@ -383,7 +384,11 @@
 		($expr:expr, $from:expr, $to:expr$(,)?) => {
 			LocExpr(
 				std::rc::Rc::new($expr),
-				ExprLocation(Source::new_virtual(Cow::Borrowed("<test>")), $from, $to),
+				ExprLocation(
+					Source::new_virtual(Cow::Borrowed("<test>"), IStr::empty()),
+					$from,
+					$to,
+				),
 			)
 		};
 	}
@@ -710,18 +715,13 @@
 	#[test]
 	fn default_param_before_nondefault() {
 		parse!("local x(foo = 'foo', bar) = null; null");
-	}
-
-	#[test]
-	fn can_parse_stdlib() {
-		parse!(jrsonnet_stdlib::STDLIB_STR);
 	}
 
 	#[test]
 	fn add_location_info_to_all_sub_expressions() {
 		use Expr::*;
 
-		let file_name = Source::new_virtual(Cow::Borrowed("<test>"));
+		let file_name = Source::new_virtual(Cow::Borrowed("<test>"), IStr::empty());
 		let expr = parse(
 			"{} { local x = 1, x: x } + {}",
 			&ParserSettings { file_name },
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -8,7 +8,7 @@
 use jrsonnet_evaluator::{
 	error::{Error::*, Result},
 	function::{builtin::Builtin, ArgLike, CallLocation, FuncVal, TlaArg},
-	gc::TraceBox,
+	gc::{GcHashMap, TraceBox},
 	tb, throw_runtime,
 	typed::{Any, Either, Either2, Either4, VecVal, M1},
 	val::{equals, ArrValue},
@@ -201,6 +201,8 @@
 	pub ext_vars: HashMap<IStr, TlaArg>,
 	/// Used for `std.native`
 	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,
+	/// Helper to add globals without implementing custom ContextInitializer
+	pub globals: GcHashMap<IStr, Thunk<Val>>,
 	/// Used for `std.trace`
 	pub trace_printer: Box<dyn TracePrinter>,
 }
@@ -210,6 +212,7 @@
 		Self {
 			ext_vars: Default::default(),
 			ext_natives: Default::default(),
+			globals: Default::default(),
 			trace_printer: Box::new(StdTracePrinter),
 		}
 	}
@@ -289,7 +292,17 @@
 impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {
 	#[cfg(not(feature = "legacy-this-file"))]
 	fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {
-		self.context.clone()
+		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()
 	}
 	#[cfg(feature = "legacy-this-file")]
 	fn initialize(&self, s: State, source: Source) -> jrsonnet_evaluator::Context {
@@ -316,6 +329,9 @@
 			"std".into(),
 			Thunk::evaluated(Val::Obj(stdlib_with_this_file)),
 		);
+		for (k, v) in &self.settings().globals {
+			context.bind(k.clone(), v.clone())
+		}
 		context.build()
 	}
 	unsafe fn as_any(&self) -> &dyn std::any::Any {
@@ -513,3 +529,25 @@
 		_ => throw_runtime!("both arguments should be of the same type"),
 	})
 }
+
+pub trait StateExt {
+	/// This method was previously implemented in jrsonnet-evaluator itself
+	fn with_stdlib(&self);
+	fn add_global(&self, name: IStr, value: Thunk<Val>);
+}
+
+impl StateExt for State {
+	fn with_stdlib(&self) {
+		let initializer = ContextInitializer::new(self.clone());
+		self.settings_mut().context_initializer = Box::new(initializer)
+	}
+	fn add_global(&self, name: IStr, value: Thunk<Val>) {
+		// Safety:
+		unsafe { self.settings().context_initializer.as_any() }
+			.downcast_ref::<ContextInitializer>()
+			.expect("not standard context initializer")
+			.settings_mut()
+			.globals
+			.insert(name, value);
+	}
+}
addedtests/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/tests/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "tests"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+jrsonnet-evaluator = { path = "../crates/jrsonnet-evaluator" }
+jrsonnet-gcmodule = "0.3.4"
+jrsonnet-stdlib = { path = "../crates/jrsonnet-stdlib" }
+serde = "1.0.142"
addedtests/golden/array_comp.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/array_comp.jsonnet
@@ -0,0 +1 @@
+[[a, b] for a in [1, 2, 3] for b in [4, 5, 6]]
addedtests/golden/array_comp.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/array_comp.jsonnet.golden
@@ -0,0 +1,38 @@
+[
+   [
+      1,
+      4
+   ],
+   [
+      1,
+      5
+   ],
+   [
+      1,
+      6
+   ],
+   [
+      2,
+      4
+   ],
+   [
+      2,
+      5
+   ],
+   [
+      2,
+      6
+   ],
+   [
+      3,
+      4
+   ],
+   [
+      3,
+      5
+   ],
+   [
+      3,
+      6
+   ]
+]
\ No newline at end of file
addedtests/golden/builtin_json.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json.jsonnet
@@ -0,0 +1 @@
+std.manifestJsonEx({ a: 3, b: 4, c: 6 }, '')
addedtests/golden/builtin_json.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json.jsonnet.golden
@@ -0,0 +1 @@
+"{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}"
\ No newline at end of file
addedtests/golden/builtin_json_minified.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json_minified.jsonnet
@@ -0,0 +1 @@
+std.manifestJsonMinified({ a: 3, b: 4, c: 6 })
addedtests/golden/builtin_json_minified.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json_minified.jsonnet.golden
@@ -0,0 +1 @@
+"{\"a\":3,\"b\":4,\"c\":6}"
\ No newline at end of file
addedtests/golden/builtin_parseJson.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_parseJson.jsonnet
@@ -0,0 +1 @@
+std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')
addedtests/golden/builtin_parseJson.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_parseJson.jsonnet.golden
@@ -0,0 +1,6 @@
+{
+   "a": -1,
+   "b": 1,
+   "c": 3.141,
+   "d": [ ]
+}
\ No newline at end of file
addedtests/golden/issue23.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue23.jsonnet
@@ -0,0 +1 @@
+import 'issue23.jsonnet'
addedtests/golden/issue23.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue23.jsonnet.golden
@@ -0,0 +1,2 @@
+infinite recursion detected
+   issue23.jsonnet:1:1-26: import "issue23.jsonnet"
\ No newline at end of file
addedtests/golden/issue40.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue40.jsonnet
@@ -0,0 +1,9 @@
+local conf = {
+  n: '',
+};
+
+local result = conf {
+  assert std.isNumber(self.n) : 'is number',
+};
+
+std.manifestJsonEx(result, '')
addedtests/golden/issue40.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue40.jsonnet.golden
@@ -0,0 +1,3 @@
+assert failed: is number
+   issue40.jsonnet:6:10-31: assertion failure
+   issue40.jsonnet:9:1-32:  function <builtin_manifest_json_ex> call
\ No newline at end of file
addedtests/golden/missing_binding.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/missing_binding.jsonnet
@@ -0,0 +1 @@
+sta
addedtests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/missing_binding.jsonnet.golden
@@ -0,0 +1,3 @@
+variable is not defined: sta
+There is variable with similar name present: std
+   missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
addedtests/golden/object_comp.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/object_comp.jsonnet
@@ -0,0 +1 @@
+{ local t = 'a', ['h' + i + '_' + z]: if 'h' + (i - 1) + '_' + z in self then t + 1 else 0 + t for i in [1, 2, 3] for z in [2, 3, 4] if z != i }
addedtests/golden/object_comp.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/object_comp.jsonnet.golden
@@ -0,0 +1,9 @@
+{
+   "h1_2": "0a",
+   "h1_3": "0a",
+   "h1_4": "0a",
+   "h2_3": "a1",
+   "h2_4": "a1",
+   "h3_2": "0a",
+   "h3_4": "a1"
+}
\ No newline at end of file
addedtests/golden/test_assertThrow.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/test_assertThrow.jsonnet
@@ -0,0 +1,2 @@
+// Test that test.assertThrow will return error, if body is not errored
+test.assertThrow(1, '1')
addedtests/golden/test_assertThrow.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/test_assertThrow.jsonnet.golden
@@ -0,0 +1,2 @@
+runtime error: expected argument to throw on evaluation, but it returned instead
+   test_assertThrow.jsonnet:2:1-26: function <assert_throw> call
\ No newline at end of file
addedtests/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+	left + right
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn it_works() {
+		let result = add(2, 2);
+		assert_eq!(result, 4);
+	}
+}
addedtests/suite/builtin_ascii.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_ascii.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(std.asciiUpper('aBc😀'), 'ABC😀') &&
+std.assertEqual(std.asciiLower('aBc😀'), 'abc😀') &&
+true
addedtests/suite/builtin_base64.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_base64.jsonnet
@@ -0,0 +1,2 @@
+std.assertEqual(std.base64('test'), 'dGVzdA==') &&
+true
addedtests/suite/builtin_chars.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_chars.jsonnet
@@ -0,0 +1,3 @@
+local c = '😎';
+std.assertEqual({ c: std.codepoint(c), l: std.length(c) }, { c: 128526, l: 1 }) &&
+true
addedtests/suite/builtin_constant.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_constant.jsonnet
@@ -0,0 +1,3 @@
+local std2 = std; local std = std2 { primitiveEquals(a, b):: false };
+// In jsonnet, this expression was failing because of being desugared to std.primitiveEquals(1, 1)
+std.assertEqual(1 == 1, true)
addedtests/suite/builtin_count.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_count.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(std.count([], ''), 0) &&
+std.assertEqual(std.count(['a', 'b', 'a'], 'd'), 0) &&
+std.assertEqual(std.count(['a', 'b', 'a'], 'a'), 2) &&
+true
addedtests/suite/builtin_join.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_join.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(std.join([0, 0], [[1, 2], [3, 4], [5, 6]]), [1, 2, 0, 0, 3, 4, 0, 0, 5, 6]) &&
+std.assertEqual(std.join(',', ['1', '2', '3', '4']), '1,2,3,4') &&
+std.assertEqual(std.join(',', ['1', null, '2', null, '3']), '1,2,3') &&
+true
addedtests/suite/builtin_member.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_member.jsonnet
@@ -0,0 +1,7 @@
+!std.member('', '') &&
+std.member('abc', 'a') &&
+!std.member('abc', 'd') &&
+!std.member([], '') &&
+std.member(['a', 'b', 'c'], 'a') &&
+!std.member(['a', 'b', 'c'], 'd') &&
+true
addedtests/suite/function_args.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/function_args.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(local a = function(b, c=2) b + c; a(2), 4) &&
+std.assertEqual(local a = function(b, c='Dear') b + c + d, d = 'World'; a('Hello'), 'HelloDearWorld') &&
+true
addedtests/suite/function_context.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/function_context.jsonnet
@@ -0,0 +1,10 @@
+local k = {
+  t(name=self.h): [self.h, name],
+  h: 3,
+};
+local f = {
+  t: k.t(),
+  h: 4,
+};
+std.assertEqual(f.t[0], f.t[1]) &&
+true
addedtests/suite/function_lazy_args.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/function_lazy_args.jsonnet
@@ -0,0 +1,5 @@
+local fun(a) = 2;
+std.assertEqual(fun(error '3'), 2) &&
+// But in tailstrict mode arguments are evaluated eagerly
+test.assertThrow(fun(error '3') tailstrict, 'runtime error: 3') &&
+true
addedtests/suite/local.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/local.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(local a = 2; local b = 3; a + b, 5) &&
+std.assertEqual(local a = 1, b = a + 1; a + b, 3) &&
+std.assertEqual(local a = 1; local a = 2; a, 2) &&
+true
addedtests/suite/math.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/math.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(2 + 2 * 2, 6) &&
+std.assertEqual(3 + (2 + 2 * 2), 9) &&
+true
addedtests/suite/object_assertion.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_assertion.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual({ assert 'a' in self : 'missing a' } + { a: 2 }, { a: 2 }) &&
+test.assertThrow({ assert 'a' in self : 'missing a', b: 1 }.b, 'assert failed: missing a') &&
+true
addedtests/suite/object_comp_self.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_comp_self.jsonnet
@@ -0,0 +1,8 @@
+std.assertEqual(std.objectFields({
+  a: {
+    [name]: name
+    for name in std.objectFields(self)
+  },
+  b: 2,
+  c: 3,
+}.a), ['a', 'b', 'c'])
addedtests/suite/object_context.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_context.jsonnet
@@ -0,0 +1,13 @@
+// `self` assigned to `me` was lost when being
+// referenced from field
+std.assertEqual({
+  local me = self,
+  a: 3,
+  b: me.a,
+}.b, 3) &&
+std.assertEqual({
+  local me = self,
+  a: 3,
+  b(): me.a,
+}.b(), 3) &&
+true
addedtests/suite/object_fields.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_fields.jsonnet
@@ -0,0 +1,4 @@
+local a = 'a', b = null;
+std.assertEqual({ [a]: 2 }, { a: 2 }) &&
+std.assertEqual({ [b]: 2 }, {}) &&
+true
addedtests/suite/object_inheritance.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_inheritance.jsonnet
@@ -0,0 +1,17 @@
+std.assertEqual({ a: self.b } + { b: 3 }, { a: 3, b: 3 }) &&
+std.assertEqual(
+  {
+    name: 'Alice',
+    welcome: 'Hello ' + self.name + '!',
+  },
+  { name: 'Alice', welcome: 'Hello Alice!' },
+) &&
+std.assertEqual(
+  {
+    name: 'Alice',
+    welcome: 'Hello ' + self.name + '!',
+  } + {
+    name: 'Bob',
+  }, { name: 'Bob', welcome: 'Hello Bob!' }
+) &&
+true
addedtests/suite/object_locals.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_locals.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual({ local a = 3, b: a }, { b: 3 }) &&
+std.assertEqual({ local a = 3, local c = a, b: c }, { b: 3 }) &&
+std.assertEqual({ local a = function(b) { [b]: 4 }, test: a('test') }, { test: { test: 4 } }) &&
+true
addedtests/suite/object_super_standalone.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_super_standalone.jsonnet
@@ -0,0 +1,11 @@
+local obj = {
+  a: 1,
+  b: 2,
+  c: 3,
+};
+local test = obj + {
+  fields: std.objectFields(super),
+  d: 5,
+};
+std.assertEqual(test.fields, ['a', 'b', 'c']) &&
+true
addedtests/suite/sjsonnet_issue_127.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/sjsonnet_issue_127.jsonnet
@@ -0,0 +1,6 @@
+local myFunc = function(a)
+  if (a) then "a" else "b";
+
+local b = "aaa";
+
+std.assertEqual(myFunc(b == [] || b == ['e']), "b")
addedtests/suite/string_concat.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/string_concat.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual('Hello' + 'World', 'HelloWorld') &&
+std.assertEqual('Hello' * 3, 'HelloHelloHello') &&
+std.assertEqual('Hello' + 'World' * 3, 'HelloWorldWorldWorld') &&
+true
addedtests/tests/as_native.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/as_native.rs
@@ -0,0 +1,20 @@
+use jrsonnet_evaluator::{error::Result, State};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+#[test]
+fn as_native() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#)?;
+	let func = val.as_func().expect("this is function");
+
+	let native = func.into_native::<((u32, u32), u32)>();
+
+	ensure_eq!(native(s.clone(), 1, 2)?, 3);
+	ensure_eq!(native(s, 3, 4)?, 7);
+
+	Ok(())
+}
addedtests/tests/builtin.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/builtin.rs
@@ -0,0 +1,94 @@
+mod common;
+
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
+	tb,
+	typed::Typed,
+	Context, State, Thunk, Val,
+};
+use jrsonnet_gcmodule::Cc;
+use jrsonnet_stdlib::StateExt;
+
+#[builtin]
+fn a() -> Result<u32> {
+	Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+	let s = State::default();
+	let a: a = a {};
+	let v = u32::from_untyped(
+		a.call(s.clone(), Context::new(), CallLocation::native(), &())?,
+		s,
+	)?;
+
+	ensure_eq!(v, 1);
+	Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+	Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	s.add_global(
+		"nativeAdd".into(),
+		Thunk::evaluated(Val::Func(FuncVal::StaticBuiltin(native_add::INST))),
+	);
+
+	let v = s.evaluate_snippet(
+		"snip".to_owned(),
+		"
+            assert nativeAdd(1, 2) == 3;
+            assert nativeAdd(100, 200) == 300;
+            null
+        ",
+	)?;
+	ensure_val_eq!(s, v, Val::Null);
+	Ok(())
+}
+
+#[builtin(fields(
+    a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+	Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+	Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	s.add_global(
+		"curryAdd".into(),
+		Thunk::evaluated(Val::Func(FuncVal::StaticBuiltin(curry_add::INST))),
+	);
+
+	let v = s.evaluate_snippet(
+		"snip".to_owned(),
+		"
+            local a = curryAdd(1);
+            local b = curryAdd(4);
+
+            assert a(2) == 3;
+            assert a(200) == 201;
+
+            assert b(2) == 6;
+            assert b(200) == 204;
+            null
+        ",
+	)?;
+	ensure_val_eq!(s, v, Val::Null);
+	Ok(())
+}
addedtests/tests/common.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/common.rs
@@ -0,0 +1,77 @@
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, FuncVal},
+	throw_runtime, ObjValueBuilder, State, Thunk, Val,
+};
+use jrsonnet_stdlib::StateExt;
+
+#[macro_export]
+macro_rules! ensure_eq {
+	($a:expr, $b:expr $(,)?) => {{
+		let a = &$a;
+		let b = &$b;
+		if a != b {
+			::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
+		}
+	}};
+}
+
+#[macro_export]
+macro_rules! ensure {
+	($v:expr $(,)?) => {
+		if !$v {
+			::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v))
+		}
+	};
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+	($s:expr, $a:expr, $b:expr) => {{
+		if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+			::jrsonnet_evaluator::throw_runtime!(
+				"assertion failed: a != b\na={:#?}\nb={:#?}",
+				$a.to_json(
+					$s.clone(),
+					2,
+					#[cfg(feature = "exp-preserve-order")]
+					false
+				)?,
+				$b.to_json(
+					$s.clone(),
+					2,
+					#[cfg(feature = "exp-preserve-order")]
+					false
+				)?,
+			)
+		}
+	}};
+}
+
+#[builtin]
+fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
+	match lazy.evaluate(s) {
+		Ok(_) => {
+			throw_runtime!("expected argument to throw on evaluation, but it returned instead")
+		}
+		Err(e) => {
+			let error = format!("{}", e.error());
+			ensure_eq!(message, error);
+		}
+	}
+	Ok(true)
+}
+
+#[allow(dead_code)]
+pub fn with_test(s: &State) {
+	let mut bobj = ObjValueBuilder::new();
+	bobj.member("assertThrow".into())
+		.hide()
+		.value(
+			s.clone(),
+			Val::Func(FuncVal::StaticBuiltin(assert_throw::INST)),
+		)
+		.expect("no error");
+
+	s.add_global("test".into(), Thunk::evaluated(Val::Obj(bobj.build())))
+}
addedtests/tests/golden.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/golden.rs
@@ -0,0 +1,70 @@
+use std::{
+	fs, io,
+	path::{Path, PathBuf},
+};
+
+use jrsonnet_evaluator::{
+	trace::{CompactFormat, PathResolver},
+	FileImportResolver, State,
+};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+fn run(root: &Path, file: &Path) -> String {
+	let s = State::default();
+	s.set_trace_format(Box::new(CompactFormat {
+		resolver: PathResolver::Relative(root.to_owned()),
+		padding: 3,
+	}));
+	s.with_stdlib();
+	common::with_test(&s);
+	s.set_import_resolver(Box::new(FileImportResolver::default()));
+
+	let v = match s.import(root, &file.display().to_string()) {
+		Ok(v) => v,
+		Err(e) => return s.stringify_err(&e),
+	};
+	match v.to_json(
+		s.clone(),
+		3,
+		#[cfg(feature = "exp-preserve-order")]
+		false,
+	) {
+		Ok(v) => v.to_string(),
+		Err(e) => s.stringify_err(&e),
+	}
+}
+
+#[test]
+fn test() -> io::Result<()> {
+	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+	root.push("golden");
+
+	for entry in fs::read_dir(&root)? {
+		let entry = entry?;
+		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+			continue;
+		}
+
+		let result = run(&root, &entry.path());
+
+		let mut golden_path = entry.path();
+		golden_path.set_extension("jsonnet.golden");
+
+		if !golden_path.exists() {
+			fs::write(golden_path, &result)?;
+		} else {
+			let golden = fs::read_to_string(golden_path)?;
+
+			assert_eq!(
+				result,
+				golden,
+				"golden didn't match for {}",
+				entry.path().display()
+			)
+		}
+	}
+
+	Ok(())
+}
addedtests/tests/sanity.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/sanity.rs
@@ -0,0 +1,42 @@
+use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+#[test]
+fn assert_positive() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null")?;
+	ensure_val_eq!(s, v, Val::Null);
+	let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)")?;
+	ensure_val_eq!(s, v, Val::Bool(true));
+
+	Ok(())
+}
+
+#[test]
+fn assert_negative() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	{
+		let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null") {
+			Ok(_) => throw_runtime!("assertion should fail"),
+			Err(e) => e,
+		};
+		let e = s.stringify_err(&e);
+		ensure!(e.starts_with("assert failed: fail\n"));
+	}
+	{
+		let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)") {
+			Ok(_) => throw_runtime!("assertion should fail"),
+			Err(e) => e,
+		};
+		let e = s.stringify_err(&e);
+		ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))
+	}
+
+	Ok(())
+}
addedtests/tests/suite.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/suite.rs
@@ -0,0 +1,47 @@
+use std::{
+	fs, io,
+	path::{Path, PathBuf},
+};
+
+use jrsonnet_evaluator::{
+	trace::{CompactFormat, PathResolver},
+	FileImportResolver, State, Val,
+};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+fn run(root: &Path, file: &Path) {
+	let s = State::default();
+	s.set_trace_format(Box::new(CompactFormat {
+		resolver: PathResolver::Relative(root.to_owned()),
+		padding: 3,
+	}));
+	s.with_stdlib();
+	common::with_test(&s);
+	s.set_import_resolver(Box::new(FileImportResolver::default()));
+
+	match s.import(root, &file.display().to_string()) {
+		Ok(Val::Bool(true)) => {}
+		Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()),
+		Ok(_) => panic!("test {} returned wrong type as result", file.display()),
+		Err(e) => panic!("test {} failed:\n{}", file.display(), s.stringify_err(&e)),
+	};
+}
+
+#[test]
+fn test() -> io::Result<()> {
+	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+	root.push("suite");
+
+	for entry in fs::read_dir(&root)? {
+		let entry = entry?;
+		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+			continue;
+		}
+
+		run(&root, &entry.path());
+	}
+
+	Ok(())
+}
addedtests/tests/typed_obj.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/typed_obj.rs
@@ -0,0 +1,189 @@
+mod common;
+
+use std::fmt::Debug;
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+use jrsonnet_stdlib::StateExt;
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+	a: u32,
+	b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+	let untyped = T::into_untyped(value.clone(), s.clone())?;
+	let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+	ensure_eq!(value, value2);
+	let untyped2 = T::into_untyped(value2, s.clone())?;
+	ensure_val_eq!(s, untyped, untyped2);
+
+	Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let a = A::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(a, A { a: 1, b: 2 });
+	test_roundtrip(a, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+	a: u32,
+	#[typed(rename = "c")]
+	b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let b = B::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(b, B { a: 1, b: 2 });
+	ensure_eq!(
+		&B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"a": 1, "c": 2}"#,
+	);
+	test_roundtrip(b, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+	#[typed(rename = "apiVersion")]
+	api_version: String,
+	#[typed(rename = "kind")]
+	kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+	#[typed(flatten)]
+	kind: ObjectKind,
+	b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let obj = Object::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{apiVersion: 'ver', kind: 'kind', b: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(
+		obj,
+		Object {
+			kind: ObjectKind {
+				api_version: "ver".into(),
+				kind: "kind".into(),
+			},
+			b: 2
+		}
+	);
+	ensure_eq!(
+		&Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+	);
+	test_roundtrip(obj, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+	a: Option<u32>,
+	b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let c = C::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(c, C { a: Some(1), b: 2 });
+	ensure_eq!(
+		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"a": 1, "b": 2}"#,
+	);
+	test_roundtrip(c, s)?;
+	Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let c = C::from_untyped(s.evaluate_snippet("snip".to_owned(), "{b: 2}")?, s.clone())?;
+	ensure_eq!(c, C { a: None, b: 2 });
+	ensure_eq!(
+		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2}"#,
+	);
+	test_roundtrip(c, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+	#[typed(flatten(ok))]
+	e: Option<E>,
+	b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+	v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let d = D::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(
+		d,
+		D {
+			e: Some(E { v: 1 }),
+			b: 2
+		}
+	);
+	ensure_eq!(
+		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2, "v": 1}"#,
+	);
+	test_roundtrip(d, s)?;
+	Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let d = D::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(d, D { e: None, b: 2 });
+	ensure_eq!(
+		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2}"#,
+	);
+	test_roundtrip(d, s)?;
+	Ok(())
+}