Skip to content
This repository was archived by the owner on Jan 25, 2024. It is now read-only.

Commit b971524

Browse files
committed
fix attrset merging for >= 3 instances
1 parent e6a41cb commit b971524

File tree

2 files changed

+67
-12
lines changed

2 files changed

+67
-12
lines changed

src/eval.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,34 @@ impl Expr {
383383

384384
/// Used for merging sets during parsing. For example:
385385
/// { a.b = 1; a.c = 2; } => { a = { b = 1; c = 2; }; }
386+
///
387+
/// Nix only allows merging several such KeyValuePairs when they correspond
388+
/// to bare literals. Inserting a mere indirection through let or a function trips
389+
/// it of:
390+
/// ```text
391+
/// nix-repl> :p { a = { b = 1; }; a.c = 2; }
392+
/// { a = { b = 1; c = 2; }; }
393+
///
394+
/// nix-repl> :p { a = (x: x){ b = 1; }; a.c = 2; }
395+
/// error: attribute 'a.c' already defined at (string):1:3
396+
///
397+
/// at «string»:1:25:
398+
///
399+
/// 1| { a = (x: x){ b = 1; }; a.c = 2; }
400+
/// | ^
401+
///
402+
/// nix-repl> :p let y = { b = 1; }; in { a = y; a.c = 2; }
403+
/// error: attribute 'a.c' already defined at (string):1:26
404+
///
405+
/// at «string»:1:33:
406+
///
407+
/// 1| let y = { b = 1; }; in { a = y; a.c = 2; }
408+
/// | ^
409+
///
410+
/// ```
411+
/// This function takes a and b, attempts to interpret them as litteral
412+
/// key value pairs or literal attrset values, and if it failed returns
413+
/// such an error.
386414
pub fn merge_set_literal(name: String, a: Gc<Expr>, b: Gc<Expr>) -> Result<Gc<Expr>, EvalError> {
387415
// evaluate literal attr sets only, otherwise error
388416
let eval_literal = |src: Gc<Expr>| {
@@ -391,18 +419,25 @@ pub fn merge_set_literal(name: String, a: Gc<Expr>, b: Gc<Expr>) -> Result<Gc<Ex
391419
} else {
392420
src
393421
};
394-
if let ExprSource::AttrSet { .. } = &src.source {
395-
src.eval()?.as_map()
396-
} else {
397-
// We cannot merge a literal with a non-literal. This error is
398-
// caused by incorrect expressions such as:
399-
// ```
400-
// repl> let x = { y = 1; }; in { a = x; a.z = 2; }
401-
// error: attribute 'a.z' at (string):1:33 already defined at (string):1:26
402-
// ```
403-
// The above would be caught because `x` is an ExprSource::Ident (as
404-
// opposed to being an ExprSource::AttrSet literal).
405-
Err(EvalError::Value(ValueError::AttrAlreadyDefined(name.to_string())))
422+
match &src.source {
423+
ExprSource::AttrSet { .. } => src.eval()?.as_map(),
424+
ExprSource::Literal {
425+
value: NixValue::Map(m),
426+
..
427+
} => Ok(m.clone()),
428+
_ => {
429+
// We cannot merge a literal with a non-literal. This error is
430+
// caused by incorrect expressions such as:
431+
// ```
432+
// repl> let x = { y = 1; }; in { a = x; a.z = 2; }
433+
// error: attribute 'a.z' at (string):1:33 already defined at (string):1:26
434+
// ```
435+
// The above would be caught because `x` is an ExprSource::Ident (as
436+
// opposed to being an ExprSource::AttrSet literal).
437+
Err(EvalError::Value(ValueError::AttrAlreadyDefined(
438+
name.to_string(),
439+
)))
440+
}
406441
}
407442
};
408443

src/tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use serde_json::json;
1414
use std::time::Duration;
1515
#[cfg(test)]
1616
use stoppable_thread::*;
17+
#[cfg(test)]
18+
use crate::error::ValueError;
1719

1820
#[allow(dead_code)]
1921
/// Evaluates a nix code snippet
@@ -294,13 +296,31 @@ fn attrs_merge() {
294296
assert_eq!(eval(&format!("{}.a.c", code)).as_int().unwrap(), 2);
295297
}
296298

299+
#[test]
300+
fn attrs_merge_3() {
301+
let code = "{ a.b = 1; a.c = 2; a.d = 3; }".to_string();
302+
assert_eq!(eval(&format!("{}.a.b", code)).as_int().unwrap(), 1);
303+
assert_eq!(eval(&format!("{}.a.c", code)).as_int().unwrap(), 2);
304+
assert_eq!(eval(&format!("{}.a.d", code)).as_int().unwrap(), 3);
305+
}
306+
297307
#[test]
298308
fn attrs_merge_conflict() {
299309
let ast = rnix::parse("{ a = { b = 1; c = 3; }; a.c = 2; }");
300310
let root = ast.root().inner().unwrap();
301311
let path = std::env::current_dir().unwrap();
302312
let parse_result = Expr::parse(root, Gc::new(Scope::Root(path)));
313+
assert!(matches!(parse_result, Err(EvalError::Value(ValueError::AttrAlreadyDefined(_)))));
314+
}
315+
316+
#[test]
317+
fn attrs_merge_conflict_2() {
318+
let ast = rnix::parse("{ a = let y = { b = 1; }; in y; a.c = 2; }");
319+
let root = ast.root().inner().unwrap();
320+
let path = std::env::current_dir().unwrap();
321+
let parse_result = Expr::parse(root, Gc::new(Scope::Root(path)));
303322
assert!(parse_result.is_err());
323+
// assert!(matches!(parse_result, Err(EvalError::Value(ValueError::AttrAlreadyDefined(_)))));
304324
}
305325

306326
#[test]

0 commit comments

Comments
 (0)