libexpr: Don't call setName on dynamic attrs

And also document in great detail why this is a wrong thing to do

Change-Id: Ifd1331ee7ee4e05322593ada801bab1c3ea8d349
This commit is contained in:
piegames
2026-03-23 14:47:39 +01:00
parent 93edf577b7
commit d04fcb57fd
2 changed files with 27 additions and 1 deletions
+23 -1
View File
@@ -187,7 +187,29 @@ Value ExprSet::eval(EvalState & state, Env & env)
.debugThrow();
}
i.valueExpr->setName(nameSym);
// clang-format off
/* This line is so wrong that it is best kept in here with the documentation why it is wrong,
* lest some naive soul may add it once again some year in the future.
* See the following witness as to why it is wrong:
*
* nix-repl> fun = (name: { ${name} = x: x; }) # This function creates a dynamic attribute with a variable name
* Added fun.
* nix-repl> revSeq = x: y: builtins.seq x (builtins.seq y x) # evaluate x, then y in sequence, then return x
* Added revSeq.
* nix-repl> fun "foo" # The code seemingly works
* { foo = «lambda foo @ «string»:1:26»; }
* nix-repl> fun "bar" #
* { bar = «lambda bar @ «string»:1:26»; }
* nix-repl> revSeq (fun "foo") (fun "bar") # Until it doesn't
* { foo = «lambda bar @ «string»:1:26»; }
*
* What happened? Expressions are AST bound, therefore all lambdas share the same Expr and thus *the same name*.
* Using `setName` here updates the name of *all* lambdas from that expression, not just of the value at hand.
* And this is why all expressions must be treated as immutable after parsing.
*/
/* i.valueExpr->setName(nameSym); */
// clang-format on
/* Keep sorted order so find can catch duplicates */
v.attrs()->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
v.attrs()->sort(); // FIXME: inefficient
+4
View File
@@ -130,6 +130,10 @@ public:
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
virtual Value eval(EvalState & state, Env & env);
virtual Value maybeThunk(EvalState & state, Env & env);
/* Lambdas have a name associated with them, when they are declared in a binding:
* `identity = x: x` will print the resulting value as `«lambda identity @ «string»:1:14»`.
* This is set in the parser. After parsing, all expressions are immutable.
*/
virtual void setName(Symbol name);
PosIdx getPos() const { return pos; }