diff --git a/nix-js/runtime-ts/src/index.ts b/nix-js/runtime-ts/src/index.ts index 3395bf5..c45fd6e 100644 --- a/nix-js/runtime-ts/src/index.ts +++ b/nix-js/runtime-ts/src/index.ts @@ -89,3 +89,4 @@ globalThis.$og = op.gt; globalThis.$oc = op.concat; globalThis.$ou = op.update; globalThis.$b = builtins; +globalThis.$e = new Map(); diff --git a/nix-js/runtime-ts/src/print.ts b/nix-js/runtime-ts/src/print.ts index 477f1a2..4dba4d3 100644 --- a/nix-js/runtime-ts/src/print.ts +++ b/nix-js/runtime-ts/src/print.ts @@ -43,11 +43,6 @@ export const printValue = (value: NixValue, seen: WeakSet = new WeakSet( return "«repeated»"; } - if (seen.has(value)) { - return "«repeated»"; - } - seen.add(value); - if (isNixPath(value)) { return value.value; } @@ -57,10 +52,23 @@ export const printValue = (value: NixValue, seen: WeakSet = new WeakSet( } if (Array.isArray(value)) { + if (value.length > 0) { + if (seen.has(value)) { + return "«repeated»"; + } + seen.add(value); + } const items = value.map((v) => printValue(v, seen)).join(" "); return `[ ${items} ]`; } + if (seen.has(value)) { + return "«repeated»"; + } + if (value.size > 0) { + seen.add(value); + } + const entries = [...value.entries()] .map(([k, v]) => `${printSymbol(k)} = ${printValue(v, seen)};`) .join(" "); diff --git a/nix-js/runtime-ts/src/thunk.ts b/nix-js/runtime-ts/src/thunk.ts index 4ebe6c5..5ad2b38 100644 --- a/nix-js/runtime-ts/src/thunk.ts +++ b/nix-js/runtime-ts/src/thunk.ts @@ -132,12 +132,9 @@ export const forceDeep = (value: NixValue, seen: WeakSet = new WeakSet() } if (seen.has(forced)) { - if (Array.isArray(forced)) { - return [CYCLE_MARKER]; - } return CYCLE_MARKER; } - if (isAttrs(forced) || isList(forced)) { + if ((isAttrs(forced) && forced.size > 0) || (isList(forced) && forced.length > 0)) { seen.add(forced); } diff --git a/nix-js/runtime-ts/src/types/global.d.ts b/nix-js/runtime-ts/src/types/global.d.ts index b2c8acf..98ca49f 100644 --- a/nix-js/runtime-ts/src/types/global.d.ts +++ b/nix-js/runtime-ts/src/types/global.d.ts @@ -43,6 +43,7 @@ declare global { var $oc: typeof op.concat; var $ou: typeof op.update; var $b: typeof builtins; + var $e: NixAttrs; var $gb: typeof Nix.getReplBinding; namespace Deno { diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index 196271c..4948e9c 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -294,7 +294,7 @@ impl Compile for Ir<'_> { ")" ); } - Ir::WithExpr(x) => x.compile(ctx, buf), + Ir::With(x) => x.compile(ctx, buf), &Ir::WithLookup(WithLookup { inner: name, .. }) => { // Nix.lookupWith code!(buf, ctx; @@ -487,7 +487,7 @@ impl Compile for TopLevel<'_> { } } -impl Compile for WithExpr<'_> { +impl Compile for With<'_> { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { let namespace = self.namespace; let body = self.body; @@ -585,7 +585,7 @@ impl Compile for AttrSet<'_> { "]))" ); } else { - code!(buf, ctx; "new Map()"); + code!(buf, ctx; "$e"); } } } diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index a4f56d6..29d9398 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -2,7 +2,7 @@ use std::cell::UnsafeCell; use std::path::Path; use bumpalo::Bump; -use hashbrown::{HashMap, HashSet}; +use hashbrown::{DefaultHashBuilder, HashMap, HashSet}; use rnix::TextRange; use string_interner::DefaultStringInterner; @@ -10,8 +10,8 @@ use crate::codegen::{CodegenContext, compile, compile_scoped}; use crate::downgrade::*; use crate::error::{Error, Result, Source}; use crate::ir::{ - Arg, ArgId, Bool, Builtin, Ir, IrRef, Null, ReplBinding, ScopedImportBinding, SymId, Thunk, - ThunkId, ToIr as _, WithLookup, + Arg, ArgId, Bool, Builtin, Ir, IrKey, IrRef, Null, ReplBinding, ScopedImportBinding, SymId, + Thunk, ThunkId, ToIr as _, WithLookup, }; #[cfg(feature = "inspector")] use crate::runtime::inspector::InspectorServer; @@ -450,6 +450,38 @@ impl<'ir, 'ctx> ScopeGuard<'_, 'ctx, 'ir> { } } +struct ThunkScope<'ir> { + bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>, + cache: HashMap, ThunkId>, + hasher: DefaultHashBuilder, +} + +impl<'ir> ThunkScope<'ir> { + fn new_in(bump: &'ir Bump) -> Self { + Self { + bindings: bumpalo::collections::Vec::new_in(bump), + cache: HashMap::new(), + hasher: DefaultHashBuilder::default(), + } + } + + fn lookup_cache(&self, key: IrKey<'ir>) -> Option { + self.cache.get(&key).copied() + } + + fn add_binding(&mut self, id: ThunkId, ir: IrRef<'ir>) { + self.bindings.push((id, ir)); + } + + fn extend_bindings(&mut self, iter: impl IntoIterator)>) { + self.bindings.extend(iter); + } + + fn add_cache(&mut self, key: IrKey<'ir>, cache: ThunkId) { + self.cache.insert(key, cache); + } +} + struct DowngradeCtx<'ctx, 'ir> { bump: &'ir Bump, symbols: &'ctx mut DefaultStringInterner, @@ -458,7 +490,7 @@ struct DowngradeCtx<'ctx, 'ir> { with_scope_count: usize, arg_count: usize, thunk_count: &'ctx mut usize, - thunk_scopes: Vec)>>, + thunk_scopes: Vec>, } fn should_thunk(ir: IrRef<'_>) -> bool { @@ -494,7 +526,7 @@ impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> { thunk_count, arg_count: 0, with_scope_count: 0, - thunk_scopes: vec![bumpalo::collections::Vec::new_in(bump)], + thunk_scopes: vec![ThunkScope::new_in(bump)], } } } @@ -517,13 +549,22 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { fn maybe_thunk(&mut self, ir: IrRef<'ir>) -> IrRef<'ir> { if should_thunk(ir) { + let scope = self.thunk_scopes.last_mut().expect("no active cache scope"); + let key = IrKey(ir); + if let Some(id) = scope.lookup_cache(key) { + return self.new_expr( + Thunk { + inner: id, + span: ir.span(), + } + .to_ir(), + ); + } let span = ir.span(); let id = ThunkId(*self.thunk_count); *self.thunk_count += 1; - self.thunk_scopes - .last_mut() - .expect("no active thunk scope") - .push((id, ir)); + scope.add_binding(id, ir); + scope.add_cache(key, id); self.new_expr(Thunk { inner: id, span }.to_ir()) } else { ir @@ -601,7 +642,7 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { }; assert_eq!(keys.len(), vals.len()); let scope = self.thunk_scopes.last_mut().expect("no active thunk scope"); - scope.extend((base..base + keys.len()).map(ThunkId).zip(vals)); + scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals)); Ok(ret) } @@ -631,12 +672,14 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> { where F: FnOnce(&mut Self) -> R, { - self.thunk_scopes - .push(bumpalo::collections::Vec::new_in(self.bump)); + self.thunk_scopes.push(ThunkScope::new_in(self.bump)); let ret = f(self); ( ret, - self.thunk_scopes.pop().expect("no thunk scope left???"), + self.thunk_scopes + .pop() + .expect("no thunk scope left???") + .bindings, ) } @@ -650,7 +693,11 @@ impl<'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'ir> { use crate::ir::TopLevel; let body = root.downgrade(&mut self)?; let span = body.span(); - let thunks = self.thunk_scopes.pop().expect("no thunk scope left???"); + let thunks = self + .thunk_scopes + .pop() + .expect("no thunk scope left???") + .bindings; Ok(self.new_expr(TopLevel { body, thunks, span }.to_ir())) } } diff --git a/nix-js/src/downgrade.rs b/nix-js/src/downgrade.rs index a882300..1a5f95f 100644 --- a/nix-js/src/downgrade.rs +++ b/nix-js/src/downgrade.rs @@ -447,7 +447,7 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::With { let body = body?; Ok(ctx.new_expr( - WithExpr { + With { namespace, body, thunks, diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs index bff7d40..de5f827 100644 --- a/nix-js/src/ir.rs +++ b/nix-js/src/ir.rs @@ -1,3 +1,5 @@ +use std::hash::{Hash, Hasher}; + use bumpalo::{Bump, boxed::Box, collections::Vec}; use rnix::{TextRange, ast}; use string_interner::symbol::SymbolU32; @@ -10,6 +12,7 @@ pub type IrRef<'ir> = &'ir Ir<'ir>; ir! { Ir<'ir>; + // Literals Int(i64), Float(f64), Bool(bool), @@ -17,27 +20,37 @@ ir! { Str { inner: Box<'ir, String> }, AttrSet { stcs: HashMap<'ir, SymId, (IrRef<'ir>, TextRange)>, dyns: Vec<'ir, (IrRef<'ir>, IrRef<'ir>, TextRange)> }, List { items: Vec<'ir, IrRef<'ir>> }, - - HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> }, - BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind }, - UnOp { rhs: IrRef<'ir>, kind: UnOpKind }, - Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option> }, - If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> }, - Call { func: IrRef<'ir>, arg: IrRef<'ir> }, - Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String }, - ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool }, Path { expr: IrRef<'ir> }, + ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool }, + + // OPs + UnOp { rhs: IrRef<'ir>, kind: UnOpKind }, + BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind }, + HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> }, + Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option> }, + + // Conditionals + If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> }, + Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String }, + + With { namespace: IrRef<'ir>, body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, + WithLookup(SymId), + + // Function related Func { body: IrRef<'ir>, param: Option>, arg: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, - TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, Arg(ArgId), - Thunk(ThunkId), + Call { func: IrRef<'ir>, arg: IrRef<'ir> }, + + // Builtins Builtins, Builtin(SymId), + + // Misc + TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, + Thunk(ThunkId), CurPos, ReplBinding(SymId), ScopedImportBinding(SymId), - WithExpr { namespace: IrRef<'ir>, body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> }, - WithLookup(SymId), } #[repr(transparent)] @@ -63,7 +76,7 @@ pub enum Attr<'ir> { } /// The kinds of binary operations supported in Nix. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum BinOpKind { // Arithmetic Add, @@ -120,7 +133,7 @@ impl From for BinOpKind { } /// The kinds of unary operations. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum UnOpKind { Neg, // Negation (`-`) Not, // Logical not (`!`) @@ -142,3 +155,290 @@ pub struct Param<'ir> { pub optional: Vec<'ir, (SymId, TextRange)>, pub ellipsis: bool, } + +#[derive(Clone, Copy)] +pub(crate) struct IrKey<'ir>(pub IrRef<'ir>); + +impl std::hash::Hash for IrKey<'_> { + fn hash(&self, state: &mut H) { + ir_content_hash(self.0, state); + } +} + +impl PartialEq for IrKey<'_> { + fn eq(&self, other: &Self) -> bool { + ir_content_eq(self.0, other.0) + } +} + +impl Eq for IrKey<'_> {} + +fn attr_content_hash(attr: &Attr<'_>, state: &mut impl Hasher) { + core::mem::discriminant(attr).hash(state); + match attr { + Attr::Dynamic(expr, _) => ir_content_hash(expr, state), + Attr::Str(sym, _) => sym.hash(state), + } +} + +fn attr_content_eq(a: &Attr<'_>, b: &Attr<'_>) -> bool { + match (a, b) { + (Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(ae, be), + (Attr::Str(a, _), Attr::Str(b, _)) => a == b, + _ => false, + } +} + +fn param_content_hash(param: &Param<'_>, state: &mut impl Hasher) { + param.required.len().hash(state); + for (sym, _) in param.required.iter() { + sym.hash(state); + } + param.optional.len().hash(state); + for (sym, _) in param.optional.iter() { + sym.hash(state); + } + param.ellipsis.hash(state); +} + +fn param_content_eq(a: &Param<'_>, b: &Param<'_>) -> bool { + a.ellipsis == b.ellipsis + && a.required.len() == b.required.len() + && a.optional.len() == b.optional.len() + && a.required + .iter() + .zip(b.required.iter()) + .all(|((a, _), (b, _))| a == b) + && a.optional + .iter() + .zip(b.optional.iter()) + .all(|((a, _), (b, _))| a == b) +} + +fn thunks_content_hash(thunks: &[(ThunkId, IrRef<'_>)], state: &mut impl Hasher) { + thunks.len().hash(state); + for (id, ir) in thunks { + id.hash(state); + ir_content_hash(ir, state); + } +} + +fn thunks_content_eq(a: &[(ThunkId, IrRef<'_>)], b: &[(ThunkId, IrRef<'_>)]) -> bool { + a.len() == b.len() + && a.iter() + .zip(b.iter()) + .all(|((ai, ae), (bi, be))| ai == bi && ir_content_eq(ae, be)) +} + +fn ir_content_hash(ir: &Ir<'_>, state: &mut impl Hasher) { + core::mem::discriminant(ir).hash(state); + match ir { + Ir::Int(x) => x.inner.hash(state), + Ir::Float(x) => x.inner.to_bits().hash(state), + Ir::Bool(x) => x.inner.hash(state), + Ir::Null(_) => {} + Ir::Str(x) => x.inner.hash(state), + Ir::AttrSet(x) => { + x.stcs.len().hash(state); + let mut combined: u64 = 0; + for (&key, (val, _)) in x.stcs.iter() { + let mut h = std::hash::DefaultHasher::new(); + key.hash(&mut h); + ir_content_hash(val, &mut h); + combined = combined.wrapping_add(h.finish()); + } + combined.hash(state); + x.dyns.len().hash(state); + for (k, v, _) in x.dyns.iter() { + ir_content_hash(k, state); + ir_content_hash(v, state); + } + } + Ir::List(x) => { + x.items.len().hash(state); + for item in x.items.iter() { + ir_content_hash(item, state); + } + } + Ir::HasAttr(x) => { + ir_content_hash(x.lhs, state); + x.rhs.len().hash(state); + for attr in x.rhs.iter() { + attr_content_hash(attr, state); + } + } + Ir::BinOp(x) => { + ir_content_hash(x.lhs, state); + ir_content_hash(x.rhs, state); + x.kind.hash(state); + } + Ir::UnOp(x) => { + ir_content_hash(x.rhs, state); + x.kind.hash(state); + } + Ir::Select(x) => { + ir_content_hash(x.expr, state); + x.attrpath.len().hash(state); + for attr in x.attrpath.iter() { + attr_content_hash(attr, state); + } + x.default.is_some().hash(state); + if let Some(d) = x.default { + ir_content_hash(d, state); + } + } + Ir::If(x) => { + ir_content_hash(x.cond, state); + ir_content_hash(x.consq, state); + ir_content_hash(x.alter, state); + } + Ir::Call(x) => { + ir_content_hash(x.func, state); + ir_content_hash(x.arg, state); + } + Ir::Assert(x) => { + ir_content_hash(x.assertion, state); + ir_content_hash(x.expr, state); + x.assertion_raw.hash(state); + } + Ir::ConcatStrings(x) => { + x.force_string.hash(state); + x.parts.len().hash(state); + for part in x.parts.iter() { + ir_content_hash(part, state); + } + } + Ir::Path(x) => ir_content_hash(x.expr, state), + Ir::Func(x) => { + ir_content_hash(x.body, state); + ir_content_hash(x.arg, state); + x.param.is_some().hash(state); + if let Some(p) = &x.param { + param_content_hash(p, state); + } + thunks_content_hash(&x.thunks, state); + } + Ir::TopLevel(x) => { + ir_content_hash(x.body, state); + thunks_content_hash(&x.thunks, state); + } + Ir::Arg(x) => x.inner.hash(state), + Ir::Thunk(x) => x.inner.hash(state), + Ir::Builtins(_) => {} + Ir::Builtin(x) => x.inner.hash(state), + Ir::CurPos(x) => x.span.hash(state), + Ir::ReplBinding(x) => x.inner.hash(state), + Ir::ScopedImportBinding(x) => x.inner.hash(state), + Ir::With(x) => { + ir_content_hash(x.namespace, state); + ir_content_hash(x.body, state); + thunks_content_hash(&x.thunks, state); + } + Ir::WithLookup(x) => x.inner.hash(state), + } +} + +fn ir_content_eq(a: &Ir<'_>, b: &Ir<'_>) -> bool { + std::ptr::eq(a, b) + || match (a, b) { + (Ir::Int(a), Ir::Int(b)) => a.inner == b.inner, + (Ir::Float(a), Ir::Float(b)) => a.inner.to_bits() == b.inner.to_bits(), + (Ir::Bool(a), Ir::Bool(b)) => a.inner == b.inner, + (Ir::Null(_), Ir::Null(_)) => true, + (Ir::Str(a), Ir::Str(b)) => *a.inner == *b.inner, + (Ir::AttrSet(a), Ir::AttrSet(b)) => { + a.stcs.len() == b.stcs.len() + && a.dyns.len() == b.dyns.len() + && a.stcs.iter().all(|(&k, (v, _))| { + b.stcs.get(&k).is_some_and(|(bv, _)| ir_content_eq(v, bv)) + }) + && a.dyns + .iter() + .zip(b.dyns.iter()) + .all(|((ak, av, _), (bk, bv, _))| { + ir_content_eq(ak, bk) && ir_content_eq(av, bv) + }) + } + (Ir::List(a), Ir::List(b)) => { + a.items.len() == b.items.len() + && a.items + .iter() + .zip(b.items.iter()) + .all(|(a, b)| ir_content_eq(a, b)) + } + (Ir::HasAttr(a), Ir::HasAttr(b)) => { + ir_content_eq(a.lhs, b.lhs) + && a.rhs.len() == b.rhs.len() + && a.rhs + .iter() + .zip(b.rhs.iter()) + .all(|(a, b)| attr_content_eq(a, b)) + } + (Ir::BinOp(a), Ir::BinOp(b)) => { + a.kind == b.kind && ir_content_eq(a.lhs, b.lhs) && ir_content_eq(a.rhs, b.rhs) + } + (Ir::UnOp(a), Ir::UnOp(b)) => a.kind == b.kind && ir_content_eq(a.rhs, b.rhs), + (Ir::Select(a), Ir::Select(b)) => { + ir_content_eq(a.expr, b.expr) + && a.attrpath.len() == b.attrpath.len() + && a.attrpath + .iter() + .zip(b.attrpath.iter()) + .all(|(a, b)| attr_content_eq(a, b)) + && match (a.default, b.default) { + (Some(a), Some(b)) => ir_content_eq(a, b), + (None, None) => true, + _ => false, + } + } + (Ir::If(a), Ir::If(b)) => { + ir_content_eq(a.cond, b.cond) + && ir_content_eq(a.consq, b.consq) + && ir_content_eq(a.alter, b.alter) + } + (Ir::Call(a), Ir::Call(b)) => { + ir_content_eq(a.func, b.func) && ir_content_eq(a.arg, b.arg) + } + (Ir::Assert(a), Ir::Assert(b)) => { + a.assertion_raw == b.assertion_raw + && ir_content_eq(a.assertion, b.assertion) + && ir_content_eq(a.expr, b.expr) + } + (Ir::ConcatStrings(a), Ir::ConcatStrings(b)) => { + a.force_string == b.force_string + && a.parts.len() == b.parts.len() + && a.parts + .iter() + .zip(b.parts.iter()) + .all(|(a, b)| ir_content_eq(a, b)) + } + (Ir::Path(a), Ir::Path(b)) => ir_content_eq(a.expr, b.expr), + (Ir::Func(a), Ir::Func(b)) => { + ir_content_eq(a.body, b.body) + && ir_content_eq(a.arg, b.arg) + && match (&a.param, &b.param) { + (Some(a), Some(b)) => param_content_eq(a, b), + (None, None) => true, + _ => false, + } + && thunks_content_eq(&a.thunks, &b.thunks) + } + (Ir::TopLevel(a), Ir::TopLevel(b)) => { + ir_content_eq(a.body, b.body) && thunks_content_eq(&a.thunks, &b.thunks) + } + (Ir::Arg(a), Ir::Arg(b)) => a.inner == b.inner, + (Ir::Thunk(a), Ir::Thunk(b)) => a.inner == b.inner, + (Ir::Builtins(_), Ir::Builtins(_)) => true, + (Ir::Builtin(a), Ir::Builtin(b)) => a.inner == b.inner, + (Ir::CurPos(a), Ir::CurPos(b)) => a.span == b.span, + (Ir::ReplBinding(a), Ir::ReplBinding(b)) => a.inner == b.inner, + (Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a.inner == b.inner, + (Ir::With(a), Ir::With(b)) => { + ir_content_eq(a.namespace, b.namespace) + && ir_content_eq(a.body, b.body) + && thunks_content_eq(&a.thunks, &b.thunks) + } + (Ir::WithLookup(a), Ir::WithLookup(b)) => a.inner == b.inner, + _ => false, + } +} diff --git a/nix-js/src/main.rs b/nix-js/src/main.rs index 5b70a73..a054925 100644 --- a/nix-js/src/main.rs +++ b/nix-js/src/main.rs @@ -101,9 +101,9 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> { } else { unreachable!() }; - match context.eval_shallow(src) { + match context.eval_deep(src) { Ok(value) => { - println!("{value}"); + println!("{}", value.display_compat()); } Err(err) => { eprintln!("{:?}", miette::Report::new(*err)); diff --git a/nix-js/tests/tests/lang/eval-okay-intersectAttrs.exp b/nix-js/tests/tests/lang/eval-okay-intersectAttrs.exp index 50445bc..8266ddf 100644 --- a/nix-js/tests/tests/lang/eval-okay-intersectAttrs.exp +++ b/nix-js/tests/tests/lang/eval-okay-intersectAttrs.exp @@ -1 +1 @@ -[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ] +[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } «repeated» «repeated» { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ] diff --git a/nix-js/tests/tests/lang/eval-okay-print.exp b/nix-js/tests/tests/lang/eval-okay-print.exp index 0d960fb..7f3ad37 100644 --- a/nix-js/tests/tests/lang/eval-okay-print.exp +++ b/nix-js/tests/tests/lang/eval-okay-print.exp @@ -1 +1 @@ -[ null [ [ «repeated» ] ] ] +[ null [ «repeated» ] ] diff --git a/nix-js/tests/tests/lang/eval-okay-print.nix b/nix-js/tests/tests/lang/eval-okay-print.nix index 1ad4656..8e3d0ae 100644 --- a/nix-js/tests/tests/lang/eval-okay-print.nix +++ b/nix-js/tests/tests/lang/eval-okay-print.nix @@ -6,6 +6,8 @@ trace toString (deepSeq "x") (a: a) + # [ «repeated» ] instead of [ [ «repeated» ] ] + # matches Lix's behaviour ( let x = [ x ]; diff --git a/nix-js/tests/tests/lang/eval-okay-sort.exp b/nix-js/tests/tests/lang/eval-okay-sort.exp index 899119e..f9f1fa0 100644 --- a/nix-js/tests/tests/lang/eval-okay-sort.exp +++ b/nix-js/tests/tests/lang/eval-okay-sort.exp @@ -1 +1 @@ -[ [ 42 77 147 249 483 526 ] [ 526 483 249 147 77 42 ] [ "bar" "fnord" "foo" "xyzzy" ] [ { key = 1; value = "foo"; } { key = 1; value = "fnord"; } { key = 2; value = "bar"; } ] [ [ ] [ ] [ 1 ] [ 1 4 ] [ 1 5 ] [ 1 6 ] [ 2 ] [ 2 3 ] [ 3 ] [ 3 ] ] ] +[ [ 42 77 147 249 483 526 ] [ 526 483 249 147 77 42 ] [ "bar" "fnord" "foo" "xyzzy" ] [ { key = 1; value = "foo"; } { key = 1; value = "fnord"; } { key = 2; value = "bar"; } ] [ [ ] [ ] [ 1 ] [ 1 4 ] [ 1 5 ] [ 1 6 ] [ 2 ] [ 2 3 ] [ 3 ] «repeated» ] ]