fix: copy path to store in concatStringsWithContext

This commit is contained in:
2026-01-31 12:42:27 +08:00
parent f0812c9063
commit 5703329850
6 changed files with 22 additions and 21 deletions

View File

@@ -84,23 +84,20 @@ export const withContext = <T>(message: string, span: string, fn: () => T): T =>
* - Path mode: Store contexts are forbidden (will throw error) * - Path mode: Store contexts are forbidden (will throw error)
* - String mode: All contexts are preserved and merged * - 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 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 * @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) { if (parts.length === 0) {
return ""; return "";
} }
const forced = parts.map(force); const forced = parts.map(force);
// Check if first element is a path const firstIsPath = !forceString && isNixPath(forced[0]);
const firstIsPath = isNixPath(forced[0]);
if (firstIsPath) { if (firstIsPath) {
// Path concatenation mode: result will be a path
let result = (forced[0] as NixPath).value; let result = (forced[0] as NixPath).value;
for (let i = 1; i < forced.length; i++) { for (let i = 1; i < forced.length; i++) {
@@ -111,13 +108,11 @@ export const concatStringsWithContext = (parts: NixValue[]): NixString | NixPath
} else if (typeof part === "string") { } else if (typeof part === "string") {
result += part; result += part;
} else if (isStringWithContext(part)) { } else if (isStringWithContext(part)) {
// Lix constraint: cannot mix store context with paths
if (part.context.size > 0) { if (part.context.size > 0) {
throw new TypeError("a string that refers to a store path cannot be appended to a path"); throw new TypeError("a string that refers to a store path cannot be appended to a path");
} }
result += part.value; result += part.value;
} else { } else {
// Coerce to string
const tempContext: NixStringContext = new Set(); const tempContext: NixStringContext = new Set();
const coerced = coerceToString(part, StringCoercionMode.Interpolation, false, tempContext); const coerced = coerceToString(part, StringCoercionMode.Interpolation, false, tempContext);
@@ -132,19 +127,15 @@ export const concatStringsWithContext = (parts: NixValue[]): NixString | NixPath
return mkPath(result); 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 context: NixStringContext = new Set();
const strParts: string[] = []; const strParts: string[] = [];
for (const part of parts) { for (const part of forced) {
const forced = force(part); if (isNixPath(part)) {
if (isNixPath(forced)) { const str = coerceToString(part, StringCoercionMode.Interpolation, true, context);
const str = coerceToString(forced, StringCoercionMode.Interpolation, true, context);
strParts.push(str); strParts.push(str);
} else { } else {
const str = coerceToString(forced, StringCoercionMode.Interpolation, false, context); const str = coerceToString(part, StringCoercionMode.Interpolation, false, context);
strParts.push(str); strParts.push(str);
} }
} }

View File

@@ -165,6 +165,10 @@ export const parseContextToInfoMap = (context: NixStringContext): Map<string, Pa
} }
} }
for (const info of result.values()) {
info.outputs.sort();
}
return result; return result;
}; };

View File

@@ -141,6 +141,12 @@ impl<Ctx: CodegenContext> Compile<Ctx> for usize {
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for bool {
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
let _ = write!(buf, "{self}");
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Quoted<'_> { impl<Ctx: CodegenContext> Compile<Ctx> for Quoted<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; "\"" escaped(self.0) "\"") code!(buf, ctx; "\"" escaped(self.0) "\"")
@@ -616,7 +622,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
"Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))" "Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))"
); );
}) })
"])" "]," self.force_string ")"
); );
} }
} }

View File

@@ -64,7 +64,7 @@ ir! {
If { pub cond: ExprId, pub consq: ExprId, pub alter: ExprId }, If { pub cond: ExprId, pub consq: ExprId, pub alter: ExprId },
Call { pub func: ExprId, pub arg: ExprId }, Call { pub func: ExprId, pub arg: ExprId },
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>, pub force_string: bool },
Path { pub expr: ExprId }, Path { pub expr: ExprId },
Func { pub body: ExprId, pub param: Option<Param>, pub arg: ExprId, pub thunks: Vec<(ExprId, ExprId)> }, Func { pub body: ExprId, pub param: Option<Param>, pub arg: ExprId, pub thunks: Vec<(ExprId, ExprId)> },
TopLevel { pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> }, TopLevel { pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> },

View File

@@ -139,7 +139,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
part part
} }
} else { } 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())) Ok(ctx.new_expr(Path { expr, span }.to_ir()))
} }
@@ -166,7 +166,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
Ok(if is_single_literal { Ok(if is_single_literal {
parts.into_iter().next().unwrap() parts.into_iter().next().unwrap()
} else { } else {
ctx.new_expr(ConcatStrings { parts, span }.to_ir()) ctx.new_expr(ConcatStrings { parts, span, force_string: true }.to_ir())
}) })
} }
} }

View File

@@ -442,7 +442,7 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
}) })
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
Ok(Attr::Dynamic( Ok(Attr::Dynamic(
ctx.new_expr(ConcatStrings { parts, span }.to_ir()), ctx.new_expr(ConcatStrings { parts, span, force_string: true }.to_ir()),
span, span,
)) ))
} }