diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index 8734f9a..1128a28 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -84,23 +84,20 @@ export const withContext = (message: string, span: string, fn: () => T): T => * - Path mode: Store contexts are forbidden (will throw error) * - String mode: All contexts are preserved and merged * - * If first element is a path, result is a path (with constraint: no store context allowed) - * * @param parts - Array of values to concatenate + * @param forceString - If true, result is always a string (paths are copied to store) * @returns String or Path with merged contexts from all parts */ -export const concatStringsWithContext = (parts: NixValue[]): NixString | NixPath => { +export const concatStringsWithContext = (parts: NixValue[], forceString: boolean): NixString | NixPath => { if (parts.length === 0) { return ""; } const forced = parts.map(force); - // Check if first element is a path - const firstIsPath = isNixPath(forced[0]); + const firstIsPath = !forceString && isNixPath(forced[0]); if (firstIsPath) { - // Path concatenation mode: result will be a path let result = (forced[0] as NixPath).value; for (let i = 1; i < forced.length; i++) { @@ -111,13 +108,11 @@ export const concatStringsWithContext = (parts: NixValue[]): NixString | NixPath } else if (typeof part === "string") { result += part; } else if (isStringWithContext(part)) { - // Lix constraint: cannot mix store context with paths if (part.context.size > 0) { throw new TypeError("a string that refers to a store path cannot be appended to a path"); } result += part.value; } else { - // Coerce to string const tempContext: NixStringContext = new Set(); const coerced = coerceToString(part, StringCoercionMode.Interpolation, false, tempContext); @@ -132,19 +127,15 @@ export const concatStringsWithContext = (parts: NixValue[]): NixString | NixPath return mkPath(result); } - // String concatenation mode - // Note: firstIsPath is already false at this point, otherwise we would have - // returned in the path concatenation branch above const context: NixStringContext = new Set(); const strParts: string[] = []; - for (const part of parts) { - const forced = force(part); - if (isNixPath(forced)) { - const str = coerceToString(forced, StringCoercionMode.Interpolation, true, context); + for (const part of forced) { + if (isNixPath(part)) { + const str = coerceToString(part, StringCoercionMode.Interpolation, true, context); strParts.push(str); } else { - const str = coerceToString(forced, StringCoercionMode.Interpolation, false, context); + const str = coerceToString(part, StringCoercionMode.Interpolation, false, context); strParts.push(str); } } diff --git a/nix-js/runtime-ts/src/string-context.ts b/nix-js/runtime-ts/src/string-context.ts index 2da8a04..0c6e20c 100644 --- a/nix-js/runtime-ts/src/string-context.ts +++ b/nix-js/runtime-ts/src/string-context.ts @@ -165,6 +165,10 @@ export const parseContextToInfoMap = (context: NixStringContext): Map Compile for usize { } } +impl Compile for bool { + fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) { + let _ = write!(buf, "{self}"); + } +} + impl Compile for Quoted<'_> { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { code!(buf, ctx; "\"" escaped(self.0) "\"") @@ -616,7 +622,7 @@ impl Compile for ConcatStrings { "Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))" ); }) - "])" + "]," self.force_string ")" ); } } diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs index c0740c7..b0fe989 100644 --- a/nix-js/src/ir.rs +++ b/nix-js/src/ir.rs @@ -64,7 +64,7 @@ ir! { If { pub cond: ExprId, pub consq: ExprId, pub alter: ExprId }, Call { pub func: ExprId, pub arg: ExprId }, Assert { pub assertion: ExprId, pub expr: ExprId, pub assertion_raw: String }, - ConcatStrings { pub parts: Vec }, + ConcatStrings { pub parts: Vec, pub force_string: bool }, Path { pub expr: ExprId }, Func { pub body: ExprId, pub param: Option, pub arg: ExprId, pub thunks: Vec<(ExprId, ExprId)> }, TopLevel { pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> }, diff --git a/nix-js/src/ir/downgrade.rs b/nix-js/src/ir/downgrade.rs index b11496d..23f03bf 100644 --- a/nix-js/src/ir/downgrade.rs +++ b/nix-js/src/ir/downgrade.rs @@ -139,7 +139,7 @@ impl Downgrade for ast::Path { part } } else { - ctx.new_expr(ConcatStrings { parts, span }.to_ir()) + ctx.new_expr(ConcatStrings { parts, span, force_string: false }.to_ir()) }; Ok(ctx.new_expr(Path { expr, span }.to_ir())) } @@ -166,7 +166,7 @@ impl Downgrade for ast::Str { Ok(if is_single_literal { parts.into_iter().next().unwrap() } else { - ctx.new_expr(ConcatStrings { parts, span }.to_ir()) + ctx.new_expr(ConcatStrings { parts, span, force_string: true }.to_ir()) }) } } diff --git a/nix-js/src/ir/utils.rs b/nix-js/src/ir/utils.rs index fc01f0d..40c4baa 100644 --- a/nix-js/src/ir/utils.rs +++ b/nix-js/src/ir/utils.rs @@ -442,7 +442,7 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul }) .collect::>>()?; Ok(Attr::Dynamic( - ctx.new_expr(ConcatStrings { parts, span }.to_ir()), + ctx.new_expr(ConcatStrings { parts, span, force_string: true }.to_ir()), span, )) }