16 May 2025 |
SomeoneSerge (Ever OOMed by Element) | * Sounds like __result is the least wrong? | 20:50:55 |
SomeoneSerge (Ever OOMed by Element) | H'm what if freeformType s 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 (Ever OOMed by Element) | 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 (Ever OOMed by Element) | Awe and horror | 21:41:09 |
SomeoneSerge (Ever OOMed by Element) | 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 (Ever OOMed by Element) | * 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 (Ever OOMed by Element) | * 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 throw s 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 (Ever OOMed by Element) |
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 (Ever OOMed by Element) | 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 (Ever OOMed by Element) |
Here's what I meant by freeformType is an option:
Was my second guess. Thanks for walking me through these!
| 22:19:58 |
SomeoneSerge (Ever OOMed by Element) | 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 (Ever OOMed by Element) | 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 check s, "modules" are things that produce options and config s, but options are confusing | 23:17:40 |
SomeoneSerge (Ever OOMed by Element) | It's not possible to extend options._module.freeformType with an apply from outside? | 23:18:12 |
SomeoneSerge (Ever OOMed by Element) | * It's not possible to extend options._module.freeformType with an apply from outside is it? | 23:18:15 |
SomeoneSerge (Ever OOMed by Element) | * 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 check s, "modules" are things that produce options and config s, but "options" are confusing | 23:18:43 |
Matt Sturgeon | Yes, there's a few "types" of thing in the module system:
configuration is the final evaluated module eval, returned by evalModules . It contains _type , options, config, type` (a submodule option-type), etc
module these are the "modules" the module system evaluates. Can be a long-form or short-hand attrset, a function returning one, or a file containing one.
options these describe the attrs that can be defined in the configuration. Declared using options.foo = mkOption {} , defined using config.foo = "value" (or the short-hand syntax foo = "value" ).
option-types these are types that options can declare they have. This is where type checking and definition merging is done.
Submodules are an option-type representing another configuration . The submodule type expects the option definitions to be modules , and it merges those modules using evalModules and uses the configuration's config as its final value.
| 23:24:13 |
Matt Sturgeon | I'm not sure. Usually merging things relates to having multiple definitions for an option, but the module system does also support merging multiple declarations for an option too, in some scenarios.
You may be able to declare options._module.freeformType = mkOption { apply = x: x; } , but IDK why you would want to do that... ?
| 23:26:47 |
SomeoneSerge (Ever OOMed by Element) | Ah right, I was trying ...freeformType.apply = instead, but I actually knew this bit. But I realized I don't actually need apply in freeformType : I was trying to reproduce the same trick at the top-level instead of at foo. | 23:35:26 |