16 Apr 2025 |
| Alexandru Tocar joined the room. | 11:47:32 |
18 Apr 2025 |
| @cirnolovetech:matrix.org joined the room. | 01:05:05 |
| fabaff joined the room. | 18:03:01 |
19 Apr 2025 |
| jopejoe1 (4094@GPN23) changed their display name from jopejoe1 to jopejoe1 (4094@eh22). | 13:01:33 |
| ·☽•Nameless☆•777 · ± changed their profile picture. | 14:32:34 |
| @accelbread:matrix.org left the room. | 20:30:31 |
22 Apr 2025 |
| @cirnolovetech:matrix.org left the room. | 09:31:37 |
25 Apr 2025 |
| @creepinson:matrix.org joined the room. | 23:38:39 |
29 Apr 2025 |
| n8henrie joined the room. | 18:04:14 |
1 May 2025 |
| Rosuavio changed their display name from Rosario Pulella to Rosuavio. | 20:08:44 |
8 May 2025 |
| isabel changed their profile picture. | 08:57:53 |
| isabel changed their profile picture. | 08:57:59 |
| ·☽•Nameless☆•777 · ± changed their profile picture. | 13:26:34 |
16 May 2025 |
SomeoneSerge (Ever OOMed by Element) | I never acquainted with types.nix , is there some fundamental reason that either of two freeform submodules couldn't work when left and right have disjoint explicit options ?
with import <nixpkgs/lib>;
evalModules {
modules = [
{
options.foo = mkOption {
type =
types.either
(types.submodule {
freeformType = types.attrsOf types.str;
options.marker_a = mkOption {
type = types.str;
};
})
(
types.submodule {
freeformType = types.attrsOf types.str;
options.marker_b = mkOption {
type = types.str;
};
}
);
};
config.foo = {
marker_b = "bar";
};
}
];
}
{ foo = { marker_a = «error: The option `foo.marker_a' was accessed but has no value defined. Try setting
the option.»; marker_b = "bar"; }; }
| 19:49:57 |
Matt Sturgeon | I believe the issue is the left and right type's check function.
either will use whichever type fist passes check value , and iirc submodules just use something like check = isAttrs
| 19:52:25 |
h7x4 | Some of the "container types" don't really check what's inside before being evaluated at a later stage. I believe this is the case for submodules, listOf and attrsOf . As a hack, you can add those checks with lib.types.addCheck . Same scenario with either and oneOf . | 19:53:46 |
Matt Sturgeon | It's intentional that the submodule-type's check function is not very restrictive, since you can assign any module to a submodule and it is down to the submodule's configuration to evaluate and merge its own definitions internally. | 19:53:58 |
Matt Sturgeon | E.g. foo = {} is a vaild definition, as is foo = { config, ... }: { } , as is foo = ./someModule.nix | 19:54:55 |
h7x4 | nix-repl> with lib.types; (listOf int).check [ true false "hello" ]
true
nix-repl> with lib.types; (addCheck (listOf int) (builtins.all builtins.isInt)).check [ true false "hello" ]
false
| 19:57:21 |
Matt Sturgeon | The other issue with marker options (marker_a , marker_b ) is how should definitions be merged that don't include the marker? E.g.
foo = lib.mkMerge [
{ something_freeform = "hi"; }
{ marker_a = "there"; }
]
You don't know which marker is defined until all merging is done.
In a simple case like this, you could refactor this as one submodule with both markers, and then add some extra logic/conditions within the submodule and/or in the optinon's final apply function.
| 20:02:16 |
h7x4 | Also ref https://discourse.nixos.org/t/problems-with-types-oneof-and-submodules/15197 | 20:03:59 |
Matt Sturgeon | One way you could work around this is to add an internal final option within the submodule and then map to it in the apply function:
foo = mkOption {
type = types.submodule (
{ config, options, ... }:
{
freeformType = types.attrsOf types.str;
options.marker_a = mkOption {
type = types.str;
};
options.marker_b = mkOption {
type = types.str;
};
options.__result = mkOption {
type = config._module.freeformType;
internal = true;
};
config.__result =
let
cfg = builtins.removeAttrs config [
"_module"
"marker_a"
"marker_b"
"__result"
];
in
if options.marker_a.isDefined then
cfg // { inherit (config) marker_a; } # TODO
else if options.marker_b.isDefined then
cfg // { inherit (config) marker_b; } # TODO
else
cfg # TODO: (empty? no marker?)
;
}
);
apply = value: value.__result;
}
| 20:16:10 |
SomeoneSerge (Ever OOMed by Element) | Yeah, thought about that. One'd need access to all defs at once... | 20:18:26 |
SomeoneSerge (Ever OOMed by Element) | H'm, do I get a merged value as an input if I addCheck (submodule ...) (def: def?marker_a) ? I just tried adding { foo = { }; } before and after { foo.marker_b = ...; } and it still seems to work | 20:28:37 |
h7x4 | I believe the attrs are merged before being typechecked. I suppose you could try adding a lib.trace inside the typecheck to verify? | 20:32:31 |
SomeoneSerge (Ever OOMed by Element) | trace: { below = "_"; }
trace: { marker_b = "bar"; }
trace: { above = "_"; }
trace: { below = "_"; }
{ foo = { above = "_"; below = "_"; marker_b = "bar"; }; }
For modules:
{ foo = { above = "_"; }; }
{
foo.marker_b = "bar";
}
{ foo = { below = "_"; }; }
| 20:37:22 |
SomeoneSerge (Ever OOMed by Element) | * trace: { below = "_"; }
trace: { marker_b = "bar"; }
trace: { above = "_"; }
trace: { below = "_"; }
{ foo = { above = "_"; below = "_"; marker_b = "bar"; }; }
For modules:
{ foo = { above = "_"; }; }
{
foo.marker_b = "bar";
}
{ foo = { below = "_"; }; }
| 20:37:32 |
Matt Sturgeon | This won't support foo = { config,...}: { marker_b = "bar"; } .
Type checking is done before merging, but after resolving mkIf (etc) wrappers.
Type checking is used to determine which type to use for merging. | 20:39:18 |
h7x4 | Aha, that makes sense 👍️ | 20:41:25 |
SomeoneSerge (Ever OOMed by Element) | Sounds like __result is least wrong? | 20:50:51 |