| 16 May 2025 |
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 (void) | Sounds like __result is least wrong? | 20:50:51 |
SomeoneSerge (void) | * Sounds like __result is the least wrong? | 20:50:55 |
SomeoneSerge (void) | H'm what if freeformTypes are different? attrsOf a and attrsOf b | 20:53:42 |
Matt Sturgeon | I believe the freeform type can be conditional on a marker option being defined without infinite recursion. | 20:58:27 |
Matt Sturgeon | So long as the marker options are explicitly defined, they are resolved before freeform definitions | 20:59:14 |
SomeoneSerge (void) | Not sure I should depend on this detail 😅 | 20:59:19 |
Matt Sturgeon | * So long as the marker options are explicitly declared, they are resolved before freeform definitions | 20:59:26 |
Matt Sturgeon | freeformType is itself an option (config._module.freeformType), so it's not really an implementation detail | 21:00:17 |
Matt Sturgeon | (
{ options, ... }:
{
freeformType = attrsOf (
if options.marker_a.isDefined && options.marker_b.isDefined then
throw "both `${options.marker_a}' and `${options.marker_b}' are defined"
else if options.marker_a.isDefined then
a
else if options.marker_b.isDefined then
b
else
throw "neither `${options.marker_a}' or `${options.marker_b}' are defined"
);
}
)
| 21:06:01 |
Matt Sturgeon | _Note: stringifying an option will use it's showOption opt.loc location, so you'll automatically get the full option prefix, e.g. "foo.marker_b" | 21:07:27 |
Matt Sturgeon | * Note: stringifying an option will use it's showOption opt.loc location, so you'll automatically get the full option prefix, e.g. "foo.marker_b" | 21:07:39 |
SomeoneSerge (void) | Awe and horror | 21:41:09 |
SomeoneSerge (void) | I suspect this works and then gets rejected by some kind of a final type check: error: The option marker_a' was accessed but has no value defined. Try setting the option` | 22:04:36 |
SomeoneSerge (void) | * I suspect this works and then gets rejected by some kind of a final type check: error: The option `marker_a' was accessed but has no value defined. Try setting the option | 22:04:51 |
SomeoneSerge (void) | * I suspect this works and then gets rejected by some kind of a final type check: error: The option `marker_a' was accessed but has no value defined. Try setting the option
(my attempt: https://gist.github.com/SomeoneSerge/29a9e6e10cdcd3596f865c41c7042610)
| 22:05:59 |
Matt Sturgeon | That's not a type check, that's an "empty value" stub. When an option has no definitions, its value is set to a stub that throws that error.
You're probably reading the value somewhere, e.g. using config.marker_a instead of options.marker_a, or not using removeAttrs when creating __result?
| 22:06:40 |
Matt Sturgeon | On line 11 you assign freeformType to a submodule, but freeformType must be an attrsOf type. | 22:12:06 |
Matt Sturgeon | On line 45 you're defining apply which is meaningless in a module, it's a feature of options.
If you're evaluating these directly with lib.evalModules instead of using a submodule option, you can do the apply manually:
(lib.evalModules { /* ... */ }).config.__result
| 22:14:08 |
SomeoneSerge (void) |
On line 45 you're defining apply which is meaningless in a module, it's a feature of options.
Yeah I was wondering if I misunderstood the "freeformType is an option"
| 22:15:13 |
SomeoneSerge (void) | Whoops, the snippet is screwed then I guess | 22:15:50 |
Matt Sturgeon | Here's what I meant by freeformType is an option: https://github.com/NixOS/nixpkgs/blob/ec56f7ae8cdc39eebb8a3b4aa7a3a6365c214fb5/lib/modules.nix#L213-L227
You can set it explicitly in a submodule by defining e.g. config._module.freeformType = attrsOf str, but the module system allows a "shorthand" syntax where freeformType can be defined as a top-level attr in a module and it'll do the conversion to assigning the option for you.
| 22:17:34 |
SomeoneSerge (void) |
Here's what I meant by freeformType is an option:
Was my second guess. Thanks for walking me through these!
| 22:19:58 |
SomeoneSerge (void) | Does work with mkOption indeed: https://gist.github.com/SomeoneSerge/b278317b83e5090fbfa9472c78701703 | 22:51:20 |
Matt Sturgeon | Awesome!
I'd highly recommend checking the marker_a.isDefined && marker_b.isDefined case too, which I assume should be an error?
The else case where neither are defined may need some special handling too: is it an error? Is it still an error if cfg is empty?
| 22:56:09 |
SomeoneSerge (void) | Yes, an error. Still struggling a bit conceptually though, seems like there are three different kinds of entities in evalModules: "types" are things that have checks, "modules" are things that produce options and configs, but options are confusing | 23:17:40 |
SomeoneSerge (void) | It's not possible to extend options._module.freeformType with an apply from outside? | 23:18:12 |
SomeoneSerge (void) | * It's not possible to extend options._module.freeformType with an apply from outside is it? | 23:18:15 |
SomeoneSerge (void) | * Yes, an error. Still struggling a bit conceptually though, seems like there are three different kinds of entities in evalModules: "types" are things that have checks, "modules" are things that produce options and configs, but "options" are confusing | 23:18:43 |