From b4e0b53cdee8fdc79d23838e21380f61e010675b Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Tue, 13 Jan 2026 16:22:12 +0800 Subject: [PATCH] fix: select --- nix-js/runtime-ts/src/helpers.ts | 70 +++++++++++++++++--------------- nix-js/src/codegen.rs | 52 ++++++------------------ 2 files changed, 51 insertions(+), 71 deletions(-) diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index 0bbc8e0..2fdb5f5 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -46,44 +46,50 @@ export const resolvePath = (path: NixValue): string => { return Deno.core.ops.op_resolve_path(path_str); }; -/** - * Select an attribute from an attribute set - * Used by codegen for attribute access (e.g., obj.key) - * - * @param obj - Attribute set to select from - * @param key - Key to select - * @returns The value at obj[key] - * @throws Error if obj is null/undefined or key not found - */ -export const select = (obj: NixValue, key: NixValue): NixValue => { - const forced_obj = forceAttrs(obj); - const forced_key = forceString(key); +export const select = (obj: NixValue, attrpath: NixValue[]): NixValue => { + let attrs = forceAttrs(obj); - if (!(forced_key in forced_obj)) { - throw new Error(`Attribute '${forced_key}' not found`); + for (const attr of attrpath.slice(0, -1)) { + const key = forceString(attr) + if (!(key in attrs)) { + throw new Error(`Attribute '${key}' not found`); + } + const cur = force(attrs[forceString(attr)]); + if (!isAttrs(cur)) { + // throw new Error(`Attribute '${forced_key}' not found`); + // FIXME: error + throw new Error(`Attribute not found`); + } + attrs = cur; } - return forced_obj[forced_key]; + const last = forceString(attrpath[attrpath.length - 1]) + if (!(last in attrs)) { + throw new Error(`Attribute '${last}' not found`); + } + return attrs[last]; }; -/** - * Select an attribute with a default value - * Used for Nix's `obj.key or default` syntax - * - * @param obj - Attribute set to select from - * @param key - Key to select - * @param default_val - Value to return if key not found (will be forced if it's a thunk) - * @returns obj[key] if exists, otherwise force(default_val) - */ -export const selectWithDefault = (obj: NixValue, key: NixValue, default_val: NixValue): NixValue => { - const attrs = forceAttrs(obj); - const forced_key = forceString(key); +export const selectWithDefault = (obj: NixValue, attrpath: NixValue[], default_val: NixValue): NixValue => { + let attrs = forceAttrs(obj); - if (!(forced_key in attrs)) { - return force(default_val); + for (const attr of attrpath.slice(0, -1)) { + const key = forceString(attr) + if (!(key in attrs)) { + return default_val + } + const cur = force(attrs[key]); + if (!isAttrs(cur)) { + return default_val; + } + attrs = cur; } - return attrs[forced_key]; + const last = forceString(attrpath[attrpath.length - 1]); + if (last in attrs) { + return attrs[last] + } + return default_val; }; export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => { @@ -93,14 +99,14 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => { let attrs = obj; for (const attr of attrpath.slice(0, -1)) { - const cur = attrs[forceString(attr)]; + const cur = force(attrs[forceString(attr)]); if (!isAttrs(cur)) { return false; } attrs = cur; } - return true; + return forceString(attrpath[attrpath.length - 1]) in attrs; }; /** diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index e3c306c..ea5813d 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -225,46 +225,20 @@ impl Compile for Let { impl Compile for Select { fn compile(&self, ctx: &Ctx) -> String { - let expr = ctx.get_ir(self.expr).compile(ctx); - - let mut result = expr; - let attr_count = self.attrpath.len(); - - for (i, attr) in self.attrpath.iter().enumerate() { - let is_last = i == attr_count - 1; - result = match attr { - Attr::Str(sym) => { - let key = ctx.get_sym(*sym).escape_quote(); - if let Some(default) = self.default - && is_last - { - let default_val = ctx.get_ir(default).compile(ctx); - format!( - "Nix.selectWithDefault({}, {}, {})", - result, key, default_val - ) - } else { - format!("Nix.select({}, {})", result, key) - } - } - Attr::Dynamic(expr_id) => { - let key = ctx.get_ir(*expr_id).compile(ctx); - if let Some(default) = self.default - && is_last - { - let default_val = ctx.get_ir(default).compile(ctx); - format!( - "Nix.selectWithDefault({}, {}, {})", - result, key, default_val - ) - } else { - format!("Nix.select({}, {})", result, key) - } - } - }; + let lhs = ctx.get_ir(self.expr).compile(ctx); + let attrpath = self + .attrpath + .iter() + .map(|attr| match attr { + Attr::Str(sym) => ctx.get_sym(*sym).escape_quote(), + Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx), + }) + .join(","); + if let Some(default) = self.default { + format!("Nix.selectWithDefault({lhs}, [{attrpath}], {})", ctx.get_ir(default).compile(ctx)) + } else { + format!("Nix.select({lhs}, [{attrpath}])") } - - result } }