chore: tidy

This commit is contained in:
2026-02-20 10:25:38 +08:00
parent 45096f5254
commit e1517c338e
10 changed files with 113 additions and 142 deletions

View File

@@ -289,13 +289,14 @@ export const toStringFunc = (value: NixValue): NixString => {
return coerceToStringWithContext(value, StringCoercionMode.ToString, false); return coerceToStringWithContext(value, StringCoercionMode.ToString, false);
}; };
export type JsonValue = number | boolean | string | null | { [key: string]: JsonValue } | Array<JsonValue>;
export const nixValueToJson = ( export const nixValueToJson = (
value: NixValue, value: NixValue,
strict: boolean, strict: boolean,
outContext: NixStringContext, outContext: NixStringContext,
copyToStore: boolean, copyToStore: boolean,
seen: Set<NixValue> = new Set(), seen: Set<NixValue> = new Set(),
): unknown => { ): JsonValue => {
const v = strict ? force(value) : value; const v = strict ? force(value) : value;
if (isThunk(v) || typeof v === "function") if (isThunk(v) || typeof v === "function")
@@ -358,7 +359,7 @@ export const nixValueToJson = (
return nixValueToJson(v.get("outPath") as NixValue, strict, outContext, copyToStore, seen); return nixValueToJson(v.get("outPath") as NixValue, strict, outContext, copyToStore, seen);
} }
const result: Record<string, unknown> = {}; const result: { [key: string]: JsonValue } = {};
const keys = Array.from(v.keys()).sort(); const keys = Array.from(v.keys()).sort();
for (const key of keys) { for (const key of keys) {
result[key] = nixValueToJson(v.get(key) as NixValue, strict, outContext, copyToStore, seen); result[key] = nixValueToJson(v.get(key) as NixValue, strict, outContext, copyToStore, seen);

View File

@@ -7,7 +7,7 @@ import {
import { force } from "../thunk"; import { force } from "../thunk";
import { forceAttrs, forceList, forceStringNoCtx, forceStringValue } from "../type-assert"; import { forceAttrs, forceList, forceStringNoCtx, forceStringValue } from "../type-assert";
import type { NixAttrs, NixValue } from "../types"; import type { NixAttrs, NixValue } from "../types";
import { coerceToString, nixValueToJson, StringCoercionMode } from "./conversion"; import { coerceToString, type JsonValue, nixValueToJson, StringCoercionMode } from "./conversion";
export interface OutputInfo { export interface OutputInfo {
path: string; path: string;
@@ -205,9 +205,9 @@ const structuredAttrsExcludedKeys = new Set([
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]); const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
const sortedJsonStringify = (obj: Record<string, unknown>): string => { const sortedJsonStringify = (obj: Record<string, JsonValue>): string => {
const sortedKeys = Object.keys(obj).sort(); const sortedKeys = Object.keys(obj).sort();
const sortedObj: Record<string, unknown> = {}; const sortedObj: Record<string, JsonValue> = {};
for (const key of sortedKeys) { for (const key of sortedKeys) {
sortedObj[key] = obj[key]; sortedObj[key] = obj[key];
} }
@@ -224,14 +224,14 @@ const extractEnv = (
const env = new Map<string, string>(); const env = new Map<string, string>();
if (structuredAttrs) { if (structuredAttrs) {
const jsonAttrs: Record<string, unknown> = {}; const jsonAttrs: Record<string, JsonValue> = {};
for (const [key, value] of attrs) { for (const [key, value] of attrs) {
if (!structuredAttrsExcludedKeys.has(key)) { if (!structuredAttrsExcludedKeys.has(key)) {
const forcedValue = force(value as NixValue); const forcedValue = force(value);
if (ignoreNulls && forcedValue === null) { if (ignoreNulls && forcedValue === null) {
continue; continue;
} }
jsonAttrs[key] = nixValueToJson(value as NixValue, true, outContext, true); jsonAttrs[key] = nixValueToJson(value, true, outContext, true);
} }
if (key === "allowedReferences") { if (key === "allowedReferences") {

View File

@@ -27,7 +27,7 @@ export const deepSeq =
recurse(val); recurse(val);
} }
} else if (isAttrs(forced)) { } else if (isAttrs(forced)) {
for (const [_, val] of Object.entries(forced)) { for (const [_, val] of forced.entries()) {
recurse(val); recurse(val);
} }
} }

View File

@@ -1,5 +1,5 @@
import { createThunk, force } from "../thunk"; import { createThunk, force } from "../thunk";
import type { NixAttrs, NixValue } from "../types"; import type { NixAttrs, NixFunction, NixValue } from "../types";
import * as arithmetic from "./arithmetic"; import * as arithmetic from "./arithmetic";
import * as attrs from "./attrs"; import * as attrs from "./attrs";
import * as conversion from "./conversion"; import * as conversion from "./conversion";
@@ -24,31 +24,31 @@ export interface PrimopMetadata {
} }
export const mkPrimop = ( export const mkPrimop = (
func: (...args: NixValue[]) => NixValue, func: NixFunction,
name: string, name: string,
arity: number, arity: number,
applied: number = 0, applied: number = 0,
): ((...args: NixValue[]) => NixValue) => { ): ((...args: NixValue[]) => NixValue) => {
(func as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = { func[PRIMOP_METADATA] = {
name, name,
arity, arity,
applied, applied,
} satisfies PrimopMetadata; } satisfies PrimopMetadata;
if (applied < arity - 1) { if (applied < arity - 1) {
const wrappedFunc = ((...args: NixValue[]) => { const wrappedFunc: NixFunction = ((arg: NixValue) => {
const result = func(...args); const result = func(arg);
if (typeof result === "function") { if (typeof result === "function") {
return mkPrimop(result, name, arity, applied + args.length); return mkPrimop(result, name, arity, applied + 1);
} }
return result; return result;
}) as (...args: NixValue[]) => NixValue; });
(wrappedFunc as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = { wrappedFunc[PRIMOP_METADATA] = {
name, name,
arity, arity,
applied, applied,
} satisfies PrimopMetadata; };
return wrappedFunc; return wrappedFunc;
} }
@@ -57,8 +57,8 @@ export const mkPrimop = (
}; };
export const isPrimop = ( export const isPrimop = (
value: unknown, value: NixValue,
): value is ((...args: never[]) => unknown) & { [PRIMOP_METADATA]: PrimopMetadata } => { ): value is NixFunction & { [PRIMOP_METADATA]: PrimopMetadata } => {
return ( return (
typeof value === "function" && typeof value === "function" &&
PRIMOP_METADATA in value && PRIMOP_METADATA in value &&
@@ -67,7 +67,7 @@ export const isPrimop = (
); );
}; };
export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined => { export const getPrimopMetadata = (func: NixValue): PrimopMetadata | undefined => {
if (isPrimop(func)) { if (isPrimop(func)) {
return func[PRIMOP_METADATA]; return func[PRIMOP_METADATA];
} }

View File

@@ -318,10 +318,12 @@ export const splitVersion = (s: NixValue): NixValue => {
return components; return components;
}; };
export const traceVerbose = (_e1: NixValue, e2: NixValue): NixStrictValue => { export const traceVerbose =
// TODO: implement traceVerbose (_e1: NixValue) =>
return force(e2); (e2: NixValue): NixStrictValue => {
}; // TODO: implement traceVerbose
return force(e2);
};
export const tryEval = (e: NixValue): NixAttrs => { export const tryEval = (e: NixValue): NixAttrs => {
try { try {

View File

@@ -39,36 +39,32 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
return "<LAMBDA>"; return "<LAMBDA>";
} }
if (typeof value === "object") { if (IS_CYCLE in value) {
if (IS_CYCLE in value) { return "«repeated»";
return "«repeated»";
}
if (seen.has(value)) {
return "«repeated»";
}
seen.add(value);
if (isNixPath(value)) {
return value.value;
}
if (isStringWithContext(value)) {
return printString(value.value);
}
if (Array.isArray(value)) {
const items = value.map((v) => printValue(v, seen)).join(" ");
return `[ ${items} ]`;
}
const entries = Object.entries(value)
.map(([k, v]) => `${printSymbol(k)} = ${printValue(v, seen)};`)
.join(" ");
return `{${entries ? ` ${entries} ` : " "}}`;
} }
throw new Error("unreachable"); if (seen.has(value)) {
return "«repeated»";
}
seen.add(value);
if (isNixPath(value)) {
return value.value;
}
if (isStringWithContext(value)) {
return printString(value.value);
}
if (Array.isArray(value)) {
const items = value.map((v) => printValue(v, seen)).join(" ");
return `[ ${items} ]`;
}
const entries = [...value.entries()]
.map(([k, v]) => `${printSymbol(k)} = ${printValue(v, seen)};`)
.join(" ");
return `{${entries ? ` ${entries} ` : " "}}`;
}; };
const printString = (s: string): string => { const printString = (s: string): string => {

View File

@@ -1,3 +1,4 @@
import { PRIMOP_METADATA, type PrimopMetadata } from "./builtins";
import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context"; import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
import { type CYCLE_MARKER, force, type NixThunk } from "./thunk"; import { type CYCLE_MARKER, force, type NixThunk } from "./thunk";
import { forceAttrs, forceStringNoCtx } from "./type-assert"; import { forceAttrs, forceStringNoCtx } from "./type-assert";
@@ -28,7 +29,10 @@ export type NixNull = null;
export const ATTR_POSITIONS = Symbol("attrPositions"); export const ATTR_POSITIONS = Symbol("attrPositions");
export type NixList = NixValue[]; export type NixList = NixValue[];
export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, number> }; export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, number> };
export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs }; export type NixFunction = ((arg: NixValue) => NixValue) & {
args?: NixArgs;
[PRIMOP_METADATA]?: PrimopMetadata;
};
export class NixArgs { export class NixArgs {
required: string[]; required: string[];
optional: string[]; optional: string[];

View File

@@ -1,5 +1,6 @@
import type { NixRuntime } from ".."; import type { NixRuntime } from "..";
import type { builtins } from "../builtins"; import type { builtins } from "../builtins";
import { JsonValue } from "../builtins/conversion";
import type { FetchGitResult, FetchTarballResult, FetchUrlResult } from "../builtins/io"; import type { FetchGitResult, FetchTarballResult, FetchUrlResult } from "../builtins/io";
import type { import type {
assert, assert,
@@ -15,7 +16,7 @@ import type {
import type { op } from "../operators"; import type { op } from "../operators";
import type { createThunk, force } from "../thunk"; import type { createThunk, force } from "../thunk";
import type { forceBool } from "../type-assert"; import type { forceBool } from "../type-assert";
import type { mkAttrs, mkFunction, NixAttrs } from "../types"; import type { mkAttrs, mkFunction, NixAttrs, NixStrictValue } from "../types";
declare global { declare global {
var Nix: NixRuntime; var Nix: NixRuntime;
@@ -92,8 +93,8 @@ declare global {
function op_match(regex: string, text: string): (string | null)[] | null; function op_match(regex: string, text: string): (string | null)[] | null;
function op_split(regex: string, text: string): (string | (string | null)[])[]; function op_split(regex: string, text: string): (string | (string | null)[])[];
function op_from_json(json: string): unknown; function op_from_json(json: string): NixStrictValue;
function op_from_toml(toml: string): unknown; function op_from_toml(toml: string): NixStrictValue;
function op_to_xml(e: NixValue): [string, string[]]; function op_to_xml(e: NixValue): [string, string[]];
function op_finalize_derivation( function op_finalize_derivation(

View File

@@ -120,11 +120,7 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
rt: tokio::runtime::Runtime, rt: tokio::runtime::Runtime,
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
wait_for_inspector: bool, wait_for_inspector: bool,
is_thunk_symbol: v8::Global<v8::Symbol>, symbols: GlobalSymbols,
primop_metadata_symbol: v8::Global<v8::Symbol>,
has_context_symbol: v8::Global<v8::Symbol>,
is_path_symbol: v8::Global<v8::Symbol>,
is_cycle_symbol: v8::Global<v8::Symbol>,
_marker: PhantomData<Ctx>, _marker: PhantomData<Ctx>,
} }
@@ -166,13 +162,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
js_runtime.op_state().borrow_mut().put(RegexCache::new()); js_runtime.op_state().borrow_mut().put(RegexCache::new());
js_runtime.op_state().borrow_mut().put(DrvHashCache::new()); js_runtime.op_state().borrow_mut().put(DrvHashCache::new());
let ( let symbols = {
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
) = {
deno_core::scope!(scope, &mut js_runtime); deno_core::scope!(scope, &mut js_runtime);
Self::get_symbols(scope)? Self::get_symbols(scope)?
}; };
@@ -186,11 +176,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
.expect("failed to build tokio runtime"), .expect("failed to build tokio runtime"),
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
wait_for_inspector: inspector_options.wait, wait_for_inspector: inspector_options.wait,
is_thunk_symbol, symbols,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
_marker: PhantomData, _marker: PhantomData,
}) })
} }
@@ -236,34 +222,12 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
// Retrieve scope from JsRuntime // Retrieve scope from JsRuntime
deno_core::scope!(scope, self.js_runtime); deno_core::scope!(scope, self.js_runtime);
let local_value = v8::Local::new(scope, &global_value); let local_value = v8::Local::new(scope, &global_value);
let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_symbol); let symbols = &self.symbols.local(scope);
let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol);
let has_context_symbol = v8::Local::new(scope, &self.has_context_symbol);
let is_path_symbol = v8::Local::new(scope, &self.is_path_symbol);
let is_cycle_symbol = v8::Local::new(scope, &self.is_cycle_symbol);
Ok(to_value( Ok(to_value(local_value, scope, symbols))
local_value,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
))
} }
/// get (IS_THUNK, PRIMOP_METADATA, HAS_CONTEXT, IS_PATH, IS_CYCLE) fn get_symbols(scope: &ScopeRef) -> Result<GlobalSymbols> {
#[allow(clippy::type_complexity)]
fn get_symbols(
scope: &ScopeRef,
) -> Result<(
v8::Global<v8::Symbol>,
v8::Global<v8::Symbol>,
v8::Global<v8::Symbol>,
v8::Global<v8::Symbol>,
v8::Global<v8::Symbol>,
)> {
let global = scope.get_current_context().global(scope); let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix") let nix_key = v8::String::new(scope, "Nix")
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?; .ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
@@ -295,18 +259,48 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
let is_path = get_symbol("IS_PATH")?; let is_path = get_symbol("IS_PATH")?;
let is_cycle = get_symbol("IS_CYCLE")?; let is_cycle = get_symbol("IS_CYCLE")?;
Ok((is_thunk, primop_metadata, has_context, is_path, is_cycle)) Ok(GlobalSymbols {
is_thunk,
primop_metadata,
has_context,
is_path,
is_cycle,
})
} }
} }
struct GlobalSymbols {
is_thunk: v8::Global<v8::Symbol>,
primop_metadata: v8::Global<v8::Symbol>,
has_context: v8::Global<v8::Symbol>,
is_path: v8::Global<v8::Symbol>,
is_cycle: v8::Global<v8::Symbol>,
}
impl GlobalSymbols {
fn local<'a>(&self, scope: &ScopeRef<'a, '_>) -> LocalSymbols<'a> {
LocalSymbols {
is_thunk: v8::Local::new(scope, &self.is_thunk),
primop_metadata: v8::Local::new(scope, &self.primop_metadata),
has_context: v8::Local::new(scope, &self.has_context),
is_path: v8::Local::new(scope, &self.is_path),
is_cycle: v8::Local::new(scope, &self.is_cycle),
}
}
}
struct LocalSymbols<'a> {
is_thunk: v8::Local<'a, v8::Symbol>,
primop_metadata: v8::Local<'a, v8::Symbol>,
has_context: v8::Local<'a, v8::Symbol>,
is_path: v8::Local<'a, v8::Symbol>,
is_cycle: v8::Local<'a, v8::Symbol>,
}
fn to_value<'a>( fn to_value<'a>(
val: LocalValue<'a>, val: LocalValue<'a>,
scope: &ScopeRef<'a, '_>, scope: &ScopeRef<'a, '_>,
is_thunk_symbol: LocalSymbol<'a>, symbols: &LocalSymbols<'a>,
primop_metadata_symbol: LocalSymbol<'a>,
has_context_symbol: LocalSymbol<'a>,
is_path_symbol: LocalSymbol<'a>,
is_cycle_symbol: LocalSymbol<'a>,
) -> Value { ) -> Value {
match () { match () {
_ if val.is_big_int() => { _ if val.is_big_int() => {
@@ -336,21 +330,13 @@ fn to_value<'a>(
let list = (0..len) let list = (0..len)
.map(|i| { .map(|i| {
let val = val.get_index(scope, i).expect("infallible index operation"); let val = val.get_index(scope, i).expect("infallible index operation");
to_value( to_value(val, scope, symbols)
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
)
}) })
.collect(); .collect();
Value::List(List::new(list)) Value::List(List::new(list))
} }
_ if val.is_function() => { _ if val.is_function() => {
if let Some(primop) = to_primop(val, scope, primop_metadata_symbol) { if let Some(primop) = to_primop(val, scope, symbols.primop_metadata) {
primop primop
} else { } else {
Value::Func Value::Func
@@ -369,34 +355,26 @@ fn to_value<'a>(
let val = array let val = array
.get_index(scope, i * 2 + 1) .get_index(scope, i * 2 + 1)
.expect("infallible index operation"); .expect("infallible index operation");
let val = to_value( let val = to_value(val, scope, symbols);
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
);
(Symbol::new(Cow::Owned(key)), val) (Symbol::new(Cow::Owned(key)), val)
}) })
.collect(); .collect();
Value::AttrSet(AttrSet::new(attrs)) Value::AttrSet(AttrSet::new(attrs))
} }
_ if val.is_object() => { _ if val.is_object() => {
if is_thunk(val, scope, is_thunk_symbol) { if is_thunk(val, scope, symbols.is_thunk) {
return Value::Thunk; return Value::Thunk;
} }
if is_cycle(val, scope, is_cycle_symbol) { if is_cycle(val, scope, symbols.is_cycle) {
return Value::Repeated; return Value::Repeated;
} }
if let Some(path_val) = extract_path(val, scope, is_path_symbol) { if let Some(path_val) = extract_path(val, scope, symbols.is_path) {
return Value::Path(path_val); return Value::Path(path_val);
} }
if let Some(string_val) = extract_string_with_context(val, scope, has_context_symbol) { if let Some(string_val) = extract_string_with_context(val, scope, symbols.has_context) {
return Value::String(string_val); return Value::String(string_val);
} }
@@ -412,18 +390,7 @@ fn to_value<'a>(
.expect("infallible index operation"); .expect("infallible index operation");
let val = val.get(scope, key).expect("infallible operation"); let val = val.get(scope, key).expect("infallible operation");
let key = key.to_rust_string_lossy(scope); let key = key.to_rust_string_lossy(scope);
( (Symbol::from(key), to_value(val, scope, symbols))
Symbol::from(key),
to_value(
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
),
)
}) })
.collect(); .collect();
Value::AttrSet(AttrSet::new(attrs)) Value::AttrSet(AttrSet::new(attrs))

View File

@@ -1,7 +1,7 @@
[files] [files]
extend-exclude = [ extend-exclude = [
"nix-js/tests/basic/regex.rs", "nix-js/tests/tests/regex.rs",
"nix-js/tests/lang", "nix-js/tests/tests/lang",
] ]
[default.extend-words] [default.extend-words]