diff --git a/nix-js/runtime-ts/src/builtins/index.ts b/nix-js/runtime-ts/src/builtins/index.ts index 129189b..786808d 100644 --- a/nix-js/runtime-ts/src/builtins/index.ts +++ b/nix-js/runtime-ts/src/builtins/index.ts @@ -249,7 +249,7 @@ export const builtins: any = { builtins: createThunk(() => builtins), currentSystem: createThunk(() => { - throw new Error("Not implemented: currentSystem"); + return "x86_64-linux" }), currentTime: createThunk(() => Date.now()), diff --git a/nix-js/runtime-ts/src/builtins/misc.ts b/nix-js/runtime-ts/src/builtins/misc.ts index a7aa403..da9d350 100644 --- a/nix-js/runtime-ts/src/builtins/misc.ts +++ b/nix-js/runtime-ts/src/builtins/misc.ts @@ -4,7 +4,8 @@ import { force } from "../thunk"; import { CatchableError } from "../types"; -import type { NixBool, NixStrictValue, NixValue, NixString } from "../types"; +import type { NixBool, NixStrictValue, NixValue } from "../types"; +import { forceList, forceAttrs, forceFunction } from "../type-assert"; import * as context from "./context"; export const addErrorContext = @@ -138,6 +139,34 @@ export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue export const zipAttrsWith = (f: NixValue) => - (list: NixValue): never => { - throw new Error("Not implemented: zipAttrsWith"); + (list: NixValue): NixValue => { + const listForced = forceList(list); + + // Map to collect all values for each attribute name + const attrMap = new Map(); + + // Iterate through each attribute set in the list + for (const item of listForced) { + const attrs = forceAttrs(force(item) as NixValue); + + // Collect all attribute names and their values + for (const [key, value] of Object.entries(attrs)) { + if (!attrMap.has(key)) { + attrMap.set(key, []); + } + attrMap.get(key)!.push(value); + } + } + + // Build the result attribute set + const result: Record = {}; + + for (const [name, values] of attrMap.entries()) { + // Apply f to name and values list + // f is curried: f name values + const fWithName = forceFunction(f)(name); + result[name] = forceFunction(fWithName)(values); + } + + return result; }; diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index a8b5070..0bbc8e0 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -72,15 +72,15 @@ export const select = (obj: NixValue, key: NixValue): NixValue => { * * @param obj - Attribute set to select from * @param key - Key to select - * @param default_val - Value to return if key not found - * @returns obj[key] if exists, otherwise default_val + * @param default_val - Value to return if key not found (will be forced if it's a thunk) + * @returns obj[key] if exists, otherwise force(default_val) */ export const selectWithDefault = (obj: NixValue, key: NixValue, default_val: NixValue): NixValue => { const attrs = forceAttrs(obj); const forced_key = forceString(key); if (!(forced_key in attrs)) { - return default_val; + return force(default_val); } return attrs[forced_key]; diff --git a/nix-js/src/fetcher/mod.rs b/nix-js/src/fetcher.rs similarity index 99% rename from nix-js/src/fetcher/mod.rs rename to nix-js/src/fetcher.rs index b00bff4..87663ce 100644 --- a/nix-js/src/fetcher/mod.rs +++ b/nix-js/src/fetcher.rs @@ -8,8 +8,6 @@ mod nar; pub use cache::FetcherCache; pub use download::Downloader; -use std::path::PathBuf; - use deno_core::op2; use serde::Serialize; diff --git a/nix-js/src/ir/downgrade.rs b/nix-js/src/ir/downgrade.rs index 551d0df..64d210a 100644 --- a/nix-js/src/ir/downgrade.rs +++ b/nix-js/src/ir/downgrade.rs @@ -245,7 +245,8 @@ impl Downgrade for ast::Select { let expr = self.expr().unwrap().downgrade(ctx)?; let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; let default = if let Some(default) = self.default_expr() { - Some(default.downgrade(ctx)?) + let default_expr = default.downgrade(ctx)?; + Some(ctx.new_expr(Ir::Thunk(default_expr))) } else { None }; diff --git a/nix-js/tests/operators.rs b/nix-js/tests/operators.rs index 32dd2cd..4c2e45f 100644 --- a/nix-js/tests/operators.rs +++ b/nix-js/tests/operators.rs @@ -99,3 +99,21 @@ fn logical_not() { assert_eq!(eval("!true"), Value::Bool(false)); assert_eq!(eval("!false"), Value::Bool(true)); } + +#[test] +fn select_with_default_lazy_evaluation() { + assert_eq!(eval("{ a = 1; }.a or (1 / 0)"), Value::Int(1)); +} + +#[test] +fn select_with_default_nested_lazy() { + assert_eq!( + eval("{ a.b = 42; }.a.b or (builtins.abort \"should not evaluate\")"), + Value::Int(42) + ); +} + +#[test] +fn select_with_default_fallback() { + assert_eq!(eval("{ a = 1; }.b or 999"), Value::Int(999)); +}