Error: 'dynamic attribute already defined' when incrementally building nested structures with dynamic paths #707
Labels
No labels
Affects/CppNix
Affects/Nightly
Affects/Only nightly
Affects/Stable
Area/build-packaging
Area/cli
Area/evaluator
Area/fetching
Area/flakes
Area/language
Area/lix ci
Area/nix-eval-jobs
Area/profiles
Area/protocol
Area/releng
Area/remote-builds
Area/repl
Area/repl/debugger
Area/store
bug
Context
contributors
Context
drive-by
Context
maintainers
Context
RFD
crash 💥
Cross Compilation
devx
docs
Downstream Dependents
E/easy
E/hard
E/help wanted
E/reproducible
E/requires rearchitecture
imported
Language/Bash
Language/C++
Language/NixLang
Language/Python
Language/Rust
Needs Langver
OS/Linux
OS/macOS
performance
regression
release-blocker
stability
Status
blocked
Status
invalid
Status
postponed
Status
wontfix
testing
testing/flakey
Topic/Large Scale Installations
ux
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lix-project/lix#707
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Describe the bug
Inconsistent handling of attribute paths containing dynamic components (
${...}
): When an attribute path includes a dynamic component, Nix treats each occurrence as a complete redefinition of that attribute, rather than merging nested attributes as it does with static attribute paths. This makes it impossible to build up nested structures incrementally when any part of the path is dynamic.This appears to be a limitation in Nix's handling of dynamic attribute paths that prevents a common pattern that works fine with static paths.
Steps To Reproduce
The issue is with how Nix handles dynamic attribute paths (
${...}
) at any level in an attribute set, compared to static attribute paths.repro-first.nix
(not shown but error referenced):repro-second.nix
:fine.nix
(works correctly):Expected behavior
I would have expected there to be no difference in evaluation of static and dynamic attribute paths and that it changes nothing how deep attribute sets are merged.
nix --version
outputAdditional context
Please request any additional context that could help you debug this.
The other nix implementation
shows the same errors, so this is not just a lix issue it seems.
that's a reasonable assumption, but unfortunately it's impossible. the current behavior is best understood as a elementwise set merge with disjointness checking. this is far from the only case of dynamic attributes being completely fucked up; consider
(a: rec { b = a; ${a} = "2"; }) "a"
.at this point we personally are more likely to throw out dynamic attributes completely in a language revision than do anything with them except emit a warning when they're used, they're such a mine field.
@pennae
nix repl
gives me this answer:Which seems to be exactly what I would expect from an imperative language?
b
is defined to the value ofa
- at that time that is the variable from outside the rec attrseta
is defined to be"2"
I am guessing that the way rec is defined plus laziness, you would have expected the result to be:
nixlang is not imperative though.
rec
is supposed to add add attributes of the current set to the scope of values inside the set, but for dynamic attributes this only happens in one direction: dynamics see statics, but not the other way around. this is why dynamic attributes have been forbidden inlet
blocks since forever, and it's also why we can't meaningfully fix anything about dynamic attributes :(the merges you're talking about are unfixable without breaking the language. consider
merging of statics is side-effect-free. merging of dynamics cannot be, and must thus not be done.
Well, I guess I understand that the nix language does not lend itself to fixing this inconsistency. Would it perhaps make sense to explain this problem in the error message shown or link to a page that explains that in more details? Because from the outside it just looks like there is a bug in nix/lix.
sure, error messages can always be improved! if you have a good idea on how to make them less confusing we're always happy the review CLs to make UX (or documentation) less horrible :)
What do you think of something along the lines of:
That's very similar to what sqlalchemy does, which I like a lot, as the target page can be evolved independently from the deployed application.
that feels a bit wrong somehow. more information is good, but more information as a pure "go to this website" is a bit ornery. it'd be great if we could give that error a unique error number that's easily looked up in the manual, and also link to the manual. giving a more complete explanation of why merging isn't happening in the actuall error message would be even better though; lix error traces are already infuriating and asking users to get out a browser to see the full message won't make that less so :(
we'd say
the dynamic attribute documentation in the manual could also use a rewrite. the entire manual could also use a rewrite. 🫠
:-) Yeah... Much could…
I do like the error message you proposed, it's a simple statement of fact. Not why, but that is something the manual could (in a later rewrite) do.
note there's definitely other complaints about attr merging behaviour on the tracker, i think at least one of them is tagged "needs langver". so it seems like this is a common confusion.
I would love error codes to happen. if you want to help out, a well written docs page describing the attr merging could be put into a section on error messages and it could be a start on the error codes project. docs writing is a big gap area in the project.
Dynamic attributes are on my sniping list for deprecation, especially in the more cursed edge cases involving
rec
. Actually fixing things unfortunately requires a langver bump most of the time, but throwing out syntax with unfortunate semantics is always an option