feat: mkAttrs

This commit is contained in:
2026-01-24 16:44:28 +08:00
parent a6aded7bea
commit 62ec37f3ad
3 changed files with 43 additions and 19 deletions

View File

@@ -23,7 +23,7 @@ import { op } from "./operators";
import { builtins, PRIMOP_METADATA } from "./builtins"; import { builtins, PRIMOP_METADATA } from "./builtins";
import { coerceToString, StringCoercionMode } from "./builtins/conversion"; import { coerceToString, StringCoercionMode } from "./builtins/conversion";
import { HAS_CONTEXT } from "./string-context"; import { HAS_CONTEXT } from "./string-context";
import { IS_PATH, mkFunction } from "./types"; import { IS_PATH, mkAttrs, mkFunction } from "./types";
import { forceBool } from "./type-assert"; import { forceBool } from "./type-assert";
export type NixRuntime = typeof Nix; export type NixRuntime = typeof Nix;
@@ -52,6 +52,7 @@ export const Nix = {
coerceToString, coerceToString,
concatStringsWithContext, concatStringsWithContext,
StringCoercionMode, StringCoercionMode,
mkAttrs,
mkFunction, mkFunction,
pushContext, pushContext,

View File

@@ -2,10 +2,11 @@
* Core TypeScript type definitions for nix-js runtime * Core TypeScript type definitions for nix-js runtime
*/ */
import { IS_THUNK } from "./thunk"; import { force, IS_THUNK } from "./thunk";
import { type StringWithContext, HAS_CONTEXT, isStringWithContext } from "./string-context"; import { type StringWithContext, HAS_CONTEXT, isStringWithContext, getStringContext } from "./string-context";
import { op } from "./operators"; import { op } from "./operators";
import { forceAttrs } from "./type-assert"; import { forceAttrs } from "./type-assert";
import { isString, typeOf } from "./builtins/type-check";
export { HAS_CONTEXT, isStringWithContext }; export { HAS_CONTEXT, isStringWithContext };
export type { StringWithContext }; export type { StringWithContext };
@@ -73,6 +74,24 @@ export const mkFunction = (
return func; return func;
}; };
export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]): NixAttrs => {
const len = keys.length;
for (let i = 0; i < len; i++) {
const key = force(keys[i]);
if (key === null) {
continue;
}
if (!isString(key)) {
throw `Expected string, got ${typeOf(key)}`
}
if (isStringWithContext(key)) {
throw new TypeError(`the string '${key.value}' is not allowed to refer to a store path`)
}
attrs[key] = values[i];
}
return attrs;
};
/** /**
* Interface for lazy thunk values * Interface for lazy thunk values
* Thunks delay evaluation until forced * Thunks delay evaluation until forced

View File

@@ -353,24 +353,28 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
attrs.push(format!("{}:{}", key.escape_quote(), value)); attrs.push(format!("{}:{}", key.escape_quote(), value));
} }
// FIXME: duplicated key if !self.dyns.is_empty() {
for (key_expr, value_expr) in &self.dyns { let (keys, vals) = self.dyns.iter().map(|&(key, val)| {
let key = ctx.get_ir(*key_expr).compile(ctx); let key = ctx.get_ir(key).compile(ctx);
let value_code = ctx.get_ir(*value_expr).compile(ctx); let val_expr = ctx.get_ir(val);
let val = val_expr.compile(ctx);
let value = if stack_trace_enabled { let span = val_expr.span();
let value_span = encode_span(ctx.get_ir(*value_expr).span(), ctx); let val = if stack_trace_enabled {
let span = encode_span(span, ctx);
format!( format!(
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))", "Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
value_span, value_code span, val
) )
} else { } else {
value_code val
}; };
attrs.push(format!("[{}]:{}", key, value)); (key, val)
}).collect::<(Vec<_>, Vec<_>)>();
format!("Nix.mkAttrs({{{}}},[{}],[{}])", attrs.join(","), keys.join(","), vals.join(","))
} else {
format!("{{{}}}", attrs.join(","))
} }
format!("{{{}}}", attrs.join(","))
} }
} }