From 6d8f1e79e602ed461e02170bde8c1ddb663cdfb0 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Fri, 13 Feb 2026 19:44:37 +0800 Subject: [PATCH] fix: handle NixPath in nixValueToJson --- nix-js/runtime-ts/src/builtins/conversion.ts | 4 +- nix-js/runtime-ts/src/builtins/derivation.ts | 68 +----------- nix-js/runtime-ts/src/conversion.ts | 107 ++++++++++--------- nix-js/runtime-ts/src/derivation-helpers.ts | 3 +- nix-js/runtime-ts/src/operators.ts | 8 +- nix-js/runtime-ts/src/thunk.ts | 2 +- nix-js/tests/derivation.rs | 2 +- 7 files changed, 71 insertions(+), 123 deletions(-) diff --git a/nix-js/runtime-ts/src/builtins/conversion.ts b/nix-js/runtime-ts/src/builtins/conversion.ts index e85999e..affdb87 100644 --- a/nix-js/runtime-ts/src/builtins/conversion.ts +++ b/nix-js/runtime-ts/src/builtins/conversion.ts @@ -2,7 +2,7 @@ * Conversion and serialization builtin functions */ -import type { NixValue, NixString, NixPath } from "../types"; +import type { NixString, NixValue } from "../types"; import { isStringWithContext, isNixPath } from "../types"; import { force } from "../thunk"; import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context"; @@ -59,7 +59,7 @@ export const fromTOML = (e: NixValue): never => { export const toJSON = (e: NixValue): NixString => { const context: Set = new Set(); - const string = JSON.stringify(nixValueToJson(e, new Set(), context)); + const string = JSON.stringify(nixValueToJson(e, true, context, true)); if (context.size === 0) { return string; } diff --git a/nix-js/runtime-ts/src/builtins/derivation.ts b/nix-js/runtime-ts/src/builtins/derivation.ts index 9374e2e..5f9220e 100644 --- a/nix-js/runtime-ts/src/builtins/derivation.ts +++ b/nix-js/runtime-ts/src/builtins/derivation.ts @@ -1,6 +1,6 @@ import type { NixValue, NixAttrs } from "../types"; import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert"; -import { force, createThunk } from "../thunk"; +import { force } from "../thunk"; import { type DerivationData, type OutputInfo, @@ -99,17 +99,6 @@ const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] => return outputs; }; -const extractOutputsForWrapper = (attrs: NixAttrs): string[] => { - if (!("outputs" in attrs)) { - return ["out"]; - } - - // FIXME: trace context? - const outputs = forceList(attrs.outputs).map(forceStringValue); - validateOutputs(outputs); - return outputs; -}; - const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => { if (!("args" in attrs)) { return []; @@ -126,12 +115,7 @@ const structuredAttrsExcludedKeys = new Set([ "args", ]); -const specialAttrs = new Set([ - "args", - "__ignoreNulls", - "__contentAddressed", - "__impure", -]); +const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]); const sortedJsonStringify = (obj: Record): string => { const sortedKeys = Object.keys(obj).sort(); @@ -159,7 +143,7 @@ const extractEnv = ( if (ignoreNulls && forcedValue === null) { continue; } - jsonAttrs[key] = nixValueToJson(value, new Set(), outContext); + jsonAttrs[key] = nixValueToJson(value, true, outContext, true); } if (key === "allowedReferences") { @@ -295,7 +279,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => { const drvArgs = extractArgs(attrs, collectedContext); const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName); - const { inputDrvs, inputSrcs } = extractInputDrvsAndSrcs(collectedContext); const collectDrvReferences = (): string[] => { @@ -429,47 +412,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => { return result; }; -export const derivation = (args: NixValue): NixAttrs => { - const attrs = forceAttrs(args); - - const outputs: string[] = extractOutputsForWrapper(attrs); - - const strictThunk = createThunk(() => derivationStrict(args), "derivationStrict"); - - const commonAttrs: NixAttrs = { ...attrs }; - - const outputToAttrListElement = (outputName: string): { name: string; value: NixAttrs } => { - const value: NixAttrs = { - ...commonAttrs, - outPath: createThunk(() => (force(strictThunk) as NixAttrs)[outputName], `outPath_${outputName}`), - drvPath: createThunk(() => (force(strictThunk) as NixAttrs).drvPath, "drvPath"), - type: "derivation", - outputName, - }; - return { name: outputName, value }; - }; - - const outputsList = outputs.map(outputToAttrListElement); - - for (const { name: outputName } of outputsList) { - commonAttrs[outputName] = createThunk( - () => outputsList.find((o) => o.name === outputName)!.value, - `output_${outputName}`, - ); - } - commonAttrs.all = createThunk(() => outputsList.map((o) => o.value), "all_outputs"); - commonAttrs.drvAttrs = attrs; - - for (const { value: outputObj } of outputsList) { - for (const { name: outputName } of outputsList) { - outputObj[outputName] = createThunk( - () => outputsList.find((o) => o.name === outputName)!.value, - `output_${outputName}`, - ); - } - outputObj.all = createThunk(() => outputsList.map((o) => o.value), "all_outputs"); - outputObj.drvAttrs = attrs; - } - - return outputsList[0].value; +export const derivation = (_: NixValue): NixAttrs => { + throw new Error("unreachable: placeholder derivation implementation called") }; diff --git a/nix-js/runtime-ts/src/conversion.ts b/nix-js/runtime-ts/src/conversion.ts index e00b280..2d7254a 100644 --- a/nix-js/runtime-ts/src/conversion.ts +++ b/nix-js/runtime-ts/src/conversion.ts @@ -1,81 +1,82 @@ import { HAS_CONTEXT, NixStringContext } from "./string-context"; -import { force } from "./thunk"; +import { force, isThunk } from "./thunk"; import type { NixValue } from "./types"; -import { isStringWithContext } from "./types"; +import { isStringWithContext, IS_PATH } from "./types"; export const nixValueToJson = ( value: NixValue, - seen = new Set(), - outContext?: NixStringContext, + strict: boolean, + outContext: NixStringContext, + copyToStore: boolean, + seen: Set = new Set(), ): any => { - const v = force(value); + const v = strict ? force(value) : value; + if (isThunk(v) || typeof v === "function") + throw new Error(`cannot convert ${isThunk(v) ? "thunk" : "lambda"} to JSON`); if (v === null) return null; - if (typeof v === "boolean") return v; - if (typeof v === "string") return v; - if (typeof v === "number") return v; - - if (typeof v === "object" && HAS_CONTEXT in v && "context" in v) { - if (outContext) { - for (const elem of v.context) { - outContext.add(elem); - } - } - return v.value; - } - if (typeof v === "bigint") { const num = Number(v); if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) { - console.warn(`derivation: integer ${v} exceeds safe range, precision may be lost in __structuredAttrs`); + console.warn(`integer ${v} exceeds safe range, precision may be lost`); } return num; } - - if (typeof v === "object" && v !== null) { - if (seen.has(v)) { - throw new Error("derivation: circular reference detected in __structuredAttrs"); + if (typeof v === "number") return v; + if (typeof v === "boolean") return v; + if (typeof v === "string") return v; + if (typeof v === "object" && HAS_CONTEXT in v) { + for (const elem of v.context) { + outContext.add(elem); } - seen.add(v); + return v.value; + } + if (typeof v === "object" && IS_PATH in v) { + if (copyToStore) { + const storePath = Deno.core.ops.op_copy_path_to_store(v.value); + outContext.add(storePath); + return storePath; + } else { + return v.value; + } + } + + if (seen.has(v)) { + throw new Error("cycle detected in toJSON"); + } else { + seen.add(v) } if (Array.isArray(v)) { - return v.map((item) => nixValueToJson(item, seen, outContext)); + return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen)); } - if (typeof v === "object") { - if ("__toString" in v && typeof force(v.__toString) === "function") { - const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue; - const result = force(toStringMethod(v)); - if (typeof result === "string") { - return result; - } - if (isStringWithContext(result)) { - if (outContext) { - for (const elem of result.context) { - outContext.add(elem); - } + // NixAttrs + if ("__toString" in v && typeof force(v.__toString) === "function") { + const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue; + const result = force(toStringMethod(v)); + if (typeof result === "string") { + return result; + } + if (isStringWithContext(result)) { + if (outContext) { + for (const elem of result.context) { + outContext.add(elem); } - return result.value; } - return nixValueToJson(result, seen, outContext); + return result.value; } - - if ("outPath" in v) { - return nixValueToJson(v.outPath, seen, outContext); - } - - const result: Record = {}; - const keys = Object.keys(v).sort(); - for (const key of keys) { - result[key] = nixValueToJson((v as Record)[key], seen, outContext); - } - return result; + return nixValueToJson(result, strict, outContext, copyToStore, seen); } - if (typeof v === "function") { - throw new Error("derivation: cannot serialize function in __structuredAttrs"); + if ("outPath" in v) { + return nixValueToJson(v.outPath, strict, outContext, copyToStore,seen); } - throw new Error(`derivation: cannot serialize ${typeof v} to JSON`); + const result: Record = {}; + const keys = Object.keys(v).sort(); + for (const key of keys) { + result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen); + } + return result; }; diff --git a/nix-js/runtime-ts/src/derivation-helpers.ts b/nix-js/runtime-ts/src/derivation-helpers.ts index 2953ad5..daebaea 100644 --- a/nix-js/runtime-ts/src/derivation-helpers.ts +++ b/nix-js/runtime-ts/src/derivation-helpers.ts @@ -43,8 +43,7 @@ export const escapeString = (s: string): string => { const quoteString = (s: string): string => `"${s}"`; -const cmpByKey = (a: [string, T], b: [string, T]): number => - a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; +const cmpByKey = (a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0); export const generateAterm = (drv: DerivationData): string => { const outputEntries: string[] = []; diff --git a/nix-js/runtime-ts/src/operators.ts b/nix-js/runtime-ts/src/operators.ts index cdbbf98..87b089c 100644 --- a/nix-js/runtime-ts/src/operators.ts +++ b/nix-js/runtime-ts/src/operators.ts @@ -7,7 +7,13 @@ import type { NixValue, NixList, NixAttrs, NixString, NixPath } from "./types"; import { isNixPath } from "./types"; import { force } from "./thunk"; import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert"; -import { type NixStringContext, getStringValue, getStringContext, mergeContexts, mkStringWithContext } from "./string-context"; +import { + type NixStringContext, + getStringValue, + getStringContext, + mergeContexts, + mkStringWithContext, +} from "./string-context"; import { coerceToString, StringCoercionMode } from "./builtins/conversion"; import { mkPath } from "./path"; import { typeOf, isNixString } from "./builtins/type-check"; diff --git a/nix-js/runtime-ts/src/thunk.ts b/nix-js/runtime-ts/src/thunk.ts index c4cf7ae..6253624 100644 --- a/nix-js/runtime-ts/src/thunk.ts +++ b/nix-js/runtime-ts/src/thunk.ts @@ -56,7 +56,7 @@ export class NixThunk implements NixThunkInterface { * @param value - Value to check * @returns true if value is a NixThunk */ -export const isThunk = (value: unknown): value is NixThunkInterface => { +export const isThunk = (value: NixValue): value is NixThunkInterface => { return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true; }; diff --git a/nix-js/tests/derivation.rs b/nix-js/tests/derivation.rs index 46f1411..46418ca 100644 --- a/nix-js/tests/derivation.rs +++ b/nix-js/tests/derivation.rs @@ -541,7 +541,7 @@ fn structured_attrs_rejects_functions() { assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!(err_msg.contains("function") && err_msg.contains("serialize")); + assert!(err_msg.contains("cannot convert lambda to JSON")); } #[test]