fix: lazy select_with_default
This commit is contained in:
@@ -249,7 +249,7 @@ export const builtins: any = {
|
|||||||
|
|
||||||
builtins: createThunk(() => builtins),
|
builtins: createThunk(() => builtins),
|
||||||
currentSystem: createThunk(() => {
|
currentSystem: createThunk(() => {
|
||||||
throw new Error("Not implemented: currentSystem");
|
return "x86_64-linux"
|
||||||
}),
|
}),
|
||||||
currentTime: createThunk(() => Date.now()),
|
currentTime: createThunk(() => Date.now()),
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { CatchableError } from "../types";
|
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";
|
import * as context from "./context";
|
||||||
|
|
||||||
export const addErrorContext =
|
export const addErrorContext =
|
||||||
@@ -138,6 +139,34 @@ export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue
|
|||||||
|
|
||||||
export const zipAttrsWith =
|
export const zipAttrsWith =
|
||||||
(f: NixValue) =>
|
(f: NixValue) =>
|
||||||
(list: NixValue): never => {
|
(list: NixValue): NixValue => {
|
||||||
throw new Error("Not implemented: zipAttrsWith");
|
const listForced = forceList(list);
|
||||||
|
|
||||||
|
// Map to collect all values for each attribute name
|
||||||
|
const attrMap = new Map<string, NixValue[]>();
|
||||||
|
|
||||||
|
// 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<string, NixValue> = {};
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,15 +72,15 @@ export const select = (obj: NixValue, key: NixValue): NixValue => {
|
|||||||
*
|
*
|
||||||
* @param obj - Attribute set to select from
|
* @param obj - Attribute set to select from
|
||||||
* @param key - Key to select
|
* @param key - Key to select
|
||||||
* @param default_val - Value to return if key not found
|
* @param default_val - Value to return if key not found (will be forced if it's a thunk)
|
||||||
* @returns obj[key] if exists, otherwise default_val
|
* @returns obj[key] if exists, otherwise force(default_val)
|
||||||
*/
|
*/
|
||||||
export const selectWithDefault = (obj: NixValue, key: NixValue, default_val: NixValue): NixValue => {
|
export const selectWithDefault = (obj: NixValue, key: NixValue, default_val: NixValue): NixValue => {
|
||||||
const attrs = forceAttrs(obj);
|
const attrs = forceAttrs(obj);
|
||||||
const forced_key = forceString(key);
|
const forced_key = forceString(key);
|
||||||
|
|
||||||
if (!(forced_key in attrs)) {
|
if (!(forced_key in attrs)) {
|
||||||
return default_val;
|
return force(default_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
return attrs[forced_key];
|
return attrs[forced_key];
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ mod nar;
|
|||||||
pub use cache::FetcherCache;
|
pub use cache::FetcherCache;
|
||||||
pub use download::Downloader;
|
pub use download::Downloader;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@@ -245,7 +245,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
|||||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
let expr = self.expr().unwrap().downgrade(ctx)?;
|
||||||
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||||
let default = if let Some(default) = self.default_expr() {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -99,3 +99,21 @@ fn logical_not() {
|
|||||||
assert_eq!(eval("!true"), Value::Bool(false));
|
assert_eq!(eval("!true"), Value::Bool(false));
|
||||||
assert_eq!(eval("!false"), Value::Bool(true));
|
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));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user