From f49634ccc0dfe8af8cd8fe95ff7515dd7e6cd168 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Mon, 16 Feb 2026 22:31:31 +0800 Subject: [PATCH] optimize: use Map to represent NixAttrs --- nix-js/runtime-ts/package.json | 1 + nix-js/runtime-ts/src/builtins/attrs.ts | 75 +++--- nix-js/runtime-ts/src/builtins/context.ts | 28 +- nix-js/runtime-ts/src/builtins/conversion.ts | 101 ++++--- nix-js/runtime-ts/src/builtins/derivation.ts | 80 +++--- nix-js/runtime-ts/src/builtins/hash.ts | 9 +- nix-js/runtime-ts/src/builtins/index.ts | 264 ++++++++++--------- nix-js/runtime-ts/src/builtins/io.ts | 151 ++++++----- nix-js/runtime-ts/src/builtins/list.ts | 15 +- nix-js/runtime-ts/src/builtins/misc.ts | 52 ++-- nix-js/runtime-ts/src/builtins/type-check.ts | 8 +- nix-js/runtime-ts/src/helpers.ts | 41 ++- nix-js/runtime-ts/src/operators.ts | 33 ++- nix-js/runtime-ts/src/path.ts | 5 +- nix-js/runtime-ts/src/thunk.ts | 20 +- nix-js/runtime-ts/src/type-assert.ts | 4 +- nix-js/runtime-ts/src/types.ts | 35 +-- nix-js/src/codegen.rs | 10 +- nix-js/src/context.rs | 2 +- nix-js/src/runtime.rs | 22 ++ nix-js/src/runtime/ops.rs | 142 +++++----- 21 files changed, 573 insertions(+), 525 deletions(-) diff --git a/nix-js/runtime-ts/package.json b/nix-js/runtime-ts/package.json index 94a6e37..8d6e544 100644 --- a/nix-js/runtime-ts/package.json +++ b/nix-js/runtime-ts/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "check": "tsc --noEmit && npx eslint && biome check", + "typecheck": "tsc --noEmit", "build": "node build.mjs", "dev": "npm run typecheck && npm run build" }, diff --git a/nix-js/runtime-ts/src/builtins/attrs.ts b/nix-js/runtime-ts/src/builtins/attrs.ts index c4eb38f..b178783 100644 --- a/nix-js/runtime-ts/src/builtins/attrs.ts +++ b/nix-js/runtime-ts/src/builtins/attrs.ts @@ -1,12 +1,12 @@ -import { mkPos } from "../helpers"; +import { mkPos, select } from "../helpers"; import { createThunk } from "../thunk"; import { forceAttrs, forceFunction, forceList, forceStringValue } from "../type-assert"; import { ATTR_POSITIONS, type NixAttrs, type NixList, type NixValue } from "../types"; -export const attrNames = (set: NixValue): string[] => Object.keys(forceAttrs(set)).sort(); +export const attrNames = (set: NixValue): string[] => Array.from(forceAttrs(set).keys()).sort(); export const attrValues = (set: NixValue): NixValue[] => - Object.entries(forceAttrs(set)) + Array.from(forceAttrs(set).entries()) .sort(([a], [b]) => { if (a < b) { return -1; @@ -21,21 +21,24 @@ export const attrValues = (set: NixValue): NixValue[] => export const getAttr = (s: NixValue) => (set: NixValue): NixValue => - forceAttrs(set)[forceStringValue(s)]; + select(forceAttrs(set), [s]); export const hasAttr = (s: NixValue) => (set: NixValue): boolean => - Object.hasOwn(forceAttrs(set), forceStringValue(s)); + forceAttrs(set).has(forceStringValue(s)); export const mapAttrs = (f: NixValue) => (attrs: NixValue): NixAttrs => { const forcedAttrs = forceAttrs(attrs); const forcedF = forceFunction(f); - const newAttrs: NixAttrs = {}; - for (const key in forcedAttrs) { - newAttrs[key] = createThunk(() => forceFunction(forcedF(key))(forcedAttrs[key]), "created by mapAttrs"); + const newAttrs: NixAttrs = new Map(); + for (const [key, val] of forcedAttrs) { + newAttrs.set( + key, + createThunk(() => forceFunction(forcedF(key))(val), "created by mapAttrs"), + ); } return newAttrs; }; @@ -43,25 +46,20 @@ export const mapAttrs = export const removeAttrs = (attrs: NixValue) => (list: NixValue): NixAttrs => { - const newAttrs: NixAttrs = {}; - const forcedAttrs = forceAttrs(attrs); + const newAttrs: NixAttrs = new Map(forceAttrs(attrs)); const forcedList = forceList(list); - const keysToRemove = new Set(forcedList.map(forceStringValue)); - - for (const key in forcedAttrs) { - if (!keysToRemove.has(key)) { - newAttrs[key] = forcedAttrs[key]; - } + for (const item of forcedList) { + newAttrs.delete(forceStringValue(item)); } return newAttrs; }; export const listToAttrs = (e: NixValue): NixAttrs => { - const attrs: NixAttrs = {}; + const attrs: NixAttrs = new Map(); const forcedE = [...forceList(e)].reverse(); for (const obj of forcedE) { const item = forceAttrs(obj); - attrs[forceStringValue(item.name)] = item.value; + attrs.set(forceStringValue(select(item, ["name"])), select(item, ["value"])); } return attrs; }; @@ -71,21 +69,17 @@ export const intersectAttrs = (e2: NixValue): NixAttrs => { const f1 = forceAttrs(e1); const f2 = forceAttrs(e2); - const attrs: NixAttrs = {}; - const k1 = Object.keys(f1); - const k2 = Object.keys(f2); - if (k1.length < k2.length) { - for (let i = 0; i < k1.length; i++) { - const key = k1[i]; - if (key in f2) { - attrs[key] = f2[key]; + const attrs: NixAttrs = new Map(); + if (f1.size < f2.size) { + for (const [key] of f1) { + if (f2.has(key)) { + attrs.set(key, f2.get(key) as NixValue); } } } else { - for (let i = 0; i < k2.length; i++) { - const key = k2[i]; - if (key in f1) { - attrs[key] = f2[key]; + for (const [key] of f2) { + if (f1.has(key)) { + attrs.set(key, f2.get(key) as NixValue); } } } @@ -97,20 +91,20 @@ export const catAttrs = (list: NixValue): NixList => { const key = forceStringValue(attr); return forceList(list) - .map((set) => forceAttrs(set)[key]) - .filter((val) => val !== undefined); + .map((set) => forceAttrs(set).get(key)) + .filter((val) => val !== undefined) as NixList; }; export const groupBy = (f: NixValue) => (list: NixValue): NixAttrs => { - const attrs: NixAttrs = {}; + const attrs: NixAttrs = new Map(); const forcedF = forceFunction(f); const forcedList = forceList(list); for (const elem of forcedList) { const key = forceStringValue(forcedF(elem)); - if (!attrs[key]) attrs[key] = []; - (attrs[key] as NixList).push(elem); + if (!attrs.has(key)) attrs.set(key, []); + (attrs.get(key) as NixList).push(elem); } return attrs; }; @@ -125,7 +119,7 @@ export const zipAttrsWith = for (const item of listForced) { const attrs = forceAttrs(item); - for (const [key, value] of Object.entries(attrs)) { + for (const [key, value] of attrs) { if (!attrMap.has(key)) { attrMap.set(key, []); } @@ -133,10 +127,13 @@ export const zipAttrsWith = } } - const result: Record = {}; + const result: NixAttrs = new Map(); for (const [name, values] of attrMap.entries()) { - result[name] = createThunk(() => forceFunction(forceFunction(f)(name))(values)); + result.set( + name, + createThunk(() => forceFunction(forceFunction(f)(name))(values)), + ); } return result; @@ -148,7 +145,7 @@ export const unsafeGetAttrPos = const name = forceStringValue(attrName); const attrs = forceAttrs(attrSet); - if (!(name in attrs)) { + if (!attrs.has(name)) { return null; } diff --git a/nix-js/runtime-ts/src/builtins/context.ts b/nix-js/runtime-ts/src/builtins/context.ts index f106417..fa6fb40 100644 --- a/nix-js/runtime-ts/src/builtins/context.ts +++ b/nix-js/runtime-ts/src/builtins/context.ts @@ -113,20 +113,20 @@ export const getContext = (value: NixValue): NixAttrs => { const context = getStringContext(s); const infoMap = parseContextToInfoMap(context); - const result: NixAttrs = {}; + const result: NixAttrs = new Map(); for (const [path, info] of infoMap) { - const attrs: NixAttrs = {}; + const attrs: NixAttrs = new Map(); if (info.path) { - attrs.path = true; + attrs.set("path", true); } if (info.allOutputs) { - attrs.allOutputs = true; + attrs.set("allOutputs", true); } if (info.outputs.length > 0) { - attrs.outputs = info.outputs; + attrs.set("outputs", info.outputs); } - result[path] = attrs; + result.set(path, attrs); } return result; @@ -154,22 +154,22 @@ export const appendContext = const ctxAttrs = forceAttrs(ctxValue); const newContext: NixStringContext = new Set(existingContext); - for (const [path, infoVal] of Object.entries(ctxAttrs)) { + for (const [path, infoVal] of ctxAttrs) { if (!path.startsWith("/nix/store/")) { throw new Error(`context key '${path}' is not a store path`); } - const info = forceAttrs(infoVal); + const info = forceAttrs(infoVal as NixValue); - if ("path" in info) { - const pathVal = force(info.path); + if (info.has("path")) { + const pathVal = force(info.get("path") as NixValue); if (pathVal === true) { newContext.add(path); } } - if ("allOutputs" in info) { - const allOutputs = force(info.allOutputs); + if (info.has("allOutputs")) { + const allOutputs = force(info.get("allOutputs") as NixValue); if (allOutputs === true) { if (!path.endsWith(".drv")) { throw new Error( @@ -180,8 +180,8 @@ export const appendContext = } } - if ("outputs" in info) { - const outputs = forceList(info.outputs); + if (info.has("outputs")) { + const outputs = forceList(info.get("outputs") as NixValue); if (outputs.length > 0 && !path.endsWith(".drv")) { throw new Error( `tried to add derivation output context of ${path}, which is not a derivation, to a string`, diff --git a/nix-js/runtime-ts/src/builtins/conversion.ts b/nix-js/runtime-ts/src/builtins/conversion.ts index 0806b1e..13d667a 100644 --- a/nix-js/runtime-ts/src/builtins/conversion.ts +++ b/nix-js/runtime-ts/src/builtins/conversion.ts @@ -118,37 +118,31 @@ export const coerceToString = ( } if (typeof v === "object" && v !== null && !Array.isArray(v)) { - // First, try the __toString method if present - // This allows custom types to define their own string representation - if ("__toString" in v) { - // Force the method in case it's a thunk - const toStringMethod = forceFunction(v.__toString); - const result = force(toStringMethod(v)); - // Recursively coerceToString - return coerceToString(result, mode, copyToStore, outContext); - } - - // If no __toString, try outPath (used for derivations and store paths) - // This allows derivation objects like { outPath = "/nix/store/..."; } to be coerced - if ("outPath" in v) { - // Recursively coerce the outPath value - const outPath = coerceToString(v.outPath, mode, copyToStore, outContext); - if ("type" in v && v.type === "derivation" && "drvPath" in v && outContext) { - const drvPathValue = force(v.drvPath); - const drvPathStr = isStringWithContext(drvPathValue) - ? drvPathValue.value - : typeof drvPathValue === "string" - ? drvPathValue - : null; - if (drvPathStr) { - const outputName = "outputName" in v ? String(force(v.outputName)) : "out"; - addBuiltContext(outContext, drvPathStr, outputName); - } + if (v instanceof Map) { + if (v.has("__toString")) { + const toStringMethod = forceFunction(v.get("__toString") as NixValue); + const result = force(toStringMethod(v)); + return coerceToString(result, mode, copyToStore, outContext); + } + + if (v.has("outPath")) { + const outPath = coerceToString(v.get("outPath") as NixValue, mode, copyToStore, outContext); + if (v.has("type") && v.get("type") === "derivation" && v.has("drvPath") && outContext) { + const drvPathValue = force(v.get("drvPath") as NixValue); + const drvPathStr = isStringWithContext(drvPathValue) + ? drvPathValue.value + : typeof drvPathValue === "string" + ? drvPathValue + : null; + if (drvPathStr) { + const outputName = v.has("outputName") ? String(force(v.get("outputName") as NixValue)) : "out"; + addBuiltContext(outContext, drvPathStr, outputName); + } + } + return outPath; } - return outPath; } - // Attribute sets without __toString or outPath cannot be coerced throw new TypeError(`cannot coerce ${typeOf(v)} to a string`); } @@ -259,8 +253,8 @@ export const coerceToPath = (value: NixValue, outContext: NixStringContext): str if (isPath(forced)) { return forced.value; } - if (isAttrs(forced) && Object.hasOwn(forced, "__toString")) { - const toStringFunc = forceFunction(forced.__toString); + if (isAttrs(forced) && forced.has("__toString")) { + const toStringFunc = forceFunction(forced.get("__toString") as NixValue); return coerceToPath(toStringFunc(forced), outContext); } @@ -339,30 +333,33 @@ export const nixValueToJson = ( return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen)); } - // 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)) { - for (const elem of result.context) { - outContext.add(elem); + if (v instanceof Map) { + if (v.has("__toString") && typeof force(v.get("__toString") as NixValue) === "function") { + const toStringMethod = force(v.get("__toString") as NixValue) as (self: typeof v) => NixValue; + const result = force(toStringMethod(v)); + if (typeof result === "string") { + return result; } - return result.value; + if (isStringWithContext(result)) { + for (const elem of result.context) { + outContext.add(elem); + } + return result.value; + } + return nixValueToJson(result, strict, outContext, copyToStore, seen); } - return nixValueToJson(result, strict, outContext, copyToStore, seen); + + if (v.has("outPath")) { + return nixValueToJson(v.get("outPath") as NixValue, strict, outContext, copyToStore, seen); + } + + const result: Record = {}; + const keys = Array.from(v.keys()).sort(); + for (const key of keys) { + result[key] = nixValueToJson(v.get(key) as NixValue, strict, outContext, copyToStore, seen); + } + return result; } - if ("outPath" in v) { - return nixValueToJson(v.outPath, strict, outContext, copyToStore, seen); - } - - 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; + throw new Error(`cannot convert ${typeof v} to JSON`); }; diff --git a/nix-js/runtime-ts/src/builtins/derivation.ts b/nix-js/runtime-ts/src/builtins/derivation.ts index 386ec49..afe9541 100644 --- a/nix-js/runtime-ts/src/builtins/derivation.ts +++ b/nix-js/runtime-ts/src/builtins/derivation.ts @@ -116,10 +116,10 @@ export const generateAtermModulo = (drv: DerivationData, inputDrvHashes: Map { - if (!("name" in attrs)) { + if (!attrs.has("name")) { throw new Error("derivation: missing required attribute 'name'"); } - const name = forceStringValue(attrs.name); + const name = forceStringValue(attrs.get("name") as NixValue); if (!name) { throw new Error("derivation: 'name' cannot be empty"); } @@ -130,17 +130,17 @@ const validateName = (attrs: NixAttrs): string => { }; const validateBuilder = (attrs: NixAttrs, outContext: NixStringContext): string => { - if (!("builder" in attrs)) { + if (!attrs.has("builder")) { throw new Error("derivation: missing required attribute 'builder'"); } - return coerceToString(attrs.builder, StringCoercionMode.ToString, true, outContext); + return coerceToString(attrs.get("builder") as NixValue, StringCoercionMode.ToString, true, outContext); }; const validateSystem = (attrs: NixAttrs): string => { - if (!("system" in attrs)) { + if (!attrs.has("system")) { throw new Error("derivation: missing required attribute 'system'"); } - return forceStringValue(attrs.system); + return forceStringValue(attrs.get("system") as NixValue); }; const validateOutputs = (outputs: string[]): void => { @@ -162,17 +162,25 @@ const validateOutputs = (outputs: string[]): void => { }; const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] => { - if (!("outputs" in attrs)) { + if (!attrs.has("outputs")) { return ["out"]; } let outputs: string[]; if (structuredAttrs) { - const outputsList = forceList(attrs.outputs); + const outputsList = forceList(attrs.get("outputs") as NixValue); outputs = outputsList.map((o) => forceStringValue(o)); } else { - const outputsStr = coerceToString(attrs.outputs, StringCoercionMode.ToString, false, new Set()); - outputs = outputsStr.split(/\s+/).filter((s) => s.length > 0); + const outputsStr = coerceToString( + attrs.get("outputs") as NixValue, + StringCoercionMode.ToString, + false, + new Set(), + ); + outputs = outputsStr + .trim() + .split(/\s+/) + .filter((s) => s.length > 0); } validateOutputs(outputs); @@ -180,10 +188,10 @@ const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] => }; const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => { - if (!("args" in attrs)) { + if (!attrs.has("args")) { return []; } - const argsList = forceList(attrs.args); + const argsList = forceList(attrs.get("args") as NixValue); return argsList.map((a) => coerceToString(a, StringCoercionMode.ToString, true, outContext)); }; @@ -217,13 +225,13 @@ const extractEnv = ( if (structuredAttrs) { const jsonAttrs: Record = {}; - for (const [key, value] of Object.entries(attrs)) { + for (const [key, value] of attrs) { if (!structuredAttrsExcludedKeys.has(key)) { - const forcedValue = force(value); + const forcedValue = force(value as NixValue); if (ignoreNulls && forcedValue === null) { continue; } - jsonAttrs[key] = nixValueToJson(value, true, outContext, true); + jsonAttrs[key] = nixValueToJson(value as NixValue, true, outContext, true); } if (key === "allowedReferences") { @@ -271,13 +279,13 @@ const extractEnv = ( } env.set("__json", sortedJsonStringify(jsonAttrs)); } else { - for (const [key, value] of Object.entries(attrs)) { + for (const [key, value] of attrs) { if (!specialAttrs.has(key)) { - const forcedValue = force(value); + const forcedValue = force(value as NixValue); if (ignoreNulls && forcedValue === null) { continue; } - env.set(key, coerceToString(value, StringCoercionMode.ToString, true, outContext)); + env.set(key, coerceToString(value as NixValue, StringCoercionMode.ToString, true, outContext)); } } } @@ -292,29 +300,29 @@ interface FixedOutputInfo { } const extractFixedOutputInfo = (attrs: NixAttrs, ignoreNulls: boolean): FixedOutputInfo | null => { - if (!("outputHash" in attrs)) { + if (!attrs.has("outputHash")) { return null; } - const hashValue = force(attrs.outputHash); + const hashValue = force(attrs.get("outputHash") as NixValue); if (ignoreNulls && hashValue === null) { return null; } - const hashRaw = forceStringNoCtx(attrs.outputHash); + const hashRaw = forceStringNoCtx(hashValue); let hashAlgo = null; - if ("outputHashAlgo" in attrs) { - const algoValue = force(attrs.outputHashAlgo); + if (attrs.has("outputHashAlgo")) { + const algoValue = force(attrs.get("outputHashAlgo") as NixValue); if (!(ignoreNulls && algoValue === null)) { - hashAlgo = forceStringNoCtx(attrs.outputHashAlgo); + hashAlgo = forceStringNoCtx(algoValue); } } let hashMode = "flat"; - if ("outputHashMode" in attrs) { - const modeValue = force(attrs.outputHashMode); + if (attrs.has("outputHashMode")) { + const modeValue = force(attrs.get("outputHashMode") as NixValue); if (!(ignoreNulls && modeValue === null)) { - hashMode = forceStringValue(attrs.outputHashMode); + hashMode = forceStringValue(modeValue); } } @@ -341,18 +349,22 @@ export const derivationStrict = (args: NixValue): NixAttrs => { const builder = validateBuilder(attrs, collectedContext); const platform = validateSystem(attrs); - const structuredAttrs = "__structuredAttrs" in attrs ? force(attrs.__structuredAttrs) === true : false; - const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false; + const structuredAttrs = attrs.has("__structuredAttrs") + ? force(attrs.get("__structuredAttrs") as NixValue) === true + : false; + const ignoreNulls = attrs.has("__ignoreNulls") + ? force(attrs.get("__ignoreNulls") as NixValue) === true + : false; const outputs = extractOutputs(attrs, structuredAttrs); const fixedOutputInfo = extractFixedOutputInfo(attrs, ignoreNulls); validateFixedOutputConstraints(fixedOutputInfo, outputs); - if ("__contentAddressed" in attrs && force(attrs.__contentAddressed) === true) { + if (attrs.has("__contentAddressed") && force(attrs.get("__contentAddressed") as NixValue) === true) { throw new Error("ca derivations are not supported"); } - if ("__impure" in attrs && force(attrs.__impure) === true) { + if (attrs.has("__impure") && force(attrs.get("__impure") as NixValue) === true) { throw new Error("impure derivations are not supported"); } @@ -376,16 +388,16 @@ export const derivationStrict = (args: NixValue): NixAttrs => { fixedOutput: fixedOutputInfo, }); - const result: NixAttrs = {}; + const result: NixAttrs = new Map(); const drvPathContext = new Set(); addDrvDeepContext(drvPathContext, rustResult.drvPath); - result.drvPath = mkStringWithContext(rustResult.drvPath, drvPathContext); + result.set("drvPath", mkStringWithContext(rustResult.drvPath, drvPathContext)); for (const [outputName, outputPath] of rustResult.outputs) { const outputContext = new Set(); addBuiltContext(outputContext, rustResult.drvPath, outputName); - result[outputName] = mkStringWithContext(outputPath, outputContext); + result.set(outputName, mkStringWithContext(outputPath, outputContext)); } return result; diff --git a/nix-js/runtime-ts/src/builtins/hash.ts b/nix-js/runtime-ts/src/builtins/hash.ts index 6405f6d..feb195f 100644 --- a/nix-js/runtime-ts/src/builtins/hash.ts +++ b/nix-js/runtime-ts/src/builtins/hash.ts @@ -1,3 +1,4 @@ +import { select } from "../helpers"; import { forceAttrs, forceStringNoCtx, forceStringValue } from "../type-assert"; import type { NixValue } from "../types"; import { realisePath } from "./io"; @@ -20,14 +21,14 @@ export const hashString = export const convertHash = (args: NixValue): string => { const attrs = forceAttrs(args); - const hash = forceStringNoCtx(attrs.hash); + const hash = forceStringNoCtx(select(attrs, ["hash"])); let hashAlgo: string | null = null; - if ("hashAlgo" in attrs) { - hashAlgo = forceStringNoCtx(attrs.hashAlgo); + if (attrs.has("hashAlgo")) { + hashAlgo = forceStringNoCtx(select(attrs, ["hashAlgo"])); } - const toHashFormat = forceStringNoCtx(attrs.toHashFormat); + const toHashFormat = forceStringNoCtx(select(attrs, ["toHashFormat"])); return Deno.core.ops.op_convert_hash({ hash, diff --git a/nix-js/runtime-ts/src/builtins/index.ts b/nix-js/runtime-ts/src/builtins/index.ts index 6492032..03ad6f4 100644 --- a/nix-js/runtime-ts/src/builtins/index.ts +++ b/nix-js/runtime-ts/src/builtins/index.ts @@ -1,5 +1,5 @@ import { createThunk, force } from "../thunk"; -import type { NixValue } from "../types"; +import type { NixAttrs, NixValue } from "../types"; import * as arithmetic from "./arithmetic"; import * as attrs from "./attrs"; import * as conversion from "./conversion"; @@ -74,149 +74,151 @@ export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined => return undefined; }; -export const builtins: Record = { - add: mkPrimop(arithmetic.add, "add", 2), - sub: mkPrimop(arithmetic.sub, "sub", 2), - mul: mkPrimop(arithmetic.mul, "mul", 2), - div: mkPrimop(arithmetic.div, "div", 2), - bitAnd: mkPrimop(arithmetic.bitAnd, "bitAnd", 2), - bitOr: mkPrimop(arithmetic.bitOr, "bitOr", 2), - bitXor: mkPrimop(arithmetic.bitXor, "bitXor", 2), - lessThan: mkPrimop(arithmetic.lessThan, "lessThan", 2), +export const builtins: NixAttrs = new Map( + Object.entries({ + add: mkPrimop(arithmetic.add, "add", 2), + sub: mkPrimop(arithmetic.sub, "sub", 2), + mul: mkPrimop(arithmetic.mul, "mul", 2), + div: mkPrimop(arithmetic.div, "div", 2), + bitAnd: mkPrimop(arithmetic.bitAnd, "bitAnd", 2), + bitOr: mkPrimop(arithmetic.bitOr, "bitOr", 2), + bitXor: mkPrimop(arithmetic.bitXor, "bitXor", 2), + lessThan: mkPrimop(arithmetic.lessThan, "lessThan", 2), - ceil: mkPrimop(math.ceil, "ceil", 1), - floor: mkPrimop(math.floor, "floor", 1), + ceil: mkPrimop(math.ceil, "ceil", 1), + floor: mkPrimop(math.floor, "floor", 1), - isAttrs: mkPrimop((e: NixValue) => typeCheck.isAttrs(force(e)), "isAttrs", 1), - isBool: mkPrimop((e: NixValue) => typeCheck.isBool(force(e)), "isBool", 1), - isFloat: mkPrimop((e: NixValue) => typeCheck.isFloat(force(e)), "isFloat", 1), - isFunction: mkPrimop((e: NixValue) => typeCheck.isFunction(force(e)), "isFunction", 1), - isInt: mkPrimop((e: NixValue) => typeCheck.isInt(force(e)), "isInt", 1), - isList: mkPrimop((e: NixValue) => typeCheck.isList(force(e)), "isList", 1), - isNull: mkPrimop((e: NixValue) => typeCheck.isNull(force(e)), "isNull", 1), - isPath: mkPrimop((e: NixValue) => typeCheck.isPath(force(e)), "isPath", 1), - isString: mkPrimop((e: NixValue) => typeCheck.isString(force(e)), "isString", 1), - typeOf: mkPrimop((e: NixValue) => typeCheck.typeOf(force(e)), "typeOf", 1), + isAttrs: mkPrimop((e: NixValue) => typeCheck.isAttrs(force(e)), "isAttrs", 1), + isBool: mkPrimop((e: NixValue) => typeCheck.isBool(force(e)), "isBool", 1), + isFloat: mkPrimop((e: NixValue) => typeCheck.isFloat(force(e)), "isFloat", 1), + isFunction: mkPrimop((e: NixValue) => typeCheck.isFunction(force(e)), "isFunction", 1), + isInt: mkPrimop((e: NixValue) => typeCheck.isInt(force(e)), "isInt", 1), + isList: mkPrimop((e: NixValue) => typeCheck.isList(force(e)), "isList", 1), + isNull: mkPrimop((e: NixValue) => typeCheck.isNull(force(e)), "isNull", 1), + isPath: mkPrimop((e: NixValue) => typeCheck.isPath(force(e)), "isPath", 1), + isString: mkPrimop((e: NixValue) => typeCheck.isString(force(e)), "isString", 1), + typeOf: mkPrimop((e: NixValue) => typeCheck.typeOf(force(e)), "typeOf", 1), - map: mkPrimop(list.map, "map", 2), - filter: mkPrimop(list.filter, "filter", 2), - length: mkPrimop(list.length, "length", 1), - head: mkPrimop(list.head, "head", 1), - tail: mkPrimop(list.tail, "tail", 1), - elem: mkPrimop(list.elem, "elem", 2), - elemAt: mkPrimop(list.elemAt, "elemAt", 2), - concatLists: mkPrimop(list.concatLists, "concatLists", 1), - concatMap: mkPrimop(list.concatMap, "concatMap", 2), - "foldl'": mkPrimop(list.foldlPrime, "foldl'", 3), - sort: mkPrimop(list.sort, "sort", 2), - partition: mkPrimop(list.partition, "partition", 2), - genList: mkPrimop(list.genList, "genList", 2), - all: mkPrimop(list.all, "all", 2), - any: mkPrimop(list.any, "any", 2), + map: mkPrimop(list.map, "map", 2), + filter: mkPrimop(list.filter, "filter", 2), + length: mkPrimop(list.length, "length", 1), + head: mkPrimop(list.head, "head", 1), + tail: mkPrimop(list.tail, "tail", 1), + elem: mkPrimop(list.elem, "elem", 2), + elemAt: mkPrimop(list.elemAt, "elemAt", 2), + concatLists: mkPrimop(list.concatLists, "concatLists", 1), + concatMap: mkPrimop(list.concatMap, "concatMap", 2), + "foldl'": mkPrimop(list.foldlPrime, "foldl'", 3), + sort: mkPrimop(list.sort, "sort", 2), + partition: mkPrimop(list.partition, "partition", 2), + genList: mkPrimop(list.genList, "genList", 2), + all: mkPrimop(list.all, "all", 2), + any: mkPrimop(list.any, "any", 2), - attrNames: mkPrimop(attrs.attrNames, "attrNames", 1), - attrValues: mkPrimop(attrs.attrValues, "attrValues", 1), - getAttr: mkPrimop(attrs.getAttr, "getAttr", 2), - hasAttr: mkPrimop(attrs.hasAttr, "hasAttr", 2), - mapAttrs: mkPrimop(attrs.mapAttrs, "mapAttrs", 2), - removeAttrs: mkPrimop(attrs.removeAttrs, "removeAttrs", 2), - listToAttrs: mkPrimop(attrs.listToAttrs, "listToAttrs", 1), - intersectAttrs: mkPrimop(attrs.intersectAttrs, "intersectAttrs", 2), - catAttrs: mkPrimop(attrs.catAttrs, "catAttrs", 2), - groupBy: mkPrimop(attrs.groupBy, "groupBy", 2), - zipAttrsWith: mkPrimop(attrs.zipAttrsWith, "zipAttrsWith", 2), - unsafeGetAttrPos: mkPrimop(attrs.unsafeGetAttrPos, "unsafeGetAttrPos", 2), + attrNames: mkPrimop(attrs.attrNames, "attrNames", 1), + attrValues: mkPrimop(attrs.attrValues, "attrValues", 1), + getAttr: mkPrimop(attrs.getAttr, "getAttr", 2), + hasAttr: mkPrimop(attrs.hasAttr, "hasAttr", 2), + mapAttrs: mkPrimop(attrs.mapAttrs, "mapAttrs", 2), + removeAttrs: mkPrimop(attrs.removeAttrs, "removeAttrs", 2), + listToAttrs: mkPrimop(attrs.listToAttrs, "listToAttrs", 1), + intersectAttrs: mkPrimop(attrs.intersectAttrs, "intersectAttrs", 2), + catAttrs: mkPrimop(attrs.catAttrs, "catAttrs", 2), + groupBy: mkPrimop(attrs.groupBy, "groupBy", 2), + zipAttrsWith: mkPrimop(attrs.zipAttrsWith, "zipAttrsWith", 2), + unsafeGetAttrPos: mkPrimop(attrs.unsafeGetAttrPos, "unsafeGetAttrPos", 2), - stringLength: mkPrimop(string.stringLength, "stringLength", 1), - substring: mkPrimop(string.substring, "substring", 3), - concatStringsSep: mkPrimop(string.concatStringsSep, "concatStringsSep", 2), - baseNameOf: mkPrimop(pathOps.baseNameOf, "baseNameOf", 1), - dirOf: mkPrimop(pathOps.dirOf, "dirOf", 1), - toPath: mkPrimop(pathOps.toPath, "toPath", 1), - match: mkPrimop(string.match, "match", 2), - split: mkPrimop(string.split, "split", 2), + stringLength: mkPrimop(string.stringLength, "stringLength", 1), + substring: mkPrimop(string.substring, "substring", 3), + concatStringsSep: mkPrimop(string.concatStringsSep, "concatStringsSep", 2), + baseNameOf: mkPrimop(pathOps.baseNameOf, "baseNameOf", 1), + dirOf: mkPrimop(pathOps.dirOf, "dirOf", 1), + toPath: mkPrimop(pathOps.toPath, "toPath", 1), + match: mkPrimop(string.match, "match", 2), + split: mkPrimop(string.split, "split", 2), - seq: mkPrimop(functional.seq, "seq", 2), - deepSeq: mkPrimop(functional.deepSeq, "deepSeq", 2), - abort: mkPrimop(functional.abort, "abort", 1), - throw: mkPrimop(functional.throwFunc, "throw", 1), - trace: mkPrimop(functional.trace, "trace", 2), - warn: mkPrimop(functional.warn, "warn", 2), - break: mkPrimop(functional.breakFunc, "break", 1), + seq: mkPrimop(functional.seq, "seq", 2), + deepSeq: mkPrimop(functional.deepSeq, "deepSeq", 2), + abort: mkPrimop(functional.abort, "abort", 1), + throw: mkPrimop(functional.throwFunc, "throw", 1), + trace: mkPrimop(functional.trace, "trace", 2), + warn: mkPrimop(functional.warn, "warn", 2), + break: mkPrimop(functional.breakFunc, "break", 1), - derivation: mkPrimop(derivation.derivationStub, "derivation", 1), - derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1), + derivation: mkPrimop(derivation.derivationStub, "derivation", 1), + derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1), - import: mkPrimop(io.importFunc, "import", 1), - scopedImport: mkPrimop(io.scopedImport, "scopedImport", 2), - storePath: mkPrimop(io.storePath, "storePath", 1), - fetchClosure: mkPrimop(io.fetchClosure, "fetchClosure", 1), - fetchMercurial: mkPrimop(io.fetchMercurial, "fetchMercurial", 1), - fetchGit: mkPrimop(io.fetchGit, "fetchGit", 1), - fetchTarball: mkPrimop(io.fetchTarball, "fetchTarball", 1), - fetchTree: mkPrimop(io.fetchTree, "fetchTree", 1), - fetchurl: mkPrimop(io.fetchurl, "fetchurl", 1), - readDir: mkPrimop(io.readDir, "readDir", 1), - readFile: mkPrimop(io.readFile, "readFile", 1), - readFileType: mkPrimop(io.readFileType, "readFileType", 1), - pathExists: mkPrimop(io.pathExists, "pathExists", 1), - path: mkPrimop(io.path, "path", 1), - toFile: mkPrimop(io.toFile, "toFile", 2), - filterSource: mkPrimop(io.filterSource, "filterSource", 2), - findFile: mkPrimop(io.findFile, "findFile", 2), - getEnv: mkPrimop(io.getEnv, "getEnv", 1), + import: mkPrimop(io.importFunc, "import", 1), + scopedImport: mkPrimop(io.scopedImport, "scopedImport", 2), + storePath: mkPrimop(io.storePath, "storePath", 1), + fetchClosure: mkPrimop(io.fetchClosure, "fetchClosure", 1), + fetchMercurial: mkPrimop(io.fetchMercurial, "fetchMercurial", 1), + fetchGit: mkPrimop(io.fetchGit, "fetchGit", 1), + fetchTarball: mkPrimop(io.fetchTarball, "fetchTarball", 1), + fetchTree: mkPrimop(io.fetchTree, "fetchTree", 1), + fetchurl: mkPrimop(io.fetchurl, "fetchurl", 1), + readDir: mkPrimop(io.readDir, "readDir", 1), + readFile: mkPrimop(io.readFile, "readFile", 1), + readFileType: mkPrimop(io.readFileType, "readFileType", 1), + pathExists: mkPrimop(io.pathExists, "pathExists", 1), + path: mkPrimop(io.path, "path", 1), + toFile: mkPrimop(io.toFile, "toFile", 2), + filterSource: mkPrimop(io.filterSource, "filterSource", 2), + findFile: mkPrimop(io.findFile, "findFile", 2), + getEnv: mkPrimop(io.getEnv, "getEnv", 1), - fromJSON: mkPrimop(conversion.fromJSON, "fromJSON", 1), - fromTOML: mkPrimop(conversion.fromTOML, "fromTOML", 1), - toJSON: mkPrimop(conversion.toJSON, "toJSON", 1), - toXML: mkPrimop(conversion.toXML, "toXML", 1), - toString: mkPrimop(conversion.toStringFunc, "toString", 1), + fromJSON: mkPrimop(conversion.fromJSON, "fromJSON", 1), + fromTOML: mkPrimop(conversion.fromTOML, "fromTOML", 1), + toJSON: mkPrimop(conversion.toJSON, "toJSON", 1), + toXML: mkPrimop(conversion.toXML, "toXML", 1), + toString: mkPrimop(conversion.toStringFunc, "toString", 1), - hashFile: mkPrimop(hash.hashFile, "hashFile", 2), - hashString: mkPrimop(hash.hashString, "hashString", 2), - convertHash: mkPrimop(hash.convertHash, "convertHash", 2), + hashFile: mkPrimop(hash.hashFile, "hashFile", 2), + hashString: mkPrimop(hash.hashString, "hashString", 2), + convertHash: mkPrimop(hash.convertHash, "convertHash", 2), - flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1), - getFlake: mkPrimop(flake.getFlake, "getFlake", 1), - parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1), - parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1), + flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1), + getFlake: mkPrimop(flake.getFlake, "getFlake", 1), + parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1), + parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1), - addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1), - appendContext: mkPrimop(misc.appendContext, "appendContext", 1), - getContext: mkPrimop(misc.getContext, "getContext", 1), - hasContext: mkPrimop(misc.hasContext, "hasContext", 1), - unsafeDiscardOutputDependency: mkPrimop( - misc.unsafeDiscardOutputDependency, - "unsafeDiscardOutputDependency", - 1, - ), - unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1), - addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2), - compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2), - functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1), - genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1), - outputOf: mkPrimop(misc.outputOf, "outputOf", 2), - parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1), - placeholder: mkPrimop(misc.placeholder, "placeholder", 1), - replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3), - splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1), - traceVerbose: mkPrimop(misc.traceVerbose, "traceVerbose", 2), - tryEval: mkPrimop(misc.tryEval, "tryEval", 1), + addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1), + appendContext: mkPrimop(misc.appendContext, "appendContext", 1), + getContext: mkPrimop(misc.getContext, "getContext", 1), + hasContext: mkPrimop(misc.hasContext, "hasContext", 1), + unsafeDiscardOutputDependency: mkPrimop( + misc.unsafeDiscardOutputDependency, + "unsafeDiscardOutputDependency", + 1, + ), + unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1), + addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2), + compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2), + functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1), + genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1), + outputOf: mkPrimop(misc.outputOf, "outputOf", 2), + parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1), + placeholder: mkPrimop(misc.placeholder, "placeholder", 1), + replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3), + splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1), + traceVerbose: mkPrimop(misc.traceVerbose, "traceVerbose", 2), + tryEval: mkPrimop(misc.tryEval, "tryEval", 1), - builtins: createThunk(() => builtins, "builtins"), - currentSystem: createThunk(() => { - return "x86_64-linux"; - }, "currentSystem"), - currentTime: createThunk(() => Date.now(), "currentTime"), + builtins: createThunk(() => builtins, "builtins"), + currentSystem: createThunk(() => { + return "x86_64-linux"; + }, "currentSystem"), + currentTime: createThunk(() => Date.now(), "currentTime"), - false: false, - true: true, - null: null, + false: false, + true: true, + null: null, - langVersion: 6, - nixPath: [], - nixVersion: "2.31.2", - storeDir: createThunk(() => { - throw new Error("stub storeDir evaluated"); + langVersion: 6, + nixPath: [], + nixVersion: "2.31.2", + storeDir: createThunk(() => { + throw new Error("stub storeDir evaluated"); + }), }), -}; +); diff --git a/nix-js/runtime-ts/src/builtins/io.ts b/nix-js/runtime-ts/src/builtins/io.ts index 7749bd5..8040161 100644 --- a/nix-js/runtime-ts/src/builtins/io.ts +++ b/nix-js/runtime-ts/src/builtins/io.ts @@ -1,3 +1,4 @@ +import { select } from "../helpers"; import { getPathValue } from "../path"; import type { NixStringContext, StringWithContext } from "../string-context"; import { addOpaqueContext, decodeContextElem, mkStringWithContext } from "../string-context"; @@ -59,7 +60,7 @@ export const scopedImport = (scope: NixValue) => (path: NixValue): NixValue => { const scopeAttrs = forceAttrs(scope); - const scopeKeys = Object.keys(scopeAttrs); + const scopeKeys = Array.from(scopeAttrs.keys()); const pathStr = realisePath(path); @@ -112,25 +113,23 @@ const normalizeUrlInput = ( return { url: forced }; } const attrs = forceAttrs(args); - const url = forceStringValue(attrs.url); - const hash = - "sha256" in attrs - ? forceStringValue(attrs.sha256) - : "hash" in attrs - ? forceStringValue(attrs.hash) - : undefined; - const name = "name" in attrs ? forceStringValue(attrs.name) : undefined; - const executable = "executable" in attrs ? forceBool(attrs.executable) : false; + const url = forceStringValue(select(attrs, ["url"])); + const hash = attrs.has("sha256") + ? forceStringValue(attrs.get("sha256") as NixValue) + : attrs.has("hash") + ? forceStringValue(attrs.get("hash") as NixValue) + : undefined; + const name = attrs.has("name") ? forceStringValue(attrs.get("name") as NixValue) : undefined; + const executable = attrs.has("executable") ? forceBool(attrs.get("executable") as NixValue) : false; return { url, hash, name, executable }; }; const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string; name?: string } => { const forced = force(args); if (isAttrs(forced)) { - const url = resolvePseudoUrl(forceStringNoCtx(forced.url)); - const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined; - const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined; - // FIXME: extract baseNameOfRaw + const url = resolvePseudoUrl(forceStringNoCtx(select(forced, ["url"]))); + const sha256 = forced.has("sha256") ? forceStringNoCtx(forced.get("sha256") as NixValue) : undefined; + const nameRaw = forced.has("name") ? forceStringNoCtx(forced.get("name") as NixValue) : undefined; const name = nameRaw === "" ? (baseNameOf(nameRaw) as string) : nameRaw; return { url, sha256, name }; } else { @@ -175,25 +174,25 @@ export const fetchGit = (args: NixValue): NixAttrs => { const result = Deno.core.ops.op_fetch_git(url, null, null, false, false, false, null); const outContext: NixStringContext = new Set(); addOpaqueContext(outContext, result.out_path); - return { - outPath: mkStringWithContext(result.out_path, outContext), - rev: result.rev, - shortRev: result.short_rev, - revCount: BigInt(result.rev_count), - lastModified: BigInt(result.last_modified), - lastModifiedDate: result.last_modified_date, - submodules: result.submodules, - narHash: result.nar_hash, - }; + return new Map([ + ["outPath", mkStringWithContext(result.out_path, outContext)], + ["rev", result.rev], + ["shortRev", result.short_rev], + ["revCount", BigInt(result.rev_count)], + ["lastModified", BigInt(result.last_modified)], + ["lastModifiedDate", result.last_modified_date], + ["submodules", result.submodules], + ["narHash", result.nar_hash], + ]); } const attrs = forceAttrs(args); - const url = forceStringValue(attrs.url); - const gitRef = "ref" in attrs ? forceStringValue(attrs.ref) : null; - const rev = "rev" in attrs ? forceStringValue(attrs.rev) : null; - const shallow = "shallow" in attrs ? forceBool(attrs.shallow) : false; - const submodules = "submodules" in attrs ? forceBool(attrs.submodules) : false; - const allRefs = "allRefs" in attrs ? forceBool(attrs.allRefs) : false; - const name = "name" in attrs ? forceStringValue(attrs.name) : null; + const url = forceStringValue(select("attrs", ["url"])); + const gitRef = attrs.has("ref") ? forceStringValue(attrs.get("ref") as NixValue) : null; + const rev = attrs.has("rev") ? forceStringValue(attrs.get("rev") as NixValue) : null; + const shallow = attrs.has("shallow") ? forceBool(attrs.get("shallow") as NixValue) : false; + const submodules = attrs.has("submodules") ? forceBool(attrs.get("submodules") as NixValue) : false; + const allRefs = attrs.has("allRefs") ? forceBool(attrs.get("allRefs") as NixValue) : false; + const name = attrs.has("name") ? forceStringValue(attrs.get("name") as NixValue) : null; const result: FetchGitResult = Deno.core.ops.op_fetch_git( url, @@ -207,16 +206,16 @@ export const fetchGit = (args: NixValue): NixAttrs => { const outContext: NixStringContext = new Set(); addOpaqueContext(outContext, result.out_path); - return { - outPath: mkStringWithContext(result.out_path, outContext), - rev: result.rev, - shortRev: result.short_rev, - revCount: BigInt(result.rev_count), - lastModified: BigInt(result.last_modified), - lastModifiedDate: result.last_modified_date, - submodules: result.submodules, - narHash: result.nar_hash, - }; + return new Map([ + ["outPath", mkStringWithContext(result.out_path, outContext)], + ["rev", result.rev], + ["shortRev", result.short_rev], + ["revCount", BigInt(result.rev_count)], + ["lastModified", BigInt(result.last_modified)], + ["lastModifiedDate", result.last_modified_date], + ["submodules", result.submodules], + ["narHash", result.nar_hash], + ]); }; export const fetchMercurial = (_args: NixValue): NixAttrs => { @@ -225,7 +224,7 @@ export const fetchMercurial = (_args: NixValue): NixAttrs => { export const fetchTree = (args: NixValue): NixAttrs => { const attrs = forceAttrs(args); - const type = "type" in attrs ? forceStringValue(attrs.type) : "auto"; + const type = attrs.has("type") ? forceStringValue(attrs.get("type") as NixValue) : "auto"; switch (type) { case "git": @@ -234,12 +233,12 @@ export const fetchTree = (args: NixValue): NixAttrs => { case "mercurial": return fetchMercurial(args); case "tarball": - return { outPath: fetchTarball(args) }; + return new Map([["outPath", fetchTarball(args)]]); case "file": - return { outPath: fetchurl(args) }; + return new Map([["outPath", fetchurl(args)]]); case "path": { - const path = forceStringValue(attrs.path); - return { outPath: path }; + const path = forceStringValue(select(attrs, ["path"])); + return new Map([["outPath", path]]); } case "github": case "gitlab": @@ -251,11 +250,14 @@ export const fetchTree = (args: NixValue): NixAttrs => { }; const fetchGitForge = (forge: string, attrs: NixAttrs): NixAttrs => { - const owner = forceStringValue(attrs.owner); - const repo = forceStringValue(attrs.repo); - const rev = - "rev" in attrs ? forceStringValue(attrs.rev) : "ref" in attrs ? forceStringValue(attrs.ref) : "HEAD"; - const host = "host" in attrs ? forceStringValue(attrs.host) : undefined; + const owner = forceStringValue(select(forge, ["owner"])); + const repo = forceStringValue(select(forge, ["repo"])); + const rev = attrs.has("rev") + ? forceStringValue(attrs.get("rev") as NixValue) + : attrs.has("ref") + ? forceStringValue(attrs.get("ref") as NixValue) + : "HEAD"; + const host = attrs.has("host") ? forceStringValue(attrs.get("host") as NixValue) : undefined; let tarballUrl: string; switch (forge) { @@ -278,17 +280,17 @@ const fetchGitForge = (forge: string, attrs: NixAttrs): NixAttrs => { throw new Error(`Unknown forge type: ${forge}`); } - const outPath = fetchTarball({ url: tarballUrl, ...attrs }); + const outPath = fetchTarball(new Map([["url", tarballUrl], ...attrs])); - return { - outPath, - rev, - shortRev: rev.substring(0, 7), - }; + return new Map([ + ["outPath", outPath], + ["rev", rev], + ["shortRev", rev.substring(0, 7)], + ]); }; const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => { - const url = forceStringValue(attrs.url); + const url = forceStringValue(select(attrs, ["url"])); if (url.endsWith(".git") || url.includes("github.com") || url.includes("gitlab.com")) { return fetchGit(attrs); } @@ -298,17 +300,17 @@ const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => { url.endsWith(".tar.bz2") || url.endsWith(".tgz") ) { - return { outPath: fetchTarball(attrs) }; + return new Map([["outPath", fetchTarball(attrs)]]); } - return { outPath: fetchurl(attrs) }; + return new Map([["outPath", fetchurl(attrs)]]); }; export const readDir = (path: NixValue): NixAttrs => { const pathStr = realisePath(path); const entries: Record = Deno.core.ops.op_read_dir(pathStr); - const result: NixAttrs = {}; + const result: NixAttrs = new Map(); for (const [name, type] of Object.entries(entries)) { - result[name] = type; + result.set(name, type); } return result; }; @@ -348,11 +350,11 @@ export const pathExists = (path: NixValue): boolean => { export const path = (args: NixValue): NixString => { const attrs = forceAttrs(args); - if (!("path" in attrs)) { + if (!attrs.has("path")) { throw new TypeError("builtins.path: 'path' attribute is required"); } - const pathValue = force(attrs.path); + const pathValue = force(attrs.get("path") as NixValue); let pathStr: string; if (isNixPath(pathValue)) { @@ -361,14 +363,14 @@ export const path = (args: NixValue): NixString => { pathStr = forceStringValue(pathValue); } - const name = "name" in attrs ? forceStringValue(attrs.name) : null; - const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true; - const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null; + const name = attrs.has("name") ? forceStringValue(attrs.get("name") as NixValue) : null; + const recursive = attrs.has("recursive") ? forceBool(attrs.get("recursive") as NixValue) : true; + const sha256 = attrs.has("sha256") ? forceStringValue(attrs.get("sha256") as NixValue) : null; let storePath: string; - if ("filter" in attrs) { - const filterFn = forceFunction(attrs.filter); + if (attrs.has("filter")) { + const filterFn = forceFunction(attrs.get("filter") as NixValue); const entries: [string, string][] = Deno.core.ops.op_walk_dir(pathStr); @@ -445,9 +447,9 @@ export const findFile = for (const item of forcedSearchPath) { const attrs = forceAttrs(item); - const prefix = "prefix" in attrs ? forceStringNoCtx(attrs.prefix) : ""; + const prefix = attrs.has("prefix") ? forceStringNoCtx(attrs.get("prefix") as NixValue) : ""; - if (!("path" in attrs)) { + if (!attrs.has("path")) { throw new Error("findFile: search path element is missing 'path' attribute"); } @@ -457,7 +459,12 @@ export const findFile = } const context: NixStringContext = new Set(); - const pathVal = coerceToString(attrs.path, StringCoercionMode.Interpolation, false, context); + const pathVal = coerceToString( + attrs.get("path") as NixValue, + StringCoercionMode.Interpolation, + false, + context, + ); if (context.size > 0) { throw new Error("findFile: path with string context is not yet supported"); diff --git a/nix-js/runtime-ts/src/builtins/list.ts b/nix-js/runtime-ts/src/builtins/list.ts index c986e00..dc3f4e3 100644 --- a/nix-js/runtime-ts/src/builtins/list.ts +++ b/nix-js/runtime-ts/src/builtins/list.ts @@ -95,18 +95,19 @@ export const partition = (list: NixValue): NixAttrs => { const forcedList = forceList(list); const forcedPred = forceFunction(pred); - const attrs = { - right: [] as NixList, - wrong: [] as NixList, - }; + const right: NixList = []; + const wrong: NixList = []; for (const elem of forcedList) { if (force(forcedPred(elem))) { - attrs.right.push(elem); + right.push(elem); } else { - attrs.wrong.push(elem); + wrong.push(elem); } } - return attrs; + return new Map([ + ["right", right], + ["wrong", wrong], + ]); }; export const genList = diff --git a/nix-js/runtime-ts/src/builtins/misc.ts b/nix-js/runtime-ts/src/builtins/misc.ts index 0966c53..c2cde1b 100644 --- a/nix-js/runtime-ts/src/builtins/misc.ts +++ b/nix-js/runtime-ts/src/builtins/misc.ts @@ -1,4 +1,5 @@ import { OrderedSet } from "js-sdsl"; +import { select } from "../helpers"; import { compareValues } from "../operators"; import { getStringContext, @@ -15,7 +16,7 @@ import { forceStringNoCtx, forceStringValue, } from "../type-assert"; -import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types"; +import type { NixAttrs, NixStrictValue, NixValue } from "../types"; import { ATTR_POSITIONS, CatchableError } from "../types"; import * as context from "./context"; import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check"; @@ -137,24 +138,20 @@ function componentsLt(c1: string, c2: string): boolean { export const functionArgs = (f: NixValue): NixAttrs => { const func = forceFunction(f); if (func.args) { - const ret: NixAttrs = {}; + const ret: NixAttrs = new Map(); for (const key of func.args.required) { - ret[key] = false; + ret.set(key, false); } for (const key of func.args.optional) { - ret[key] = true; + ret.set(key, true); } const positions = func.args.positions; if (positions && Object.keys(positions).length > 0) { - Object.defineProperty(ret, ATTR_POSITIONS, { - value: positions, - enumerable: false, - writable: false, - }); + ret[ATTR_POSITIONS] = positions; } return ret; } - return {}; + return new Map(); }; const checkComparable = (value: NixStrictValue): void => { @@ -166,7 +163,8 @@ const checkComparable = (value: NixStrictValue): void => { export const genericClosure = (args: NixValue): NixValue => { const forcedArgs = forceAttrs(args); - const { startSet, operator } = forcedArgs; + const startSet = select(forcedArgs, ["startSet"]); + const operator = select(forcedArgs, ["operator"]); const initialList = forceList(startSet); const opFunction = forceFunction(operator); @@ -177,7 +175,7 @@ export const genericClosure = (args: NixValue): NixValue => { for (const item of initialList) { const itemAttrs = forceAttrs(item); - const key = force(itemAttrs.key); + const key = force(select(itemAttrs, ["key"])); checkComparable(key); if (resultSet.find(key).equals(resultSet.end())) { resultSet.insert(key); @@ -193,7 +191,7 @@ export const genericClosure = (args: NixValue): NixValue => { for (const newItem of newItems) { const newItemAttrs = forceAttrs(newItem); - const key = force(newItemAttrs.key); + const key = force(select(newItemAttrs, ["key"])); checkComparable(key); if (resultSet.find(key).equals(resultSet.end())) { resultSet.insert(key); @@ -223,10 +221,10 @@ export const parseDrvName = (s: NixValue): NixAttrs => { break; } } - return { - name, - version, - }; + return new Map([ + ["name", name], + ["version", version], + ]); }; export const placeholder = (output: NixValue): NixValue => { @@ -322,21 +320,21 @@ export const splitVersion = (s: NixValue): NixValue => { export const traceVerbose = (_e1: NixValue, e2: NixValue): NixStrictValue => { // TODO: implement traceVerbose - return force(e2) + return force(e2); }; -export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue } => { +export const tryEval = (e: NixValue): NixAttrs => { try { - return { - success: true, - value: force(e), - }; + return new Map([ + ["success", true], + ["value", force(e)], + ]); } catch (err) { if (err instanceof CatchableError) { - return { - success: false, - value: false, - }; + return new Map([ + ["success", false], + ["value", false], + ]); } else { throw err; } diff --git a/nix-js/runtime-ts/src/builtins/type-check.ts b/nix-js/runtime-ts/src/builtins/type-check.ts index 568e97b..274e89c 100644 --- a/nix-js/runtime-ts/src/builtins/type-check.ts +++ b/nix-js/runtime-ts/src/builtins/type-check.ts @@ -1,5 +1,4 @@ import { - HAS_CONTEXT, isNixPath, isStringWithContext, type NixAttrs, @@ -19,10 +18,7 @@ export const isNixString = (v: NixStrictValue): v is NixString => { }; export const isAttrs = (e: NixStrictValue): e is NixAttrs => { - const val = e; - return ( - typeof val === "object" && !Array.isArray(val) && val !== null && !(HAS_CONTEXT in val) && !isPath(val) - ); + return e instanceof Map; }; export const isBool = (e: NixStrictValue): e is NixBool => typeof e === "boolean"; @@ -60,7 +56,7 @@ export const typeOf = (e: NixStrictValue): NixType => { if (isNixString(e)) return "string"; if (isNixPath(e)) return "path"; if (Array.isArray(e)) return "list"; - if (typeof e === "object") return "set"; + if (e instanceof Map) return "set"; if (typeof e === "function") return "lambda"; throw new TypeError(`Unknown Nix type: ${typeof e}`); diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index 28aa3c3..1b214d6 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -3,7 +3,7 @@ import { isAttrs, typeOf } from "./builtins/type-check"; import { mkPath } from "./path"; import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context"; import { force } from "./thunk"; -import { forceAttrs, forceBool, forceFunction, forceStringValue } from "./type-assert"; +import { forceAttrs, forceBool, forceFunction, forceStringNoCtx, forceStringValue } from "./type-assert"; import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types"; import { CatchableError, isNixPath } from "./types"; @@ -169,18 +169,18 @@ function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue { for (const attr of attrpath.slice(0, -1)) { const key = forceStringValue(attr); - if (!(key in attrs)) { + if (!attrs.has(key)) { throw new Error(`Attribute '${key}' not found`); } - const cur = forceAttrs(attrs[forceStringValue(attr)]); + const cur = forceAttrs(attrs.get(key) as NixValue); attrs = cur; } const last = forceStringValue(attrpath[attrpath.length - 1]); - if (!(last in attrs)) { + if (!attrs.has(last)) { throw new Error(`Attribute '${last}' not found`); } - return attrs[last]; + return attrs.get(last) as NixValue; } export const selectWithDefault = ( @@ -218,10 +218,10 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal: for (const attr of attrpath.slice(0, -1)) { const key = forceStringValue(attr); - if (!(key in attrs)) { + if (!attrs.has(key)) { return defaultVal; } - const cur = force(attrs[key]); + const cur = force(attrs.get(key) as NixValue); if (!isAttrs(cur)) { return defaultVal; } @@ -229,8 +229,8 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal: } const last = forceStringValue(attrpath[attrpath.length - 1]); - if (last in attrs) { - return attrs[last]; + if (attrs.has(last)) { + return attrs.get(last) as NixValue; } return defaultVal; } @@ -243,14 +243,18 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => { let attrs = forced; for (const attr of attrpath.slice(0, -1)) { - const cur = force(attrs[forceStringValue(attr)]); + const key = forceStringNoCtx(attr); + if (!attrs.has(key)) { + return false; + } + const cur = force(attrs.get(key) as NixValue); if (!isAttrs(cur)) { return false; } attrs = cur; } - return forceStringValue(attrpath[attrpath.length - 1]) in attrs; + return attrs.has(forceStringValue(attrpath[attrpath.length - 1])); }; export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => { @@ -277,13 +281,8 @@ function callImpl(func: NixValue, arg: NixValue): NixValue { forcedFunc.args?.check(arg); return forcedFunc(arg); } - if ( - typeof forcedFunc === "object" && - !Array.isArray(forcedFunc) && - forcedFunc !== null && - "__functor" in forcedFunc - ) { - const functor = forceFunction(forcedFunc.__functor); + if (forcedFunc instanceof Map && forcedFunc.has("__functor")) { + const functor = forceFunction(forcedFunc.get("__functor") as NixValue); return call(functor(forcedFunc), arg); } throw new Error(`attempt to call something which is not a function but ${typeOf(forcedFunc)}`); @@ -307,7 +306,7 @@ export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => { }; export const mkPos = (span: string): NixAttrs => { - return Deno.core.ops.op_decode_span(span); + return new Map(Object.entries(Deno.core.ops.op_decode_span(span))); }; interface WithScope { @@ -319,8 +318,8 @@ export const lookupWith = (name: string, withScope: WithScope | null): NixValue let current = withScope; while (current !== null) { const attrs = forceAttrs(current.env); - if (name in attrs) { - return attrs[name]; + if (attrs.has(name)) { + return attrs.get(name) as NixValue; } current = current.last; } diff --git a/nix-js/runtime-ts/src/operators.ts b/nix-js/runtime-ts/src/operators.ts index 26ad183..4f47724 100644 --- a/nix-js/runtime-ts/src/operators.ts +++ b/nix-js/runtime-ts/src/operators.ts @@ -16,8 +16,8 @@ import { isNixPath } from "./types"; const canCoerceToString = (v: NixValue): boolean => { const forced = force(v); if (isNixString(forced)) return true; - if (typeof forced === "object" && forced !== null && !Array.isArray(forced)) { - if ("outPath" in forced || "__toString" in forced) return true; + if (forced instanceof Map) { + if (forced.has("outPath") || forced.has("__toString")) return true; } return false; }; @@ -219,27 +219,24 @@ export const op = { const attrsA = av as NixAttrs; const attrsB = bv as NixAttrs; - // Derivation comparison: compare outPaths only - // Safe to force 'type' because it's always a string literal, never a computed value - if ("type" in attrsA && "type" in attrsB) { - const typeValA = force(attrsA.type); - const typeValB = force(attrsB.type); + if (attrsA.has("type") && attrsB.has("type")) { + const typeValA = force(attrsA.get("type") as NixValue); + const typeValB = force(attrsB.get("type") as NixValue); if (typeValA === "derivation" && typeValB === "derivation") { - if ("outPath" in attrsA && "outPath" in attrsB) { - return op.eq(attrsA.outPath, attrsB.outPath); + if (attrsA.has("outPath") && attrsB.has("outPath")) { + return op.eq(attrsA.get("outPath") as NixValue, attrsB.get("outPath") as NixValue); } } } - // Otherwise, compare attributes one by one - const keysA = Object.keys(attrsA).sort(); - const keysB = Object.keys(attrsB).sort(); + const keysA = Array.from(attrsA.keys()).sort(); + const keysB = Array.from(attrsB.keys()).sort(); if (keysA.length !== keysB.length) return false; for (let i = 0; i < keysA.length; i++) { if (keysA[i] !== keysB[i]) return false; - if (!op.eq(attrsA[keysA[i]], attrsB[keysB[i]])) return false; + if (!op.eq(attrsA.get(keysA[i]) as NixValue, attrsB.get(keysB[i]) as NixValue)) return false; } return true; @@ -270,5 +267,13 @@ export const op = { return forceList(a).concat(forceList(b)); }, - update: (a: NixValue, b: NixValue): NixAttrs => ({ ...forceAttrs(a), ...forceAttrs(b) }), + update: (a: NixValue, b: NixValue): NixAttrs => { + const mapA = forceAttrs(a); + const mapB = forceAttrs(b); + const result: NixAttrs = new Map(mapA); + for (const [k, v] of mapB) { + result.set(k, v); + } + return result; + }, }; diff --git a/nix-js/runtime-ts/src/path.ts b/nix-js/runtime-ts/src/path.ts index fd69745..2da34fa 100644 --- a/nix-js/runtime-ts/src/path.ts +++ b/nix-js/runtime-ts/src/path.ts @@ -14,12 +14,11 @@ const canonicalizePath = (path: string): string => { const component = path.slice(i, j); i = j; - if (component === ".") { - } else if (component === "..") { + if (component === "..") { if (parts.length > 0) { parts.pop(); } - } else { + } else if (component !== ".") { parts.push(component); } } diff --git a/nix-js/runtime-ts/src/thunk.ts b/nix-js/runtime-ts/src/thunk.ts index df6d354..146e741 100644 --- a/nix-js/runtime-ts/src/thunk.ts +++ b/nix-js/runtime-ts/src/thunk.ts @@ -1,6 +1,6 @@ import { isAttrs, isList } from "./builtins/type-check"; import { HAS_CONTEXT } from "./string-context"; -import type { NixStrictValue, NixThunkInterface, NixValue } from "./types"; +import type { NixAttrs, NixStrictValue, NixThunkInterface, NixValue } from "./types"; import { IS_PATH } from "./types"; export const IS_THUNK = Symbol("is_thunk"); @@ -117,7 +117,7 @@ export const createThunk = (func: () => NixValue, label?: string): NixThunkInter }; export const IS_CYCLE = Symbol("is_cycle"); -export const CYCLE_MARKER = { [IS_CYCLE]: true }; +export const CYCLE_MARKER = { [IS_CYCLE]: true as const }; /** * Deeply force a value, handling cycles by returning a special marker. @@ -150,10 +150,10 @@ export const forceDeep = (value: NixValue, seen: WeakSet = new WeakSet() return forced.map((item) => forceDeep(item, seen)); } - if (typeof forced === "object") { - const result: Record = {}; - for (const [key, val] of Object.entries(forced)) { - result[key] = forceDeep(val, seen); + if (forced instanceof Map) { + const result: NixAttrs = new Map(); + for (const [key, val] of forced) { + result.set(key, forceDeep(val, seen)); } return result; } @@ -180,10 +180,10 @@ export const forceShallow = (value: NixValue): NixStrictValue => { } if (isAttrs(forced)) { - const result: Record = {}; - for (const [key, val] of Object.entries(forced)) { - const forcedVal = force(val); - result[key] = forcedVal === forced ? CYCLE_MARKER : forcedVal; + const result: NixAttrs = new Map(); + for (const [key, val] of forced) { + const forcedVal = force(val as NixValue); + result.set(key, forcedVal === forced ? CYCLE_MARKER : forcedVal); } return result; } diff --git a/nix-js/runtime-ts/src/type-assert.ts b/nix-js/runtime-ts/src/type-assert.ts index 79afe7d..a82624c 100644 --- a/nix-js/runtime-ts/src/type-assert.ts +++ b/nix-js/runtime-ts/src/type-assert.ts @@ -26,9 +26,9 @@ export const forceFunction = (value: NixValue): NixFunction => { if (isFunction(forced)) { return forced; } - if (typeof forced === "object" && !Array.isArray(forced) && forced !== null && "__functor" in forced) { + if (forced instanceof Map && forced.has("__functor")) { const functorSet = forced as NixAttrs; - const functor = forceFunction(functorSet.__functor); + const functor = forceFunction(functorSet.get("__functor") as NixValue); return (arg: NixValue) => forceFunction(functor(functorSet))(arg); } throw new TypeError(`Expected function, got ${typeOf(forced)}`); diff --git a/nix-js/runtime-ts/src/types.ts b/nix-js/runtime-ts/src/types.ts index 327ddf5..20a89e9 100644 --- a/nix-js/runtime-ts/src/types.ts +++ b/nix-js/runtime-ts/src/types.ts @@ -1,5 +1,5 @@ import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context"; -import { force, IS_THUNK } from "./thunk"; +import { type CYCLE_MARKER, force, IS_THUNK } from "./thunk"; import { forceAttrs, forceStringNoCtx } from "./type-assert"; export { HAS_CONTEXT, isStringWithContext }; export type { StringWithContext }; @@ -22,9 +22,9 @@ export type NixBool = boolean; export type NixString = string | StringWithContext; export type NixNull = null; +export const ATTR_POSITIONS = Symbol("attrPositions"); export type NixList = NixValue[]; -// FIXME: reject contextful string -export type NixAttrs = { [key: string]: NixValue }; +export type NixAttrs = Map & { [ATTR_POSITIONS]?: Record }; export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs }; export class NixArgs { required: string[]; @@ -43,13 +43,13 @@ export class NixArgs { const attrs = forceAttrs(arg); for (const key of this.required) { - if (!Object.hasOwn(attrs, key)) { + if (!attrs.has(key)) { throw new Error(`Function called without required argument '${key}'`); } } if (!this.ellipsis) { - for (const key in attrs) { + for (const key of attrs.keys()) { if (!this.allowed.has(key)) { throw new Error(`Function called with unexpected argument '${key}'`); } @@ -77,18 +77,18 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]): continue; } const str = forceStringNoCtx(key); - attrs[str] = values[i]; + attrs.set(str, values[i]); } return attrs; }; -export const ATTR_POSITIONS = Symbol("attrPositions"); - export const mkAttrsWithPos = ( - attrs: NixAttrs, + obj: Record, positions: Record, dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] }, ): NixAttrs => { + const attrs: NixAttrs = new Map(Object.entries(obj)); + if (dyns) { const len = dyns.dynKeys.length; for (let i = 0; i < len; i++) { @@ -97,17 +97,13 @@ export const mkAttrsWithPos = ( continue; } const str = forceStringNoCtx(key); - attrs[str] = dyns.dynVals[i]; + attrs.set(str, dyns.dynVals[i]); positions[str] = dyns.dynSpans[i]; } } if (Object.keys(positions).length > 0) { - Object.defineProperty(attrs, ATTR_POSITIONS, { - value: positions, - enumerable: false, - writable: false, - }); + attrs[ATTR_POSITIONS] = positions; } return attrs; @@ -120,7 +116,14 @@ export interface NixThunkInterface { } export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString; -export type NixValue = NixPrimitive | NixPath | NixList | NixAttrs | NixFunction | NixThunkInterface; +export type NixValue = + | NixPrimitive + | NixPath + | NixList + | NixAttrs + | NixFunction + | NixThunkInterface + | typeof CYCLE_MARKER; export type NixStrictValue = Exclude; export class CatchableError extends Error {} diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index ca565df..46a37a9 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -263,9 +263,9 @@ impl Compile for Ir { } &Ir::Builtin(Builtin { inner: name, .. }) => { code!(buf, ctx; - "Nix.builtins[" + "Nix.builtins.get(" ctx.get_sym(name) - "]" + ")" ); } Ir::ConcatStrings(x) => x.compile(ctx, buf), @@ -309,9 +309,9 @@ impl Compile for Ir { } &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { code!(buf, ctx; - "__scope[" + "__scope.get(" ctx.get_sym(name) - "]" + ")" ); } Ir::WithExpr(x) => x.compile(ctx, buf), @@ -632,7 +632,7 @@ impl Compile for AttrSet { "})" ); } else { - code!(buf, ctx; "{}"); + code!(buf, ctx; "new Map()"); } } } diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index 0deecc0..ffdbf3a 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -123,7 +123,7 @@ impl Context { let code = self.ctx.compile(source, None)?; self.runtime.eval( format!( - "Nix.builtins.derivation = {};Nix.builtins.storeDir=\"{}\"", + "Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}')", code, self.get_store_dir() ), diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index e5bd710..c13c2e0 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -364,6 +364,28 @@ fn to_value<'a>( Value::Func } } + _ if val.is_map() => { + let val = val.try_cast::().expect("infallible conversion"); + let size = val.size() as u32; + let array = val.as_array(scope); + let attrs = (0..size) + .map(|i| { + let key = array.get_index(scope, i * 2).expect("infallible index operation"); + let key = key.to_rust_string_lossy(scope); + let val = array.get_index(scope, i * 2 + 1).expect("infallible index operation"); + let val = to_value( + val, + scope, + is_thunk_symbol, + primop_metadata_symbol, + has_context_symbol, + is_path_symbol, + is_cycle_symbol, + ); + (Symbol::new(Cow::Owned(key)), val) + }).collect(); + Value::AttrSet(AttrSet::new(attrs)) + } _ if val.is_object() => { if is_thunk(val, scope, is_thunk_symbol) { return Value::Thunk; diff --git a/nix-js/src/runtime/ops.rs b/nix-js/src/runtime/ops.rs index b265e17..c91ae04 100644 --- a/nix-js/src/runtime/ops.rs +++ b/nix-js/src/runtime/ops.rs @@ -745,10 +745,10 @@ impl<'a> deno_core::convert::ToV8<'a> for NixJsonValue { scope: &mut v8::PinScope<'a, 'i>, ) -> std::result::Result, Self::Error> { Ok(match self { - Self::Null => v8::null(scope).into() , - Self::Bool(b) => v8::Boolean::new(scope, b).into() , - Self::Int(i) => v8::BigInt::new_from_i64(scope, i).into() , - Self::Float(f) => v8::Number::new(scope, f).into() , + Self::Null => v8::null(scope).into(), + Self::Bool(b) => v8::Boolean::new(scope, b).into(), + Self::Int(i) => v8::BigInt::new_from_i64(scope, i).into(), + Self::Float(f) => v8::Number::new(scope, f).into(), Self::Str(s) => v8::String::new(scope, &s) .map(|s| s.into()) .ok_or("failed to create v8 string")?, @@ -757,18 +757,18 @@ impl<'a> deno_core::convert::ToV8<'a> for NixJsonValue { .into_iter() .map(|v| v.to_v8(scope)) .collect::, _>>()?; - v8::Array::new_with_elements(scope, &elements).into() + v8::Array::new_with_elements(scope, &elements).into() } Self::Obj(entries) => { - let obj = v8::Object::new(scope); + let map = v8::Map::new(scope); for (k, v) in entries { let key: v8::Local = v8::String::new(scope, &k) .ok_or("failed to create v8 string")? .into(); let val = v.to_v8(scope)?; - obj.set(scope, key, val); + map.set(scope, key, val); } - obj.into() + map.into() } }) } @@ -1227,19 +1227,19 @@ impl<'s> XmlCtx<'s> { nix_obj: v8::Local<'s, v8::Object>, ) -> Result { let get_fn = |scope: &v8::PinScope<'s, 'i>, name: &str| { - let key = v8::String::new(scope, name).ok_or("v8 string" )?; + let key = v8::String::new(scope, name).ok_or("v8 string")?; let val = nix_obj .get(scope, key.into()) - .ok_or_else(|| format!("no {name}") )?; + .ok_or_else(|| format!("no {name}"))?; v8::Local::::try_from(val) - .map_err(|e| format!("{name} not function: {e}") ) + .map_err(|e| format!("{name} not function: {e}")) }; let get_sym = |scope: &v8::PinScope<'s, 'i>, name: &str| { - let key = v8::String::new(scope, name).ok_or("v8 string" )?; + let key = v8::String::new(scope, name).ok_or("v8 string")?; let val = nix_obj .get(scope, key.into()) - .ok_or_else(|| format!("no {name}") )?; - v8::Local::::try_from(val).map_err(|e| format!("{name} not symbol: {e}") ) + .ok_or_else(|| format!("no {name}"))?; + v8::Local::::try_from(val).map_err(|e| format!("{name} not symbol: {e}")) }; Ok(Self { force_fn: get_fn(scope, "force")?, @@ -1378,12 +1378,12 @@ impl XmlWriter { } } - fn is_derivation<'s>(obj: v8::Local<'s, v8::Object>, scope: &mut v8::PinScope<'s, '_>) -> bool { + fn is_derivation<'s>(map: v8::Local<'s, v8::Map>, scope: &mut v8::PinScope<'s, '_>) -> bool { let key = match v8::String::new(scope, "type") { Some(k) => k, None => return false, }; - match obj.get(scope, key.into()) { + match map.get(scope, key.into()) { Some(v) if v.is_string() => v.to_rust_string_lossy(scope) == "derivation", _ => false, } @@ -1414,7 +1414,7 @@ impl XmlWriter { return Ok(()); } if val.is_big_int() { - let bi = val.to_big_int(scope).ok_or("bigint" )?; + let bi = val.to_big_int(scope).ok_or("bigint")?; let (i, _) = bi.i64_value(); self.indent(depth); self.buf.push_str("::try_from(val).map_err(|e| e.to_string() )?; + let arr = v8::Local::::try_from(val).map_err(|e| e.to_string())?; self.indent(depth); self.buf.push_str("\n"); for i in 0..arr.length() { - let elem = arr.get_index(scope, i).ok_or("array elem" )?; + let elem = arr.get_index(scope, i).ok_or("array elem")?; self.write_value(elem, scope, ctx, depth + 1)?; } self.indent(depth); @@ -1453,14 +1453,18 @@ impl XmlWriter { if val.is_function() { return self.write_function(val, scope, ctx, depth); } + if val.is_map() { + let map = v8::Local::::try_from(val).map_err(|e| e.to_string())?; + return self.write_attrs(map, scope, ctx, depth); + } if val.is_object() { - let obj = val.to_object(scope).ok_or("to_object" )?; + let obj = val.to_object(scope).ok_or("to_object")?; if Self::has_sym(obj, scope, ctx.has_context) { - let key = v8::String::new(scope, "value").ok_or("v8 str" )?; + let key = v8::String::new(scope, "value").ok_or("v8 str")?; let s = obj .get(scope, key.into()) - .ok_or("value" )? + .ok_or("value")? .to_rust_string_lossy(scope); self.collect_context(obj, scope); self.indent(depth); @@ -1470,10 +1474,10 @@ impl XmlWriter { return Ok(()); } if Self::has_sym(obj, scope, ctx.is_path) { - let key = v8::String::new(scope, "value").ok_or("v8 str" )?; + let key = v8::String::new(scope, "value").ok_or("v8 str")?; let s = obj .get(scope, key.into()) - .ok_or("value" )? + .ok_or("value")? .to_rust_string_lossy(scope); self.indent(depth); self.buf.push_str("\n"); return Ok(()); } - - return self.write_attrs(obj, scope, ctx, depth); } self.indent(depth); @@ -1497,31 +1499,39 @@ impl XmlWriter { fn write_attrs<'s>( &mut self, - obj: v8::Local<'s, v8::Object>, + map: v8::Local<'s, v8::Map>, scope: &mut v8::PinScope<'s, '_>, ctx: &XmlCtx<'s>, depth: usize, ) -> Result<()> { - if Self::is_derivation(obj, scope) { + if Self::is_derivation(map, scope) { self.indent(depth); self.buf.push_str(" = + v8::String::new(scope, "drvPath").ok_or("v8 str")?.into(); + let drv_str = if let Some(drv_val) = map.get(scope, drv_path_key) { + if drv_val.is_undefined() { + None + } else { + let forced = self.force(drv_val, scope, ctx)?; + let s = self.extract_str(forced, scope, ctx); + if let Some(ref s) = s { + self.buf.push_str(" drvPath=\""); + self.escape_attr(s); + self.buf.push('"'); + } + s } - s } else { None }; - let out_path_key = v8::String::new(scope, "outPath").ok_or("v8 str" )?; - if let Some(out_val) = obj.get(scope, out_path_key.into()) { + let out_path_key: v8::Local = + v8::String::new(scope, "outPath").ok_or("v8 str")?.into(); + if let Some(out_val) = map.get(scope, out_path_key) + && !out_val.is_undefined() + { let forced = self.force(out_val, scope, ctx)?; if let Some(ref s) = self.extract_str(forced, scope, ctx) { self.buf.push_str(" outPath=\""); @@ -1542,7 +1552,7 @@ impl XmlWriter { self.indent(depth + 1); self.buf.push_str("\n"); } else { - self.write_attrs_sorted(obj, scope, ctx, depth + 1)?; + self.write_attrs_sorted(map, scope, ctx, depth + 1)?; } self.indent(depth); @@ -1550,7 +1560,7 @@ impl XmlWriter { } else { self.indent(depth); self.buf.push_str("\n"); - self.write_attrs_sorted(obj, scope, ctx, depth + 1)?; + self.write_attrs_sorted(map, scope, ctx, depth + 1)?; self.indent(depth); self.buf.push_str("\n"); } @@ -1559,34 +1569,32 @@ impl XmlWriter { fn write_attrs_sorted<'s>( &mut self, - obj: v8::Local<'s, v8::Object>, + map: v8::Local<'s, v8::Map>, scope: &mut v8::PinScope<'s, '_>, ctx: &XmlCtx<'s>, depth: usize, ) -> Result<()> { - let keys = obj - .get_own_property_names(scope, v8::GetPropertyNamesArgsBuilder::new().build()) - .ok_or("property names" )?; + let arr = map.as_array(scope); + let len = arr.length(); - let mut key_strings: Vec = Vec::with_capacity(keys.length() as usize); - for i in 0..keys.length() { - let key = keys.get_index(scope, i).ok_or("key index" )?; - key_strings.push(key.to_rust_string_lossy(scope)); + let mut entries: Vec<(String, v8::Local<'s, v8::Value>)> = + Vec::with_capacity((len / 2) as usize); + let mut i = 0; + while i < len { + let key = arr.get_index(scope, i).ok_or("map key")?; + let val = arr.get_index(scope, i + 1).ok_or("map value")?; + entries.push((key.to_rust_string_lossy(scope), val)); + i += 2; } - key_strings.sort(); - - for key_str in &key_strings { - let v8_key = v8::String::new(scope, key_str).ok_or("v8 str" )?; - let val = obj - .get(scope, v8_key.into()) - .ok_or("attr value" )?; + entries.sort_by(|a, b| a.0.cmp(&b.0)); + for (key_str, val) in &entries { self.indent(depth); self.buf.push_str("\n"); - self.write_value(val, scope, ctx, depth + 1)?; + self.write_value(*val, scope, ctx, depth + 1)?; self.indent(depth); self.buf.push_str("\n"); @@ -1601,7 +1609,7 @@ impl XmlWriter { ctx: &XmlCtx<'s>, depth: usize, ) -> Result<()> { - let obj = val.to_object(scope).ok_or("fn to_object" )?; + let obj = val.to_object(scope).ok_or("fn to_object")?; if let Some(meta) = obj.get(scope, ctx.primop_meta.into()) && meta.is_object() @@ -1612,16 +1620,16 @@ impl XmlWriter { return Ok(()); } - let args_key = v8::String::new(scope, "args").ok_or("v8 str" )?; + let args_key = v8::String::new(scope, "args").ok_or("v8 str")?; let args_val = obj.get(scope, args_key.into()); match args_val { Some(args) if args.is_object() && !args.is_null_or_undefined() => { - let args_obj = args.to_object(scope).ok_or("args to_object" )?; + let args_obj = args.to_object(scope).ok_or("args to_object")?; - let req_key = v8::String::new(scope, "required").ok_or("v8 str" )?; - let opt_key = v8::String::new(scope, "optional").ok_or("v8 str" )?; - let ellipsis_key = v8::String::new(scope, "ellipsis").ok_or("v8 str" )?; + let req_key = v8::String::new(scope, "required").ok_or("v8 str")?; + let opt_key = v8::String::new(scope, "optional").ok_or("v8 str")?; + let ellipsis_key = v8::String::new(scope, "ellipsis").ok_or("v8 str")?; let mut all_formals: Vec = Vec::new(); if let Some(req) = args_obj.get(scope, req_key.into()) @@ -1721,12 +1729,12 @@ impl<'a> FromV8<'a> for ToXmlResult { value: v8::Local<'a, v8::Value>, ) -> std::result::Result { let global = scope.get_current_context().global(scope); - let nix_key = v8::String::new(scope, "Nix").ok_or("v8 string" )?; + let nix_key = v8::String::new(scope, "Nix").ok_or("v8 string")?; let nix_obj = global .get(scope, nix_key.into()) - .ok_or("no Nix global" )? + .ok_or("no Nix global")? .to_object(scope) - .ok_or("Nix not object" )?; + .ok_or("Nix not object")?; let ctx = XmlCtx::new(scope, nix_obj)?;