| 18 Nov 2024 |
nbp | Oh … how did I missed it …
Properties are evaluated at options, not after, which is why using the value { bar = lib.mkForce {}; } and the value { bar.baz = lib.mkForce {}; } appears in the result, and that we can have one override the other as the update operator defines. Simply because properties are not evaluated, but the value are simply merged.
nix-repl> opt = { options.foo = lib.mkOption { type = lib.types.attrs; }; }
nix-repl> d0 = { foo = lib.mkForce { bar = {}; }; }
nix-repl> d1 = { foo = lib.mkForce { bar.baz = {}; }; }
nix-repl> :p (lib.evalModules { modules = [ opt d0 d1 ]; }).config
{ foo = { bar = { }; }; }
nix-repl> :p (lib.evalModules { modules = [ opt d1 d0 ]; }).config
{ foo = { bar = { baz = { }; }; }; }
Which does not have the priority leaking in the value of the option.
| 12:58:17 |
frontear | oh my it was so obvious too | 21:38:52 |
frontear | thanks for the response thats honestly somewhat embarrassing that i didnt catch that earlier | 21:39:14 |
frontear | because like you said, the priority being there was a big sign | 21:39:26 |
| 19 Nov 2024 |
ibizaman | In reply to @frontear:matrix.org thanks for the response thats honestly somewhat embarrassing that i didnt catch that earlier Hey sometimes you’re too much in the weeds, seeing the big picture becomes hard. Definitely happened to me more than once. | 12:39:21 |
| 20 Nov 2024 |
| Inayet removed their profile picture. | 00:59:38 |
| 21 Nov 2024 |
| Jason Odoom changed their profile picture. | 02:51:47 |
| oddlama joined the room. | 13:46:18 |
| 25 Nov 2024 |
| NullCube joined the room. | 10:00:58 |
| 27 Nov 2024 |
| jopejoe1 (4094@39c3) changed their display name from jopejoe1 to jopejoe1 [4094]. | 18:19:03 |
| 6 Dec 2024 |
| cafkafk 🏳️⚧️ changed their profile picture. | 03:43:51 |
| 11 Dec 2024 |
| dminca left the room. | 14:19:11 |
| 12 Dec 2024 |
| ·☽•Nameless☆•777 · ± changed their profile picture. | 14:34:25 |
| 13 Dec 2024 |
| getchoo joined the room. | 09:28:33 |
getchoo | Hi all
I've come across a fun problem recently. I maintain a set of modules, and previously I had an option in a submodule, but now I've made it a regular (top-level) option. Per usual with changes like this, I wanted to use mkRenamedOptionModule to alias that option submodule option into the top-level option, but I got a nasty surprise with a cannot find attribute <old attribute name> error message
After doing some digging, I'm assuming this is because submodules sort of strip out the regular option data of their inner values, and only expose the fully evaluated configuration. Am I right here? Is there a good way around this?
Here's a minimum repro:
# repro.nix
let
lib = import <nixpkgs/lib>;
in
lib.evalModules {
modules = [
{
options = {
# Create a submodule option
aSubmodule = lib.mkOption {
type = lib.types.submodule {
options = {
# With an inner option
someSubmoduleOption = lib.mkEnableOption "something";
};
};
default = { };
};
# And a top-level option
aRegularOption = lib.mkEnableOption "something";
};
}
# Alias them
(lib.mkRenamedOptionModule [ "aSubmodule" "someSubmoduleOption" ] [ "aRegularOption" ])
{
# Test the alias
# `nix eval --file repro.nix config.aRegularOption`
aSubmodule.someSubmoduleOption = true;
# ...but instead you get
# error: evaluation aborted with the following error message: 'cannot find attribute `aSubmodule.someSubmoduleOption''
}
];
}
| 09:33:27 |
getchoo | * Hi all
I've come across a fun problem recently. I maintain a set of modules, and previously I had an option in a submodule, but now I've made it a regular (top-level) option. Per usual with changes like this, I wanted to use mkRenamedOptionModule to alias that submodule option into the top-level option, but I got a nasty surprise with a cannot find attribute <old attribute name> error message
After doing some digging, I'm assuming this is because submodules sort of strip out the regular option data of their inner values, and only expose the fully evaluated configuration. Am I right here? Is there a good way around this?
Here's a minimum repro:
# repro.nix
let
lib = import <nixpkgs/lib>;
in
lib.evalModules {
modules = [
{
options = {
# Create a submodule option
aSubmodule = lib.mkOption {
type = lib.types.submodule {
options = {
# With an inner option
someSubmoduleOption = lib.mkEnableOption "something";
};
};
default = { };
};
# And a top-level option
aRegularOption = lib.mkEnableOption "something";
};
}
# Alias them
(lib.mkRenamedOptionModule [ "aSubmodule" "someSubmoduleOption" ] [ "aRegularOption" ])
{
# Test the alias
# `nix eval --file repro.nix config.aRegularOption`
aSubmodule.someSubmoduleOption = true;
# ...but instead you get
# error: evaluation aborted with the following error message: 'cannot find attribute `aSubmodule.someSubmoduleOption''
}
];
}
| 09:33:54 |
infinisil | @getchoo:matrix.org Can't see a way around that problem tbh! Seems like you'll have to manually set it up
| 09:55:07 |
nbp | The problem likely comes from the fact that renamed options aggregate the values from the old definitions, ... Except that we have not yet defined a standard way to gather for 'any' type of submodules. Thus we do not have a set of option sets to work with.
Submodules no longer expose the option set as part of their results. If you were to add it back locally, It might be possible to extend the doRename function to work with a set of options. | 10:20:01 |
getchoo | Thanks for looking into it and the explanation. Maybe it's something I'll look into working on once I'm more familiar with some of the internals here
This was actually my first time looking at the impl of stuff like doRename 😆 | 22:59:21 |
| 14 Dec 2024 |
Matt Sturgeon | Don't rename modules usually expect the alias-option to not exist; i.e. doRename is responsible for creating the other option ? Even if so, I think that's unrelated.
I've definitely made renames into submodules from outside before (we do this for 50% of nixvim lol), but IIRC making renames out from submodules runs into issues because options.** doesn't exist. Specifically, you run into a _type = "option" object at the submodule-option boundary, options.aSubmodule in the above example.
In nixvim I experimented with resolving the option path recursively, using type.getSubOptions. That was for removal modules, but similar principal.
(This would be (options.aSubmodule._type.getSubOptions options.aSubmodule.loc).someSubmoduleOption in the prior example)
It's also been a while since I looked at the linked code, and IIRC some stuff in that file is broken. Probably the unrelated tryEval hack 😅
| 04:48:16 |
| 17 Dec 2024 |
getchoo | Came back to the problem tonight before checking this room again. Seems I came up with a pretty similar solution lol
# repro.nix
let
lib = import <nixpkgs/lib>;
budgetIsDefined = value: (builtins.tryEval value).success;
in
lib.evalModules {
modules = [
{
options = {
# Create a submodule option
aSubmodule = lib.mkOption {
type = lib.types.submodule {
options = {
# With an inner option
someSubmoduleOption = lib.mkEnableOption "something" // {
# Add some warning stuff
description = "Alias of `aRegularOption`";
apply = lib.trace "Obsolete option `aSubmodule.someSubmoduleOption` is used. It was renamed to `aRegularOption`.";
visible = false;
};
};
};
default = { };
};
# Then add a top-level option
aRegularOption = lib.mkEnableOption "something";
};
}
# Do the actual aliasing
(
{ config, ... }:
{
# We only want to do any of this if the alias option is used
config = lib.mkIf (budgetIsDefined config.aSubmodule.someSubmoduleOption) {
aRegularOption = config.aSubmodule.someSubmoduleOption;
};
}
)
{
# Now test the alias
# `nix eval --file repro.nix config.aRegularOption`
aSubmodule.someSubmoduleOption = true;
}
];
}
The tryEval hack also has a bit of a footgun with type checking though, as incorrect definition errors are eaten up in the same way as when there are no definitions. Don't think there is any way to get more context out of primitive throws though, so this will probably need to be good enough ™️
| 02:01:44 |
getchoo | And seriously, thanks for linking that deprecations file. If I ever need to do this more, I'll totally have to borrow some stuff from there 👍️ | 02:03:28 |
Matt Sturgeon | I really don't recommend using tryEval in this way... It was very situational in nixvim, and even then still probably wasn't the best solution. As you've pointed out, it has plenty of downsides.
I would recommend trying to find the option by recursively walking the "option attr path" (aka loc), checking each attr for lib.isOption and if true, continuing with opt.type.getSubOptions opt.loc.
getOptionRecursive was the part of the file I was hoping you would take inspiration from 😅
Once you have the actual (sub)option you can make use of opt.isDefined with no hacks needed.
| 09:49:16 |
| 20 Dec 2024 |
| 🐰 xiaoxiangmoe joined the room. | 13:59:03 |
| 21 Dec 2024 |
| stablejoy left the room. | 05:08:18 |
| sleepymonad joined the room. | 21:16:14 |
| ·☽•Nameless☆•777 · ± changed their profile picture. | 21:37:38 |
| sleepymonad set a profile picture. | 21:56:33 |
| 22 Dec 2024 |
| allrealmsoflife joined the room. | 20:27:50 |
| 26 Dec 2024 |
| Lorenz Leutgeb changed their display name from Lorenz Leutgeb to Lorenz Leutgeb (📞6343). | 19:33:43 |