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)
* - 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);
}
}

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;
};

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<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
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 "))"
);
})
"])"
"]," self.force_string ")"
);
}
}

View File

@@ -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<ExprId> },
ConcatStrings { pub parts: Vec<ExprId>, pub force_string: bool },
Path { pub expr: 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)> },

View File

@@ -139,7 +139,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> 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<Ctx: DowngradeContext> Downgrade<Ctx> 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())
})
}
}

View File

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