Compare commits
2 Commits
f50324f7c9
...
86953dd9d3
| Author | SHA1 | Date | |
|---|---|---|---|
|
86953dd9d3
|
|||
|
d1f87260a6
|
2
Justfile
2
Justfile
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@evalr expr:
|
@evalr expr:
|
||||||
RUST_LOG=info cargo run --bin eval --release -- '{{expr}}'
|
RUST_LOG=silent cargo run --bin eval --release -- '{{expr}}'
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ name = "builtins"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "scc_optimization"
|
name = "thunk_scope"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export const hasAttr =
|
|||||||
(set: NixValue): boolean =>
|
(set: NixValue): boolean =>
|
||||||
Object.hasOwn(forceAttrs(set), forceStringValue(s));
|
Object.hasOwn(forceAttrs(set), forceStringValue(s));
|
||||||
|
|
||||||
let counter = 0;
|
|
||||||
export const mapAttrs =
|
export const mapAttrs =
|
||||||
(f: NixValue) =>
|
(f: NixValue) =>
|
||||||
(attrs: NixValue): NixAttrs => {
|
(attrs: NixValue): NixAttrs => {
|
||||||
@@ -39,8 +38,7 @@ export const mapAttrs =
|
|||||||
const forcedF = forceFunction(f);
|
const forcedF = forceFunction(f);
|
||||||
const newAttrs: NixAttrs = {};
|
const newAttrs: NixAttrs = {};
|
||||||
for (const key in forcedAttrs) {
|
for (const key in forcedAttrs) {
|
||||||
newAttrs[key] = createThunk(() => forceFunction(forcedF(key))(forcedAttrs[key]), `created by mapAttrs (${counter})`);
|
newAttrs[key] = createThunk(() => forceFunction(forcedF(key))(forcedAttrs[key]), "created by mapAttrs");
|
||||||
counter += 1;
|
|
||||||
}
|
}
|
||||||
return newAttrs;
|
return newAttrs;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -351,6 +351,18 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const specialAttrs = new Set([
|
||||||
|
"name",
|
||||||
|
"builder",
|
||||||
|
"system",
|
||||||
|
"args",
|
||||||
|
"outputs",
|
||||||
|
"__structuredAttrs",
|
||||||
|
"__ignoreNulls",
|
||||||
|
"__contentAddressed",
|
||||||
|
"impure",
|
||||||
|
]);
|
||||||
|
|
||||||
export const derivation = (args: NixValue): NixAttrs => {
|
export const derivation = (args: NixValue): NixAttrs => {
|
||||||
const attrs = forceAttrs(args);
|
const attrs = forceAttrs(args);
|
||||||
const strict = derivationStrict(args);
|
const strict = derivationStrict(args);
|
||||||
@@ -364,18 +376,6 @@ export const derivation = (args: NixValue): NixAttrs => {
|
|||||||
const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false;
|
const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false;
|
||||||
const drvArgs = extractArgs(attrs, collectedContext);
|
const drvArgs = extractArgs(attrs, collectedContext);
|
||||||
|
|
||||||
const specialAttrs = new Set([
|
|
||||||
"name",
|
|
||||||
"builder",
|
|
||||||
"system",
|
|
||||||
"args",
|
|
||||||
"outputs",
|
|
||||||
"__structuredAttrs",
|
|
||||||
"__ignoreNulls",
|
|
||||||
"__contentAddressed",
|
|
||||||
"impure",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const baseAttrs: NixAttrs = {
|
const baseAttrs: NixAttrs = {
|
||||||
type: "derivation",
|
type: "derivation",
|
||||||
drvPath: strict.drvPath,
|
drvPath: strict.drvPath,
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import * as misc from "./misc";
|
|||||||
import * as derivation from "./derivation";
|
import * as derivation from "./derivation";
|
||||||
|
|
||||||
import type { NixValue } from "../types";
|
import type { NixValue } from "../types";
|
||||||
import { createThunk, force } from "../thunk";
|
import { createThunk, force, isThunk } from "../thunk";
|
||||||
|
import { getTos } from "../helpers";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symbol used to mark functions as primops (primitive operations)
|
* Symbol used to mark functions as primops (primitive operations)
|
||||||
@@ -263,4 +264,9 @@ export const builtins: any = {
|
|||||||
nixPath: [],
|
nixPath: [],
|
||||||
nixVersion: "2.31.2",
|
nixVersion: "2.31.2",
|
||||||
storeDir: "INVALID_PATH",
|
storeDir: "INVALID_PATH",
|
||||||
|
|
||||||
|
__traceCaller: (e: NixValue) => {
|
||||||
|
console.log(`traceCaller: ${getTos()}`)
|
||||||
|
return e
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { getPathValue } from "../path";
|
|||||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
import type { NixStringContext, StringWithContext } from "../string-context";
|
||||||
import { mkStringWithContext } from "../string-context";
|
import { mkStringWithContext } from "../string-context";
|
||||||
import { isPath } from "./type-check";
|
import { isPath } from "./type-check";
|
||||||
import { getCorepkg } from "../corepkgs";
|
|
||||||
|
const importCache = new Map<string, NixValue>();
|
||||||
|
|
||||||
export const importFunc = (path: NixValue): NixValue => {
|
export const importFunc = (path: NixValue): NixValue => {
|
||||||
const context: NixStringContext = new Set();
|
const context: NixStringContext = new Set();
|
||||||
@@ -30,9 +31,17 @@ Dependency tracking for imported derivations may be incomplete.`,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cached = importCache.get(pathStr);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
// Call Rust op - returns JS code string
|
// Call Rust op - returns JS code string
|
||||||
const code = Deno.core.ops.op_import(pathStr);
|
const code = Deno.core.ops.op_import(pathStr);
|
||||||
return Function(`return (${code})`)();
|
const result = Function(`return (${code})`)();
|
||||||
|
|
||||||
|
importCache.set(pathStr, result);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const scopedImport =
|
export const scopedImport =
|
||||||
@@ -452,13 +461,8 @@ export const findFile =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lookupPathStr.startsWith("nix/")) {
|
if (lookupPathStr.startsWith("nix/")) {
|
||||||
const corepkgName = lookupPathStr.substring(4);
|
// FIXME: special path type
|
||||||
const corepkgContent = getCorepkg(corepkgName);
|
return { [IS_PATH]: true, value: `<${lookupPathStr}>` };
|
||||||
|
|
||||||
if (corepkgContent !== undefined) {
|
|
||||||
// FIXME: special path type
|
|
||||||
return { [IS_PATH]: true, value: `<nix/${corepkgName}>` };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CatchableError(`file '${lookupPathStr}' was not found in the Nix search path`);
|
throw new CatchableError(`file '${lookupPathStr}' was not found in the Nix search path`);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Miscellaneous builtin functions
|
* Miscellaneous builtin functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createThunk, force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { CatchableError } from "../types";
|
import { CatchableError } from "../types";
|
||||||
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
||||||
import { forceList, forceAttrs, forceFunction, forceStringValue, forceString, forceStringNoCtx } from "../type-assert";
|
import { forceList, forceAttrs, forceFunction, forceStringValue, forceString, forceStringNoCtx } from "../type-assert";
|
||||||
@@ -20,7 +20,8 @@ import {
|
|||||||
export const addErrorContext =
|
export const addErrorContext =
|
||||||
(e1: NixValue) =>
|
(e1: NixValue) =>
|
||||||
(e2: NixValue): NixValue => {
|
(e2: NixValue): NixValue => {
|
||||||
console.log("[WARNING]: addErrorContext not implemented");
|
// FIXME:
|
||||||
|
// console.log("[WARNING]: addErrorContext not implemented");
|
||||||
return e2;
|
return e2;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
export const FETCHURL_NIX = `{
|
|
||||||
system ? "", # obsolete
|
|
||||||
url,
|
|
||||||
hash ? "", # an SRI hash
|
|
||||||
|
|
||||||
# Legacy hash specification
|
|
||||||
md5 ? "",
|
|
||||||
sha1 ? "",
|
|
||||||
sha256 ? "",
|
|
||||||
sha512 ? "",
|
|
||||||
outputHash ?
|
|
||||||
if hash != "" then
|
|
||||||
hash
|
|
||||||
else if sha512 != "" then
|
|
||||||
sha512
|
|
||||||
else if sha1 != "" then
|
|
||||||
sha1
|
|
||||||
else if md5 != "" then
|
|
||||||
md5
|
|
||||||
else
|
|
||||||
sha256,
|
|
||||||
outputHashAlgo ?
|
|
||||||
if hash != "" then
|
|
||||||
""
|
|
||||||
else if sha512 != "" then
|
|
||||||
"sha512"
|
|
||||||
else if sha1 != "" then
|
|
||||||
"sha1"
|
|
||||||
else if md5 != "" then
|
|
||||||
"md5"
|
|
||||||
else
|
|
||||||
"sha256",
|
|
||||||
|
|
||||||
executable ? false,
|
|
||||||
unpack ? false,
|
|
||||||
name ? baseNameOf (toString url),
|
|
||||||
impure ? false,
|
|
||||||
}:
|
|
||||||
|
|
||||||
derivation (
|
|
||||||
{
|
|
||||||
builder = "builtin:fetchurl";
|
|
||||||
|
|
||||||
# New-style output content requirements.
|
|
||||||
outputHashMode = if unpack || executable then "recursive" else "flat";
|
|
||||||
|
|
||||||
inherit
|
|
||||||
name
|
|
||||||
url
|
|
||||||
executable
|
|
||||||
unpack
|
|
||||||
;
|
|
||||||
|
|
||||||
system = "builtin";
|
|
||||||
|
|
||||||
# No need to double the amount of network traffic
|
|
||||||
preferLocalBuild = true;
|
|
||||||
|
|
||||||
# This attribute does nothing; it's here to avoid changing evaluation results.
|
|
||||||
impureEnvVars = [
|
|
||||||
"http_proxy"
|
|
||||||
"https_proxy"
|
|
||||||
"ftp_proxy"
|
|
||||||
"all_proxy"
|
|
||||||
"no_proxy"
|
|
||||||
];
|
|
||||||
|
|
||||||
# To make "nix-prefetch-url" work.
|
|
||||||
urls = [ url ];
|
|
||||||
}
|
|
||||||
// (if impure then { __impure = true; } else { inherit outputHashAlgo outputHash; })
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { FETCHURL_NIX } from "./fetchurl.nix";
|
|
||||||
|
|
||||||
export const COREPKGS: Record<string, string> = {
|
|
||||||
"fetchurl.nix": FETCHURL_NIX,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCorepkg = (name: string): string | undefined => {
|
|
||||||
return COREPKGS[name];
|
|
||||||
};
|
|
||||||
@@ -19,12 +19,10 @@ interface StackFrame {
|
|||||||
const callStack: StackFrame[] = [];
|
const callStack: StackFrame[] = [];
|
||||||
const MAX_STACK_DEPTH = 1000;
|
const MAX_STACK_DEPTH = 1000;
|
||||||
|
|
||||||
export const STACK_TRACE = { enabled: false };
|
|
||||||
|
|
||||||
function enrichError(error: unknown): Error {
|
function enrichError(error: unknown): Error {
|
||||||
const err = error instanceof Error ? error : new Error(String(error));
|
const err = error instanceof Error ? error : new Error(String(error));
|
||||||
|
|
||||||
if (!STACK_TRACE.enabled || callStack.length === 0) {
|
if (callStack.length === 0) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,13 +36,17 @@ function enrichError(error: unknown): Error {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTos = (): string => {
|
||||||
|
const tos = callStack[callStack.length - 2];
|
||||||
|
const { file, line, column } = Deno.core.ops.op_decode_span(tos.span);
|
||||||
|
return `${tos.message} at ${file}:${line}:${column}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push an error context onto the stack
|
* Push an error context onto the stack
|
||||||
* Used for tracking evaluation context (e.g., "while evaluating the condition")
|
* Used for tracking evaluation context (e.g., "while evaluating the condition")
|
||||||
*/
|
*/
|
||||||
export const pushContext = (message: string, span: string): void => {
|
export const pushContext = (message: string, span: string): void => {
|
||||||
if (!STACK_TRACE.enabled) return;
|
|
||||||
|
|
||||||
if (callStack.length >= MAX_STACK_DEPTH) {
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
callStack.shift();
|
callStack.shift();
|
||||||
}
|
}
|
||||||
@@ -55,7 +57,6 @@ export const pushContext = (message: string, span: string): void => {
|
|||||||
* Pop an error context from the stack
|
* Pop an error context from the stack
|
||||||
*/
|
*/
|
||||||
export const popContext = (): void => {
|
export const popContext = (): void => {
|
||||||
if (!STACK_TRACE.enabled) return;
|
|
||||||
callStack.pop();
|
callStack.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,10 +65,6 @@ export const popContext = (): void => {
|
|||||||
* Automatically pushes context before execution and pops after
|
* Automatically pushes context before execution and pops after
|
||||||
*/
|
*/
|
||||||
export const withContext = <T>(message: string, span: string, fn: () => T): T => {
|
export const withContext = <T>(message: string, span: string, fn: () => T): T => {
|
||||||
if (!STACK_TRACE.enabled) {
|
|
||||||
return fn();
|
|
||||||
}
|
|
||||||
|
|
||||||
pushContext(message, span);
|
pushContext(message, span);
|
||||||
try {
|
try {
|
||||||
return fn();
|
return fn();
|
||||||
@@ -183,7 +180,7 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => {
|
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => {
|
||||||
if (STACK_TRACE.enabled && span) {
|
if (span) {
|
||||||
const pathStrings = attrpath.map((a) => forceStringValue(a));
|
const pathStrings = attrpath.map((a) => forceStringValue(a));
|
||||||
const path = pathStrings.join(".");
|
const path = pathStrings.join(".");
|
||||||
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
|
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
|
||||||
@@ -229,7 +226,7 @@ export const selectWithDefault = (
|
|||||||
default_val: NixValue,
|
default_val: NixValue,
|
||||||
span?: string,
|
span?: string,
|
||||||
): NixValue => {
|
): NixValue => {
|
||||||
if (STACK_TRACE.enabled && span) {
|
if (span) {
|
||||||
const pathStrings = attrpath.map((a) => forceStringValue(a));
|
const pathStrings = attrpath.map((a) => forceStringValue(a));
|
||||||
const path = pathStrings.join(".");
|
const path = pathStrings.join(".");
|
||||||
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
|
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
|
||||||
@@ -337,7 +334,7 @@ export const validateParams = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
|
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
|
||||||
if (STACK_TRACE.enabled && span) {
|
if (span) {
|
||||||
if (callStack.length >= MAX_STACK_DEPTH) {
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
callStack.shift();
|
callStack.shift();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
concatStringsWithContext,
|
concatStringsWithContext,
|
||||||
call,
|
call,
|
||||||
assert,
|
assert,
|
||||||
STACK_TRACE,
|
|
||||||
pushContext,
|
pushContext,
|
||||||
popContext,
|
popContext,
|
||||||
withContext,
|
withContext,
|
||||||
@@ -41,7 +40,6 @@ export const Nix = {
|
|||||||
HAS_CONTEXT,
|
HAS_CONTEXT,
|
||||||
IS_PATH,
|
IS_PATH,
|
||||||
DEBUG_THUNKS,
|
DEBUG_THUNKS,
|
||||||
STACK_TRACE,
|
|
||||||
|
|
||||||
assert,
|
assert,
|
||||||
call,
|
call,
|
||||||
|
|||||||
@@ -224,13 +224,15 @@ export const op = {
|
|||||||
const attrsA = av as NixAttrs;
|
const attrsA = av as NixAttrs;
|
||||||
const attrsB = bv as NixAttrs;
|
const attrsB = bv as NixAttrs;
|
||||||
|
|
||||||
// If both denote a derivation (type = "derivation"), compare their outPaths
|
// Derivation comparison: compare outPaths only
|
||||||
const isDerivationA = "type" in attrsA && force(attrsA.type) === "derivation";
|
// Safe to force 'type' because it's always a string literal, never a computed value
|
||||||
const isDerivationB = "type" in attrsB && force(attrsB.type) === "derivation";
|
if ("type" in attrsA && "type" in attrsB) {
|
||||||
|
const typeValA = force(attrsA.type);
|
||||||
if (isDerivationA && isDerivationB) {
|
const typeValB = force(attrsB.type);
|
||||||
if ("outPath" in attrsA && "outPath" in attrsB) {
|
if (typeValA === "derivation" && typeValB === "derivation") {
|
||||||
return op.eq(attrsA.outPath, attrsB.outPath);
|
if ("outPath" in attrsA && "outPath" in attrsB) {
|
||||||
|
return op.eq(attrsA.outPath, attrsB.outPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import type { NixValue, NixThunkInterface, NixStrictValue } from "./types";
|
|||||||
export const IS_THUNK = Symbol("is_thunk");
|
export const IS_THUNK = Symbol("is_thunk");
|
||||||
|
|
||||||
const forceStack: NixThunk[] = [];
|
const forceStack: NixThunk[] = [];
|
||||||
|
const MAX_FORCE_DEPTH = 1000;
|
||||||
|
|
||||||
export const DEBUG_THUNKS = { enabled: false };
|
export const DEBUG_THUNKS = { enabled: true };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NixThunk class - represents a lazy, unevaluated expression
|
* NixThunk class - represents a lazy, unevaluated expression
|
||||||
@@ -97,13 +98,28 @@ export const force = (value: NixValue): NixStrictValue => {
|
|||||||
|
|
||||||
if (DEBUG_THUNKS.enabled) {
|
if (DEBUG_THUNKS.enabled) {
|
||||||
forceStack.push(thunk);
|
forceStack.push(thunk);
|
||||||
|
if (forceStack.length > MAX_FORCE_DEPTH) {
|
||||||
|
let msg = `force depth exceeded ${MAX_FORCE_DEPTH} at ${thunk}\n`;
|
||||||
|
msg += "Force chain (most recent first):\n";
|
||||||
|
for (let i = forceStack.length - 1; i >= Math.max(0, forceStack.length - 20); i--) {
|
||||||
|
const t = forceStack[i];
|
||||||
|
msg += ` ${i + 1}. ${t}`;
|
||||||
|
msg += "\n";
|
||||||
|
}
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = force(func());
|
const result = force(func());
|
||||||
thunk.result = result;
|
thunk.result = result;
|
||||||
return result;
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
thunk.func = func;
|
||||||
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
forceStack.pop();
|
if (DEBUG_THUNKS.enabled) {
|
||||||
|
forceStack.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
|||||||
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
||||||
debug_flags.push("Nix.DEBUG_THUNKS.enabled=true");
|
debug_flags.push("Nix.DEBUG_THUNKS.enabled=true");
|
||||||
}
|
}
|
||||||
if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
|
||||||
debug_flags.push("Nix.STACK_TRACE.enabled=true");
|
|
||||||
}
|
|
||||||
let debug_prefix = if debug_flags.is_empty() {
|
let debug_prefix = if debug_flags.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
@@ -97,17 +94,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
let cond_code = ctx.get_ir(cond).compile(ctx);
|
let cond_code = ctx.get_ir(cond).compile(ctx);
|
||||||
let consq = ctx.get_ir(consq).compile(ctx);
|
let consq = ctx.get_ir(consq).compile(ctx);
|
||||||
let alter = ctx.get_ir(alter).compile(ctx);
|
let alter = ctx.get_ir(alter).compile(ctx);
|
||||||
|
let cond_span = encode_span(ctx.get_ir(cond).span(), ctx);
|
||||||
// Only add context tracking if STACK_TRACE is enabled
|
format!(
|
||||||
if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
"(Nix.withContext(\"while evaluating a branch condition\",{},()=>Nix.forceBool({})))?({}):({})",
|
||||||
let cond_span = encode_span(ctx.get_ir(cond).span(), ctx);
|
cond_span, cond_code, consq, alter
|
||||||
format!(
|
)
|
||||||
"(Nix.withContext(\"while evaluating a branch condition\",{},()=>Nix.forceBool({})))?({}):({})",
|
|
||||||
cond_span, cond_code, consq, alter
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!("Nix.forceBool({cond_code})?({consq}):({alter})")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ir::BinOp(x) => x.compile(ctx),
|
Ir::BinOp(x) => x.compile(ctx),
|
||||||
Ir::UnOp(x) => x.compile(ctx),
|
Ir::UnOp(x) => x.compile(ctx),
|
||||||
@@ -116,23 +107,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
Ir::List(x) => x.compile(ctx),
|
Ir::List(x) => x.compile(ctx),
|
||||||
Ir::Call(x) => x.compile(ctx),
|
Ir::Call(x) => x.compile(ctx),
|
||||||
Ir::Arg(x) => format!("arg{}", x.inner.0),
|
Ir::Arg(x) => format!("arg{}", x.inner.0),
|
||||||
Ir::Let(x) => x.compile(ctx),
|
Ir::TopLevel(x) => x.compile(ctx),
|
||||||
Ir::Select(x) => x.compile(ctx),
|
Ir::Select(x) => x.compile(ctx),
|
||||||
&Ir::Thunk(Thunk {
|
&Ir::Thunk(Thunk { inner: expr_id, .. }) => {
|
||||||
inner: expr_id,
|
|
||||||
span,
|
|
||||||
}) => {
|
|
||||||
let inner = ctx.get_ir(expr_id).compile(ctx);
|
|
||||||
format!(
|
|
||||||
"Nix.createThunk(()=>({}),\"expr{} {}:{}:{}\")",
|
|
||||||
inner,
|
|
||||||
expr_id.0,
|
|
||||||
ctx.get_current_source().get_name(),
|
|
||||||
usize::from(span.start()),
|
|
||||||
usize::from(span.end())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
&Ir::ExprRef(ExprRef { inner: expr_id, .. }) => {
|
|
||||||
format!("expr{}", expr_id.0)
|
format!("expr{}", expr_id.0)
|
||||||
}
|
}
|
||||||
Ir::Builtins(_) => "Nix.builtins".to_string(),
|
Ir::Builtins(_) => "Nix.builtins".to_string(),
|
||||||
@@ -149,27 +126,16 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
}) => {
|
}) => {
|
||||||
let assertion_code = ctx.get_ir(assertion).compile(ctx);
|
let assertion_code = ctx.get_ir(assertion).compile(ctx);
|
||||||
let expr = ctx.get_ir(expr).compile(ctx);
|
let expr = ctx.get_ir(expr).compile(ctx);
|
||||||
|
let assertion_span = encode_span(ctx.get_ir(assertion).span(), ctx);
|
||||||
// Only add context tracking if STACK_TRACE is enabled
|
let span = encode_span(span, ctx);
|
||||||
if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
format!(
|
||||||
let assertion_span = encode_span(ctx.get_ir(assertion).span(), ctx);
|
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{},{})",
|
||||||
let span = encode_span(span, ctx);
|
assertion_span,
|
||||||
format!(
|
assertion_code,
|
||||||
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",{},()=>({})),{},{},{})",
|
expr,
|
||||||
assertion_span,
|
assertion_raw.escape_quote(),
|
||||||
assertion_code,
|
span
|
||||||
expr,
|
)
|
||||||
assertion_raw.escape_quote(),
|
|
||||||
span
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"Nix.assert({},{},{})",
|
|
||||||
assertion_code,
|
|
||||||
expr,
|
|
||||||
assertion_raw.escape_quote()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ir::CurPos(cur_pos) => {
|
Ir::CurPos(cur_pos) => {
|
||||||
let span_str = encode_span(cur_pos.span, ctx);
|
let span_str = encode_span(cur_pos.span, ctx);
|
||||||
@@ -186,20 +152,12 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
|||||||
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
||||||
|
|
||||||
// Only add context tracking if STACK_TRACE is enabled
|
|
||||||
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
|
||||||
|
|
||||||
// Helper to wrap operation with context (only if enabled)
|
|
||||||
let with_ctx = |op_name: &str, op_call: String| {
|
let with_ctx = |op_name: &str, op_call: String| {
|
||||||
if stack_trace_enabled {
|
let span = encode_span(self.span, ctx);
|
||||||
let span = encode_span(self.span, ctx);
|
format!(
|
||||||
format!(
|
"Nix.withContext(\"while evaluating the {} operator\",{},()=>({}))",
|
||||||
"Nix.withContext(\"while evaluating the {} operator\",{},()=>({}))",
|
op_name, span, op_call
|
||||||
op_name, span, op_call
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
op_call
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
@@ -248,7 +206,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
|||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
|
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
|
||||||
let body = ctx.get_ir(self.body).compile(ctx);
|
let thunk_defs = compile_thunks(&self.thunks, ctx);
|
||||||
|
let body_code = ctx.get_ir(self.body).compile(ctx);
|
||||||
|
let body = if thunk_defs.is_empty() {
|
||||||
|
body_code
|
||||||
|
} else {
|
||||||
|
format!("{{{}return {}}}", thunk_defs, body_code)
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(Param {
|
if let Some(Param {
|
||||||
required,
|
required,
|
||||||
@@ -260,9 +224,17 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
|||||||
let required = format!("[{}]", required.join(","));
|
let required = format!("[{}]", required.join(","));
|
||||||
let mut optional = optional.iter().map(|&sym| ctx.get_sym(sym).escape_quote());
|
let mut optional = optional.iter().map(|&sym| ctx.get_sym(sym).escape_quote());
|
||||||
let optional = format!("[{}]", optional.join(","));
|
let optional = format!("[{}]", optional.join(","));
|
||||||
format!("Nix.mkFunction(arg{id}=>({body}),{required},{optional},{ellipsis})")
|
if thunk_defs.is_empty() {
|
||||||
|
format!("Nix.mkFunction(arg{id}=>({body}),{required},{optional},{ellipsis})")
|
||||||
|
} else {
|
||||||
|
format!("Nix.mkFunction(arg{id}=>{body},{required},{optional},{ellipsis})")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("arg{id}=>({body})")
|
if thunk_defs.is_empty() {
|
||||||
|
format!("arg{id}=>({body})")
|
||||||
|
} else {
|
||||||
|
format!("arg{id}=>{body}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,51 +248,38 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Call {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if a Thunk should be kept (not unwrapped) for non-recursive let bindings.
|
fn compile_thunks<Ctx: CodegenContext>(thunks: &[(ExprId, ExprId)], ctx: &Ctx) -> String {
|
||||||
/// Returns true for complex expressions that should remain lazy to preserve Nix semantics.
|
if thunks.is_empty() {
|
||||||
fn should_keep_thunk(ir: &Ir) -> bool {
|
return String::new();
|
||||||
match ir {
|
|
||||||
// Simple literals can be evaluated eagerly
|
|
||||||
Ir::Int(_) | Ir::Float(_) | Ir::Bool(_) | Ir::Null(_) | Ir::Str(_) => false,
|
|
||||||
// Builtin references are safe to evaluate eagerly
|
|
||||||
Ir::Builtin(_) | Ir::Builtins(_) => false,
|
|
||||||
Ir::ExprRef(_) => false,
|
|
||||||
// Everything else should remain lazy:
|
|
||||||
_ => true,
|
|
||||||
}
|
}
|
||||||
|
thunks
|
||||||
|
.iter()
|
||||||
|
.map(|&(slot, inner)| {
|
||||||
|
let inner_code = ctx.get_ir(inner).compile(ctx);
|
||||||
|
let inner_span = ctx.get_ir(inner).span();
|
||||||
|
format!(
|
||||||
|
"let expr{}=Nix.createThunk(()=>({}),\"expr{} {}:{}:{}\")",
|
||||||
|
slot.0,
|
||||||
|
inner_code,
|
||||||
|
slot.0,
|
||||||
|
ctx.get_current_source().get_name(),
|
||||||
|
usize::from(inner_span.start()),
|
||||||
|
usize::from(inner_span.end())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.join(";")
|
||||||
|
+ ";"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap_thunk(ir: &Ir, ctx: &impl CodegenContext) -> String {
|
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
|
||||||
if let Ir::Thunk(Thunk { inner, .. }) = ir {
|
|
||||||
let inner_ir = ctx.get_ir(*inner);
|
|
||||||
if should_keep_thunk(inner_ir) {
|
|
||||||
ir.compile(ctx)
|
|
||||||
} else {
|
|
||||||
inner_ir.compile(ctx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ir.compile(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Let {
|
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let info = &self.binding_sccs;
|
let thunk_defs = compile_thunks(&self.thunks, ctx);
|
||||||
let mut js_statements = Vec::new();
|
|
||||||
|
|
||||||
for (scc_exprs, is_recursive) in info.sccs.iter() {
|
|
||||||
for &expr in scc_exprs {
|
|
||||||
let value = if *is_recursive {
|
|
||||||
ctx.get_ir(expr).compile(ctx)
|
|
||||||
} else {
|
|
||||||
unwrap_thunk(ctx.get_ir(expr), ctx)
|
|
||||||
};
|
|
||||||
js_statements.push(format!("const expr{}={}", expr.0, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = ctx.get_ir(self.body).compile(ctx);
|
let body = ctx.get_ir(self.body).compile(ctx);
|
||||||
format!("(()=>{{{};return {}}})()", js_statements.join(";"), body)
|
if thunk_defs.is_empty() {
|
||||||
|
body
|
||||||
|
} else {
|
||||||
|
format!("(()=>{{{}return {}}})()", thunk_defs, body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,21 +310,16 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
|||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let mut attrs = Vec::new();
|
let mut attrs = Vec::new();
|
||||||
let mut attr_positions = Vec::new();
|
let mut attr_positions = Vec::new();
|
||||||
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
|
||||||
|
|
||||||
for (&sym, &(expr, attr_span)) in &self.stcs {
|
for (&sym, &(expr, attr_span)) in &self.stcs {
|
||||||
let key = ctx.get_sym(sym);
|
let key = ctx.get_sym(sym);
|
||||||
let value_code = ctx.get_ir(expr).compile(ctx);
|
let value_code = ctx.get_ir(expr).compile(ctx);
|
||||||
|
|
||||||
let value = if stack_trace_enabled {
|
let value_span = encode_span(ctx.get_ir(expr).span(), ctx);
|
||||||
let value_span = encode_span(ctx.get_ir(expr).span(), ctx);
|
let value = format!(
|
||||||
format!(
|
"Nix.withContext(\"while evaluating the attribute '{}'\",{},()=>({}))",
|
||||||
"Nix.withContext(\"while evaluating the attribute '{}'\",{},()=>({}))",
|
key, value_span, value_code
|
||||||
key, value_span, value_code
|
);
|
||||||
)
|
|
||||||
} else {
|
|
||||||
value_code
|
|
||||||
};
|
|
||||||
attrs.push(format!("{}:{}", key.escape_quote(), value));
|
attrs.push(format!("{}:{}", key.escape_quote(), value));
|
||||||
|
|
||||||
let attr_pos_str = encode_span(attr_span, ctx);
|
let attr_pos_str = encode_span(attr_span, ctx);
|
||||||
@@ -381,15 +335,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
|||||||
let val_expr = ctx.get_ir(*val);
|
let val_expr = ctx.get_ir(*val);
|
||||||
let val = val_expr.compile(ctx);
|
let val = val_expr.compile(ctx);
|
||||||
let span = val_expr.span();
|
let span = val_expr.span();
|
||||||
let val = if stack_trace_enabled {
|
let span = encode_span(span, ctx);
|
||||||
let span = encode_span(span, ctx);
|
let val = format!(
|
||||||
format!(
|
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
|
||||||
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
|
span, val
|
||||||
span, val
|
);
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val
|
|
||||||
};
|
|
||||||
let dyn_span_str = encode_span(*attr_span, ctx);
|
let dyn_span_str = encode_span(*attr_span, ctx);
|
||||||
(key, val, dyn_span_str)
|
(key, val, dyn_span_str)
|
||||||
})
|
})
|
||||||
@@ -416,23 +366,17 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
|||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
|
||||||
|
|
||||||
let list = self
|
let list = self
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, item)| {
|
.map(|(idx, item)| {
|
||||||
let item_code = ctx.get_ir(*item).compile(ctx);
|
let item_code = ctx.get_ir(*item).compile(ctx);
|
||||||
if stack_trace_enabled {
|
let item_span = encode_span(ctx.get_ir(*item).span(), ctx);
|
||||||
let item_span = encode_span(ctx.get_ir(*item).span(), ctx);
|
format!(
|
||||||
format!(
|
"Nix.withContext(\"while evaluating list element {}\",{},()=>({}))",
|
||||||
"Nix.withContext(\"while evaluating list element {}\",{},()=>({}))",
|
idx, item_span, item_code
|
||||||
idx, item_span, item_code
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
item_code
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.join(",");
|
.join(",");
|
||||||
format!("[{list}]")
|
format!("[{list}]")
|
||||||
@@ -441,22 +385,16 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
|||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
|
||||||
|
|
||||||
let parts: Vec<String> = self
|
let parts: Vec<String> = self
|
||||||
.parts
|
.parts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|part| {
|
.map(|part| {
|
||||||
let part_code = ctx.get_ir(*part).compile(ctx);
|
let part_code = ctx.get_ir(*part).compile(ctx);
|
||||||
if stack_trace_enabled {
|
let part_span = encode_span(ctx.get_ir(*part).span(), ctx);
|
||||||
let part_span = encode_span(ctx.get_ir(*part).span(), ctx);
|
format!(
|
||||||
format!(
|
"Nix.withContext(\"while evaluating a path segment\",{},()=>({}))",
|
||||||
"Nix.withContext(\"while evaluating a path segment\",{},()=>({}))",
|
part_span, part_code
|
||||||
part_span, part_code
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
part_code
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,21 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::HashMap;
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use petgraph::graphmap::DiGraphMap;
|
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
use crate::codegen::{CodegenContext, compile};
|
use crate::codegen::{CodegenContext, compile};
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::{
|
use crate::ir::{
|
||||||
Arg, ArgId, Bool, Builtin, Downgrade as _, DowngradeContext, ExprId, ExprRef, Ir, Null, SymId,
|
Arg, ArgId, Bool, Builtin, Downgrade as _, DowngradeContext, ExprId, Ir, Null, SymId, Thunk,
|
||||||
Thunk, ToIr as _, synthetic_span,
|
ToIr as _, synthetic_span,
|
||||||
};
|
};
|
||||||
use crate::runtime::{Runtime, RuntimeContext};
|
use crate::runtime::{Runtime, RuntimeContext};
|
||||||
use crate::store::{Store, StoreBackend, StoreConfig};
|
use crate::store::{Store, StoreBackend, StoreConfig};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct SccInfo {
|
|
||||||
/// list of SCCs (exprs, recursive)
|
|
||||||
pub(crate) sccs: Vec<(Vec<ExprId>, bool)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
runtime: Runtime<Ctx>,
|
runtime: Runtime<Ctx>,
|
||||||
@@ -256,14 +249,6 @@ impl RuntimeContext for Ctx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DependencyTracker {
|
|
||||||
graph: DiGraphMap<ExprId, ()>,
|
|
||||||
current_binding: Option<ExprId>,
|
|
||||||
let_scope_exprs: HashSet<ExprId>,
|
|
||||||
// The outer binding that owns this tracker (for nested let scopes in function params)
|
|
||||||
owner_binding: Option<ExprId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Scope<'ctx> {
|
enum Scope<'ctx> {
|
||||||
Global(&'ctx HashMap<SymId, ExprId>),
|
Global(&'ctx HashMap<SymId, ExprId>),
|
||||||
Let(HashMap<SymId, ExprId>),
|
Let(HashMap<SymId, ExprId>),
|
||||||
@@ -292,7 +277,7 @@ pub struct DowngradeCtx<'ctx> {
|
|||||||
irs: Vec<Option<Ir>>,
|
irs: Vec<Option<Ir>>,
|
||||||
scopes: Vec<Scope<'ctx>>,
|
scopes: Vec<Scope<'ctx>>,
|
||||||
arg_id: usize,
|
arg_id: usize,
|
||||||
dep_tracker_stack: Vec<DependencyTracker>,
|
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> DowngradeCtx<'ctx> {
|
impl<'ctx> DowngradeCtx<'ctx> {
|
||||||
@@ -301,7 +286,7 @@ impl<'ctx> DowngradeCtx<'ctx> {
|
|||||||
scopes: vec![Scope::Global(global)],
|
scopes: vec![Scope::Global(global)],
|
||||||
irs: vec![],
|
irs: vec![],
|
||||||
arg_id: 0,
|
arg_id: 0,
|
||||||
dep_tracker_stack: Vec::new(),
|
thunk_scopes: vec![Vec::new()],
|
||||||
ctx,
|
ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,14 +331,15 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
| Ir::Float(_)
|
| Ir::Float(_)
|
||||||
| Ir::Bool(_)
|
| Ir::Bool(_)
|
||||||
| Ir::Null(_)
|
| Ir::Null(_)
|
||||||
| Ir::Str(_) => id,
|
| Ir::Str(_)
|
||||||
_ => self.new_expr(
|
| Ir::Thunk(_) => id,
|
||||||
Thunk {
|
_ => {
|
||||||
inner: id,
|
let span = ir.span();
|
||||||
span: ir.span(),
|
let slot = self.reserve_slots(1).next().expect("reserve_slots failed");
|
||||||
}
|
self.replace_ir(slot, Thunk { inner: slot, span }.to_ir());
|
||||||
.to_ir(),
|
self.register_thunk(slot, id);
|
||||||
),
|
slot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,45 +361,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
}
|
}
|
||||||
Scope::Let(let_scope) => {
|
Scope::Let(let_scope) => {
|
||||||
if let Some(&expr) = let_scope.get(&sym) {
|
if let Some(&expr) = let_scope.get(&sym) {
|
||||||
// Find which tracker contains this expression
|
return Ok(self.new_expr(Thunk { inner: expr, span }.to_ir()));
|
||||||
let expr_tracker_idx = self
|
|
||||||
.dep_tracker_stack
|
|
||||||
.iter()
|
|
||||||
.position(|t| t.let_scope_exprs.contains(&expr));
|
|
||||||
|
|
||||||
// Find the innermost tracker with a current_binding
|
|
||||||
let current_tracker_idx = self
|
|
||||||
.dep_tracker_stack
|
|
||||||
.iter()
|
|
||||||
.rposition(|t| t.current_binding.is_some());
|
|
||||||
|
|
||||||
// Record dependency if both exist
|
|
||||||
if let (Some(expr_idx), Some(curr_idx)) =
|
|
||||||
(expr_tracker_idx, current_tracker_idx)
|
|
||||||
{
|
|
||||||
let current_binding = self.dep_tracker_stack[curr_idx]
|
|
||||||
.current_binding
|
|
||||||
.expect("current_binding not set");
|
|
||||||
let owner_binding = self.dep_tracker_stack[curr_idx].owner_binding;
|
|
||||||
|
|
||||||
// If referencing from inner scope to outer scope
|
|
||||||
if curr_idx >= expr_idx {
|
|
||||||
let tracker = &mut self.dep_tracker_stack[expr_idx];
|
|
||||||
let from_node = current_binding;
|
|
||||||
let to_node = expr;
|
|
||||||
if curr_idx > expr_idx {
|
|
||||||
// Cross-scope reference: use owner_binding if available
|
|
||||||
if let Some(owner) = owner_binding {
|
|
||||||
tracker.graph.add_edge(owner, expr, ());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Same-level reference: record directly
|
|
||||||
tracker.graph.add_edge(from_node, to_node, ());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(self.new_expr(ExprRef { inner: expr, span }.to_ir()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Scope::Param(param_sym, expr) => {
|
&Scope::Param(param_sym, expr) => {
|
||||||
@@ -486,11 +434,15 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||||
let root = root.downgrade(&mut self)?;
|
use crate::ir::TopLevel;
|
||||||
|
let body = root.downgrade(&mut self)?;
|
||||||
|
let thunks = self.pop_thunk_scope();
|
||||||
|
let span = self.get_ir(body).span();
|
||||||
|
let top_level = self.new_expr(TopLevel { body, thunks, span }.to_ir());
|
||||||
self.ctx
|
self.ctx
|
||||||
.irs
|
.irs
|
||||||
.extend(self.irs.into_iter().map(Option::unwrap));
|
.extend(self.irs.into_iter().map(Option::unwrap));
|
||||||
Ok(root)
|
Ok(top_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
|
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
|
||||||
@@ -515,83 +467,26 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&mut Self) -> R,
|
||||||
{
|
{
|
||||||
|
let namespace = self.maybe_thunk(namespace);
|
||||||
self.scopes.push(Scope::With(namespace));
|
self.scopes.push(Scope::With(namespace));
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
let mut guard = ScopeGuard { ctx: self };
|
||||||
f(guard.as_ctx())
|
f(guard.as_ctx())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_dep_tracker(&mut self, slots: &[ExprId]) {
|
fn push_thunk_scope(&mut self) {
|
||||||
let mut graph = DiGraphMap::new();
|
self.thunk_scopes.push(Vec::new());
|
||||||
let mut let_scope_exprs = HashSet::new();
|
|
||||||
|
|
||||||
for &expr in slots.iter() {
|
|
||||||
graph.add_node(expr);
|
|
||||||
let_scope_exprs.insert(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dep_tracker_stack.push(DependencyTracker {
|
|
||||||
graph,
|
|
||||||
current_binding: None,
|
|
||||||
let_scope_exprs,
|
|
||||||
owner_binding: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_dep_tracker_with_owner(&mut self, slots: &[ExprId], owner: ExprId) {
|
fn pop_thunk_scope(&mut self) -> Vec<(ExprId, ExprId)> {
|
||||||
let mut graph = DiGraphMap::new();
|
self.thunk_scopes
|
||||||
let mut let_scope_exprs = HashSet::new();
|
|
||||||
|
|
||||||
for &expr in slots.iter() {
|
|
||||||
graph.add_node(expr);
|
|
||||||
let_scope_exprs.insert(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dep_tracker_stack.push(DependencyTracker {
|
|
||||||
graph,
|
|
||||||
current_binding: None,
|
|
||||||
let_scope_exprs,
|
|
||||||
owner_binding: Some(owner),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_binding(&self) -> Option<ExprId> {
|
|
||||||
self.dep_tracker_stack
|
|
||||||
.last()
|
|
||||||
.and_then(|t| t.current_binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_current_binding(&mut self, expr: Option<ExprId>) {
|
|
||||||
if let Some(tracker) = self.dep_tracker_stack.last_mut() {
|
|
||||||
tracker.current_binding = expr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_dep_tracker(&mut self) -> Result<SccInfo> {
|
|
||||||
let tracker = self
|
|
||||||
.dep_tracker_stack
|
|
||||||
.pop()
|
.pop()
|
||||||
.expect("pop_dep_tracker without active tracker");
|
.expect("pop_thunk_scope without active scope")
|
||||||
|
}
|
||||||
|
|
||||||
use petgraph::algo::kosaraju_scc;
|
fn register_thunk(&mut self, slot: ExprId, inner: ExprId) {
|
||||||
let sccs = kosaraju_scc(&tracker.graph);
|
self.thunk_scopes
|
||||||
|
.last_mut()
|
||||||
let mut sccs_topo = Vec::new();
|
.expect("register_thunk without active scope")
|
||||||
|
.push((slot, inner));
|
||||||
for scc_nodes in sccs.iter() {
|
|
||||||
let mut scc_exprs = Vec::new();
|
|
||||||
let mut is_recursive = scc_nodes.len() > 1;
|
|
||||||
|
|
||||||
for &expr in scc_nodes {
|
|
||||||
scc_exprs.push(expr);
|
|
||||||
|
|
||||||
if !is_recursive && tracker.graph.contains_edge(expr, expr) {
|
|
||||||
is_recursive = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sccs_topo.push((scc_exprs, is_recursive));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SccInfo { sccs: sccs_topo })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,8 +214,8 @@ pub struct StackFrame {
|
|||||||
pub src: NamedSource<Arc<str>>,
|
pub src: NamedSource<Arc<str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_STACK_FRAMES: usize = 10;
|
const MAX_STACK_FRAMES: usize = 20;
|
||||||
const FRAMES_AT_START: usize = 5;
|
const FRAMES_AT_START: usize = 15;
|
||||||
const FRAMES_AT_END: usize = 5;
|
const FRAMES_AT_END: usize = 5;
|
||||||
|
|
||||||
pub(crate) fn parse_js_error(error: Box<JsError>, ctx: &impl RuntimeContext) -> Error {
|
pub(crate) fn parse_js_error(error: Box<JsError>, ctx: &impl RuntimeContext) -> Error {
|
||||||
@@ -234,7 +234,11 @@ pub(crate) fn parse_js_error(error: Box<JsError>, ctx: &impl RuntimeContext) ->
|
|||||||
} else {
|
} else {
|
||||||
(None, None, Vec::new())
|
(None, None, Vec::new())
|
||||||
};
|
};
|
||||||
let stack_trace = truncate_stack_trace(frames);
|
let stack_trace = if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
||||||
|
truncate_stack_trace(frames)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
let message = error.get_message().to_string();
|
let message = error.get_message().to_string();
|
||||||
let js_backtrace = error.stack.map(|stack| {
|
let js_backtrace = error.stack.map(|stack| {
|
||||||
stack
|
stack
|
||||||
@@ -272,7 +276,7 @@ fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
|
|||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
|
|
||||||
for line in stack.lines() {
|
for line in stack.lines() {
|
||||||
// Format: NIX_STACK_FRAME:start:end[:extra_data]
|
// Format: NIX_STACK_FRAME:source_id:start:end[:extra_data]
|
||||||
let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
|
let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use hashbrown::HashMap;
|
|||||||
use rnix::{TextRange, ast};
|
use rnix::{TextRange, ast};
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
use crate::context::SccInfo;
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::value::format_symbol;
|
use crate::value::format_symbol;
|
||||||
use nix_js_macros::ir;
|
use nix_js_macros::ir;
|
||||||
@@ -44,11 +43,9 @@ pub trait DowngradeContext {
|
|||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
|
|
||||||
fn push_dep_tracker(&mut self, slots: &[ExprId]);
|
fn push_thunk_scope(&mut self);
|
||||||
fn push_dep_tracker_with_owner(&mut self, slots: &[ExprId], owner: ExprId);
|
fn pop_thunk_scope(&mut self) -> Vec<(ExprId, ExprId)>;
|
||||||
fn get_current_binding(&self) -> Option<ExprId>;
|
fn register_thunk(&mut self, slot: ExprId, inner: ExprId);
|
||||||
fn set_current_binding(&mut self, expr: Option<ExprId>);
|
|
||||||
fn pop_dep_tracker(&mut self) -> Result<SccInfo>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ir! {
|
ir! {
|
||||||
@@ -71,10 +68,9 @@ ir! {
|
|||||||
Assert { pub assertion: ExprId, pub expr: ExprId, pub assertion_raw: String },
|
Assert { pub assertion: ExprId, pub expr: ExprId, pub assertion_raw: String },
|
||||||
ConcatStrings { pub parts: Vec<ExprId> },
|
ConcatStrings { pub parts: Vec<ExprId> },
|
||||||
Path { pub expr: ExprId },
|
Path { pub expr: ExprId },
|
||||||
Func { pub body: ExprId, pub param: Option<Param>, pub arg: ExprId },
|
Func { pub body: ExprId, pub param: Option<Param>, pub arg: ExprId, pub thunks: Vec<(ExprId, ExprId)> },
|
||||||
Let { pub binding_sccs: SccInfo, pub body: ExprId },
|
TopLevel { pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> },
|
||||||
Arg(ArgId),
|
Arg(ArgId),
|
||||||
ExprRef(ExprId),
|
|
||||||
Thunk(ExprId),
|
Thunk(ExprId),
|
||||||
Builtins,
|
Builtins,
|
||||||
Builtin(SymId),
|
Builtin(SymId),
|
||||||
@@ -101,10 +97,9 @@ impl Ir {
|
|||||||
Ir::ConcatStrings(c) => c.span,
|
Ir::ConcatStrings(c) => c.span,
|
||||||
Ir::Path(p) => p.span,
|
Ir::Path(p) => p.span,
|
||||||
Ir::Func(f) => f.span,
|
Ir::Func(f) => f.span,
|
||||||
Ir::Let(l) => l.span,
|
Ir::TopLevel(t) => t.span,
|
||||||
Ir::Arg(a) => a.span,
|
Ir::Arg(a) => a.span,
|
||||||
Ir::ExprRef(e) => e.span,
|
Ir::Thunk(e) => e.span,
|
||||||
Ir::Thunk(t) => t.span,
|
|
||||||
Ir::Builtins(b) => b.span,
|
Ir::Builtins(b) => b.span,
|
||||||
Ir::Builtin(b) => b.span,
|
Ir::Builtin(b) => b.span,
|
||||||
Ir::CurPos(c) => c.span,
|
Ir::CurPos(c) => c.span,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
|||||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
let inner = interpol.expr().unwrap().downgrade(ctx)?;
|
let inner = interpol.expr().unwrap().downgrade(ctx)?;
|
||||||
Ok(ctx.new_expr(Thunk { inner, span }.to_ir()))
|
Ok(ctx.maybe_thunk(inner))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
@@ -220,7 +220,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
|||||||
|
|
||||||
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
|
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
|
||||||
let entries: Vec<_> = self.entries().collect();
|
let entries: Vec<_> = self.entries().collect();
|
||||||
let (binding_sccs, body) = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| {
|
downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
|
||||||
// Create plain attrset as body with inherit
|
// Create plain attrset as body with inherit
|
||||||
let mut attrs = AttrSet {
|
let mut attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
@@ -229,22 +229,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for sym in binding_keys {
|
for sym in binding_keys {
|
||||||
// FIXME: span
|
|
||||||
let expr = ctx.lookup(*sym, synthetic_span())?;
|
let expr = ctx.lookup(*sym, synthetic_span())?;
|
||||||
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ctx.new_expr(attrs.to_ir()))
|
Ok(ctx.new_expr(attrs.to_ir()))
|
||||||
})?;
|
})
|
||||||
|
|
||||||
Ok(ctx.new_expr(
|
|
||||||
Let {
|
|
||||||
body,
|
|
||||||
binding_sccs,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,17 +298,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
|||||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
let expr = self.expr().unwrap().downgrade(ctx)?;
|
||||||
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||||
let default = if let Some(default) = self.default_expr() {
|
let default = if let Some(default) = self.default_expr() {
|
||||||
let span = default.syntax().text_range();
|
|
||||||
let default_expr = default.downgrade(ctx)?;
|
let default_expr = default.downgrade(ctx)?;
|
||||||
Some(
|
Some(ctx.maybe_thunk(default_expr))
|
||||||
ctx.new_expr(
|
|
||||||
Thunk {
|
|
||||||
inner: default_expr,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -378,17 +359,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
let body_expr = self.body().unwrap();
|
let body_expr = self.body().unwrap();
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
|
||||||
let (binding_sccs, body) =
|
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
|
||||||
downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| body_expr.downgrade(ctx))?;
|
body_expr.downgrade(ctx)
|
||||||
|
})
|
||||||
Ok(ctx.new_expr(
|
|
||||||
Let {
|
|
||||||
body,
|
|
||||||
binding_sccs,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,9 +385,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
let raw_param = self.param().unwrap();
|
let raw_param = self.param().unwrap();
|
||||||
let arg = ctx.new_arg(raw_param.syntax().text_range());
|
let arg = ctx.new_arg(raw_param.syntax().text_range());
|
||||||
|
|
||||||
|
ctx.push_thunk_scope();
|
||||||
|
|
||||||
let param;
|
let param;
|
||||||
let body;
|
let body;
|
||||||
let span = self.body().unwrap().syntax().text_range();
|
|
||||||
|
|
||||||
match raw_param {
|
match raw_param {
|
||||||
ast::Param::IdentParam(id) => {
|
ast::Param::IdentParam(id) => {
|
||||||
@@ -436,7 +410,6 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
|
|
||||||
let PatternBindings {
|
let PatternBindings {
|
||||||
body: inner_body,
|
body: inner_body,
|
||||||
scc_info,
|
|
||||||
required,
|
required,
|
||||||
optional,
|
optional,
|
||||||
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
||||||
@@ -449,24 +422,18 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
ellipsis,
|
ellipsis,
|
||||||
});
|
});
|
||||||
|
|
||||||
body = ctx.new_expr(
|
body = inner_body;
|
||||||
Let {
|
|
||||||
body: inner_body,
|
|
||||||
binding_sccs: scc_info,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let thunks = ctx.pop_thunk_scope();
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
// The function's body and parameters are now stored directly in the `Func` node.
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Func {
|
Func {
|
||||||
body,
|
body,
|
||||||
param,
|
param,
|
||||||
arg,
|
arg,
|
||||||
|
thunks,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use hashbrown::hash_map::Entry;
|
|||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use rnix::ast;
|
use rnix::ast;
|
||||||
|
use rnix::TextRange;
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
@@ -99,13 +100,7 @@ pub fn downgrade_inherit(
|
|||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
);
|
);
|
||||||
ctx.new_expr(
|
ctx.maybe_thunk(select_expr)
|
||||||
Thunk {
|
|
||||||
inner: select_expr,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
ctx.lookup(ident, span)?
|
ctx.lookup(ident, span)?
|
||||||
};
|
};
|
||||||
@@ -221,20 +216,12 @@ pub fn downgrade_static_attrpathvalue(
|
|||||||
|
|
||||||
pub struct PatternBindings {
|
pub struct PatternBindings {
|
||||||
pub body: ExprId,
|
pub body: ExprId,
|
||||||
pub scc_info: SccInfo,
|
|
||||||
pub required: Vec<SymId>,
|
pub required: Vec<SymId>,
|
||||||
pub optional: Vec<SymId>,
|
pub optional: Vec<SymId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for Lambda pattern parameters with SCC analysis.
|
/// Helper function for Lambda pattern parameters.
|
||||||
/// Processes pattern entries like `{ a, b ? 2, ... }@alias` and creates optimized bindings.
|
/// Processes pattern entries like `{ a, b ? 2, ... }@alias` and creates bindings.
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `pat_entries`: Iterator over pattern entries from the AST
|
|
||||||
/// - `alias`: Optional alias symbol (from @alias syntax)
|
|
||||||
/// - `arg`: The argument expression to extract from
|
|
||||||
///
|
|
||||||
/// Returns a tuple of (binding slots, body, SCC info, required params, allowed params)
|
|
||||||
pub fn downgrade_pattern_bindings<Ctx>(
|
pub fn downgrade_pattern_bindings<Ctx>(
|
||||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||||
alias: Option<SymId>,
|
alias: Option<SymId>,
|
||||||
@@ -294,97 +281,6 @@ where
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the owner from outer tracker's current_binding
|
|
||||||
let owner = ctx.get_current_binding();
|
|
||||||
|
|
||||||
let (scc_info, body) = downgrade_bindings_generic_with_owner(
|
|
||||||
ctx,
|
|
||||||
binding_keys,
|
|
||||||
|ctx, sym_to_slot| {
|
|
||||||
let mut bindings = HashMap::new();
|
|
||||||
|
|
||||||
for Param {
|
|
||||||
sym,
|
|
||||||
sym_span,
|
|
||||||
default,
|
|
||||||
span,
|
|
||||||
} in params
|
|
||||||
{
|
|
||||||
let slot = *sym_to_slot.get(&sym).unwrap();
|
|
||||||
ctx.set_current_binding(Some(slot));
|
|
||||||
|
|
||||||
let default = if let Some(default) = default {
|
|
||||||
Some(default.clone().downgrade(ctx)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let select_expr = ctx.new_expr(
|
|
||||||
Select {
|
|
||||||
expr: arg,
|
|
||||||
attrpath: vec![Attr::Str(sym, sym_span)],
|
|
||||||
default,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
);
|
|
||||||
bindings.insert(sym, select_expr);
|
|
||||||
ctx.set_current_binding(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(alias_sym) = alias {
|
|
||||||
bindings.insert(alias_sym, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(bindings)
|
|
||||||
},
|
|
||||||
body_fn,
|
|
||||||
owner, // Pass the owner to track cross-scope dependencies
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(PatternBindings {
|
|
||||||
body,
|
|
||||||
scc_info,
|
|
||||||
required,
|
|
||||||
optional,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic helper function to downgrade bindings with SCC analysis.
|
|
||||||
/// This is the core logic for let bindings, extracted for reuse.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `binding_keys`: The symbols for all bindings
|
|
||||||
/// - `compute_bindings_fn`: Called in let scope with sym_to_slot mapping to compute binding values
|
|
||||||
/// - `body_fn`: Called in let scope to compute the body expression
|
|
||||||
///
|
|
||||||
/// Returns a tuple of (binding slots, body result, SCC info)
|
|
||||||
pub fn downgrade_bindings_generic<Ctx, B, F>(
|
|
||||||
ctx: &mut Ctx,
|
|
||||||
binding_keys: Vec<SymId>,
|
|
||||||
compute_bindings_fn: B,
|
|
||||||
body_fn: F,
|
|
||||||
) -> Result<(SccInfo, ExprId)>
|
|
||||||
where
|
|
||||||
Ctx: DowngradeContext,
|
|
||||||
B: FnOnce(&mut Ctx, &HashMap<SymId, ExprId>) -> Result<HashMap<SymId, ExprId>>,
|
|
||||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
|
||||||
{
|
|
||||||
downgrade_bindings_generic_with_owner(ctx, binding_keys, compute_bindings_fn, body_fn, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn downgrade_bindings_generic_with_owner<Ctx, B, F>(
|
|
||||||
ctx: &mut Ctx,
|
|
||||||
binding_keys: Vec<SymId>,
|
|
||||||
compute_bindings_fn: B,
|
|
||||||
body_fn: F,
|
|
||||||
owner: Option<ExprId>,
|
|
||||||
) -> Result<(SccInfo, ExprId)>
|
|
||||||
where
|
|
||||||
Ctx: DowngradeContext,
|
|
||||||
B: FnOnce(&mut Ctx, &HashMap<SymId, ExprId>) -> Result<HashMap<SymId, ExprId>>,
|
|
||||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
|
||||||
{
|
|
||||||
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
||||||
let let_bindings: HashMap<_, _> = binding_keys
|
let let_bindings: HashMap<_, _> = binding_keys
|
||||||
.iter()
|
.iter()
|
||||||
@@ -392,53 +288,63 @@ where
|
|||||||
.zip(slots.iter().copied())
|
.zip(slots.iter().copied())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(owner_binding) = owner {
|
for &slot in &slots {
|
||||||
ctx.push_dep_tracker_with_owner(&slots, owner_binding);
|
let span = synthetic_span();
|
||||||
} else {
|
ctx.replace_ir(slot, Thunk { inner: slot, span }.to_ir());
|
||||||
ctx.push_dep_tracker(&slots);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
||||||
let bindings = compute_bindings_fn(ctx, &let_bindings)?;
|
for Param {
|
||||||
|
sym,
|
||||||
|
sym_span,
|
||||||
|
default,
|
||||||
|
span,
|
||||||
|
} in params
|
||||||
|
{
|
||||||
|
let slot = *let_bindings.get(&sym).unwrap();
|
||||||
|
|
||||||
let scc_info = ctx.pop_dep_tracker()?;
|
let default = if let Some(default) = default {
|
||||||
|
let default = default.clone().downgrade(ctx)?;
|
||||||
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
Some(ctx.maybe_thunk(default))
|
||||||
if let Some(&expr) = bindings.get(&sym) {
|
|
||||||
ctx.replace_ir(
|
|
||||||
*slot,
|
|
||||||
Thunk {
|
|
||||||
inner: expr,
|
|
||||||
span: ctx.get_ir(expr).span(),
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::internal(format!(
|
None
|
||||||
"binding '{}' not found",
|
};
|
||||||
format_symbol(ctx.get_sym(sym))
|
|
||||||
)));
|
let select_expr = ctx.new_expr(
|
||||||
}
|
Select {
|
||||||
|
expr: arg,
|
||||||
|
attrpath: vec![Attr::Str(sym, sym_span)],
|
||||||
|
default,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
|
ctx.register_thunk(slot, select_expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(alias_sym) = alias {
|
||||||
|
let slot = *let_bindings.get(&alias_sym).unwrap();
|
||||||
|
ctx.register_thunk(slot, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = body_fn(ctx, &binding_keys)?;
|
let body = body_fn(ctx, &binding_keys)?;
|
||||||
|
|
||||||
Ok((scc_info, body))
|
Ok(PatternBindings {
|
||||||
|
body,
|
||||||
|
required,
|
||||||
|
optional,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to downgrade entries with let bindings semantics.
|
/// Helper function to downgrade entries with let bindings semantics.
|
||||||
/// This extracts common logic for both `rec` attribute sets and `let...in` expressions.
|
/// This extracts common logic for both `rec` attribute sets and `let...in` expressions.
|
||||||
///
|
|
||||||
/// Returns a tuple of (binding slots, body result, SCC info) where:
|
|
||||||
/// - binding slots: pre-allocated expression slots for the bindings
|
|
||||||
/// - body result: the result of calling `body_fn` in the let scope
|
|
||||||
/// - SCC info: strongly connected components information for optimization
|
|
||||||
pub fn downgrade_let_bindings<Ctx, F>(
|
pub fn downgrade_let_bindings<Ctx, F>(
|
||||||
entries: Vec<ast::Entry>,
|
entries: Vec<ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
|
_span: TextRange,
|
||||||
body_fn: F,
|
body_fn: F,
|
||||||
) -> Result<(SccInfo, ExprId)>
|
) -> Result<ExprId>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext,
|
Ctx: DowngradeContext,
|
||||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
||||||
@@ -468,8 +374,6 @@ where
|
|||||||
let attrpath = value.attrpath().unwrap();
|
let attrpath = value.attrpath().unwrap();
|
||||||
let attrs_vec: Vec<_> = attrpath.attrs().collect();
|
let attrs_vec: Vec<_> = attrpath.attrs().collect();
|
||||||
|
|
||||||
// Only check for duplicate definitions if this is a top-level binding (path length == 1)
|
|
||||||
// For nested paths (e.g., types.a, types.b), they will be merged into the same attrset
|
|
||||||
if attrs_vec.len() == 1 {
|
if attrs_vec.len() == 1 {
|
||||||
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
let sym = ctx.new_sym(ident.to_string());
|
||||||
@@ -485,7 +389,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if attrs_vec.len() > 1 {
|
} else if attrs_vec.len() > 1 {
|
||||||
// For nested paths, just record the first-level name without checking duplicates
|
|
||||||
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
let sym = ctx.new_sym(ident.to_string());
|
||||||
binding_syms.insert(sym);
|
binding_syms.insert(sym);
|
||||||
@@ -496,51 +399,47 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
||||||
|
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
||||||
|
let let_bindings: HashMap<_, _> = binding_keys
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.zip(slots.iter().copied())
|
||||||
|
.collect();
|
||||||
|
|
||||||
downgrade_bindings_generic(
|
for &slot in &slots {
|
||||||
ctx,
|
let span = synthetic_span();
|
||||||
binding_keys,
|
ctx.replace_ir(slot, Thunk { inner: slot, span }.to_ir());
|
||||||
|ctx, sym_to_slot| {
|
}
|
||||||
let mut temp_attrs = AttrSet {
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
span: synthetic_span(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
||||||
match entry {
|
let mut temp_attrs = AttrSet {
|
||||||
ast::Entry::Inherit(inherit) => {
|
stcs: HashMap::new(),
|
||||||
for attr in inherit.attrs() {
|
dyns: Vec::new(),
|
||||||
if let ast::Attr::Ident(ident) = attr {
|
span: synthetic_span(),
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
};
|
||||||
let slot = *sym_to_slot.get(&sym).unwrap();
|
|
||||||
ctx.set_current_binding(Some(slot));
|
for entry in entries {
|
||||||
}
|
match entry {
|
||||||
}
|
ast::Entry::Inherit(inherit) => {
|
||||||
downgrade_inherit(inherit, &mut temp_attrs.stcs, ctx)?;
|
downgrade_inherit(inherit, &mut temp_attrs.stcs, ctx)?;
|
||||||
ctx.set_current_binding(None);
|
}
|
||||||
}
|
ast::Entry::AttrpathValue(value) => {
|
||||||
ast::Entry::AttrpathValue(value) => {
|
downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?;
|
||||||
let attrpath = value.attrpath().unwrap();
|
|
||||||
if let Some(first_attr) = attrpath.attrs().next()
|
|
||||||
&& let ast::Attr::Ident(ident) = first_attr
|
|
||||||
{
|
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
|
||||||
let slot = *sym_to_slot.get(&sym).unwrap();
|
|
||||||
ctx.set_current_binding(Some(slot));
|
|
||||||
}
|
|
||||||
downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?;
|
|
||||||
ctx.set_current_binding(None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(temp_attrs
|
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
||||||
.stcs
|
if let Some(&(expr, _)) = temp_attrs.stcs.get(&sym) {
|
||||||
.into_iter()
|
ctx.register_thunk(*slot, expr);
|
||||||
.map(|(k, (v, _))| (k, v))
|
} else {
|
||||||
.collect())
|
return Err(Error::internal(format!(
|
||||||
},
|
"binding '{}' not found",
|
||||||
body_fn,
|
format_symbol(ctx.get_sym(sym))
|
||||||
)
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body_fn(ctx, &binding_keys)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -539,7 +539,11 @@ fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
|||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
#[string]
|
#[string]
|
||||||
fn op_get_env(#[string] key: String) -> std::result::Result<String, NixError> {
|
fn op_get_env(#[string] key: String) -> std::result::Result<String, NixError> {
|
||||||
Ok(std::env::var(key).map_err(|err| format!("Failed to read env var: {err}"))?)
|
match std::env::var(key) {
|
||||||
|
Ok(val) => Ok(val),
|
||||||
|
Err(std::env::VarError::NotPresent) => Ok("".into()),
|
||||||
|
Err(err) => Err(format!("Failed to read env var: {err}").into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
||||||
|
|||||||
Reference in New Issue
Block a user