!wfudwzqQUiJYJnqfSY:nixos.org

NixOS Module System

204 Members
47 Servers

Load older messages


SenderMessageTime
29 May 2026
@mattsturg:matrix.orgMatt Sturgeon

For the purposes of discovering declared options, type.getSubOptions is usually suitable.

When defining a submodule (with a module), that module can declare additional options for use within the submodule. However, if you only care about options declared by modules available when the type was instantiated, then opt.type.getSubOptions opt.loc is perfectly valid.

11:48:22
@mattsturg:matrix.orgMatt Sturgeon *

For the purposes of discovering declared options, type.getSubOptions is usually "good enough".

When defining a submodule (with a module), that module can declare additional options for use within the submodule. However, if you only care about options declared by modules available when the type was instantiated, then opt.type.getSubOptions opt.loc is perfectly valid.

11:49:12
@mattsturg:matrix.orgMatt Sturgeon

You may find it easier to actually leverage the doc's tooling and use lib.options.optionAttrSetToDocList to recursively collect a list of declared options:

$ nix repl
Nix 2.34.7
Type :? for help.

nix-repl> lib = import ./lib

nix-repl> :a import ./nixos { configuration = {}; }
Added 6 variables.
config, options, pkgs, system, vm, vmWithBootLoader

nix-repl> :p builtins.head (lib.options.optionAttrSetToDocList options)
{
  declarations = [ "lib/modules.nix" ];
  default = {
    _type = "literalExpression";
    text = "{ }";
  };
  description = <omitted for brevity>;
  internal = false;
  loc = [
    "_module"
    "args"
  ];
  name = "_module.args";
  readOnly = false;
  type = "lazy attribute set of raw value";
  visible = true;
}
11:58:50
@toonn:matrix.orgtoonn I mentioned the problem with optionAttrSetToDocList that I ran into, it means having to parse the types out of strings. 12:17:09
@toonn:matrix.orgtoonn With getSubOptions I just worry about running into a similar limitatation since it seems to also be targeted at documentation generation. 12:17:45
@toonn:matrix.orgtoonn I'd also still have to dig for the freeformType because _freeformOptions in getSubOptions does not (maybe not always?) include the information I need, it was {} when I was looking at a submodule with a freeformType that allows JSON. 12:20:32
@mattsturg:matrix.orgMatt Sturgeon

The core of optionAttrsetToDocList that you can take inspiration from is:

concatMap (opt: [opt] ++ opt.type.getSubOptions opt.loc) (collect isOption options)

There are some additional option-visibility checks, partly to avoid infinite tree-recursion, partly to respect docs-visibility.

While getSubOptions is primarily intended for docs-generation, and optionAttrsetToDocList is explicitly for docs generation, you can still use getSubOptions and a similar recursive option collection pattern provided you're aware of the limitations of getSubOptions we discussed before (base-configuration from type instantiation vs final-merged configuration from valueMeta).

The options returned by getSubOptions are complete options, with full type access. It essentially just returns baseConfiguration.options (extended only to add the prefix to option loc paths).

12:24:20
@toonn:matrix.orgtoonn OK, ill consider using getSubOptions. But that still leaves freeformType. 12:27:20
@mattsturg:matrix.orgMatt Sturgeon

What do you need to know about freeformType? Note that types.submodule makes it available as type.nestedTypes.freeformType (here). You're still relying on wrapper types to propagate their wrapped-types' nestedTypes though.

Luckily, freeformType is actually just the _module.freeformType option, so you can also read it from options._module.freeformType.value, which is available from getSubOptions if it was defined at type-instantiation time:

nix-repl> opt = options.services.pixelfed.nginx

nix-repl> opts = opt.type.getSubOptions opt.loc

nix-repl> opts._module.freeformType.value
null
12:34:16
@toonn:matrix.orgtoonn That's null because it's a nullOr submodule, no? 12:38:20
@mattsturg:matrix.orgMatt Sturgeon it's a nullOr deferredType. In this case I assume it's null because the services.pixelfed.nginx's submodule-type isn't freeform. 12:39:24
@toonn:matrix.orgtoonn You're right, that one's not freeform. 12:40:24
@mattsturg:matrix.orgMatt Sturgeon * it's a nullOr optionType. In this case I assume it's null because the services.pixelfed.nginx's submodule-type isn't freeform. 12:40:29
@toonn:matrix.orgtoonn OK, so if I want to treat things uniformly getSubOptions might be easier than figuring out where in the functor payloads the information can be found. I'll be trying that and probably run into more questions : ) Thank you! 12:42:43
@mattsturg:matrix.orgMatt Sturgeon

where in the functor payloads the information can be found

the final merged configuration isn't visible to the functor. Because it only exists after merging option definitions. Option definitions can only exist once a type is used with an option.

12:43:46
@mattsturg:matrix.orgMatt Sturgeon *

where in the functor payloads the information can be found

the final merged configuration isn't visible to the functor. Because it only exists after merging option definitions. Option definitions can only exist once a type is used with an option.
So if you must do this from the type's perspective, the base-configuration (pre option-merge) is all you have access to.

12:44:14
@toonn:matrix.orgtoonn I'm starting from (nixos {}).options.services.<module>, IIUC those are merged option definitions, just may not be complete yet, missing some required definitions? 12:46:26
@mattsturg:matrix.orgMatt Sturgeon

The functor you referred to is options.services.<module>.type.functor, right? Note that you've stepped from the option (options.services.<module>) into its type .type. The type's functor is defined when the type is created, before any options are in the picture.

The option itself has access to it's final merged value (options.services.<module>.value) and if its type supports v2 check+merge, you may also have merged metadata (options.services.<module>.valueMeta). Note how none of that is under .type.

Once you return options.services.<module>.type.getSubOptions options.services.<module>.loc you have a new set of options that came from the type's base configuration; you've lost all knowledge of the owner option, its definitions, merged value, or merged metadata.

12:50:48
@toonn:matrix.orgtoonn Except for the `loc` no? 12:52:22
@mattsturg:matrix.orgMatt SturgeonSure, but that's just a list of strings, not an option.12:52:41
@toonn:matrix.orgtoonn I don't think I have a need for that information once I recurse, since what I return will be under the <module> or whatever nested option. 12:53:17
@mattsturg:matrix.orgMatt Sturgeon * Sure, but that's just a list of strings, not an option. It's useful only for properly prefixing the sub-options' own locs, for user-facing clarity. 12:53:20
@toonn:matrix.orgtoonn getSubOptions doesn't return a flat structure of all nested options, right? So I don't need to sort them by loc to regain the structure? 12:55:03
@mattsturg:matrix.orgMatt Sturgeon

I always recommend trying to keep the prefix accurate, so that once you have your final list of options you can do "${opt}" or showOption opt.loc and get the correct option attrpath. But you're right, functionally it makes no difference.
Having correct locs can be useful in attrsOf submodule scenarios, too, where we typically add "<name>" to the prefix. Or listOf submodule where we add "*" to the prefix.

In my interpretation of getSubOptions, it makes no guarantee of its structure. Imagine a type like either or attrTag, where different submodules could in theory be supported. It may make sense for such a type's getSubOptions to return an attrset of wrapped-type-name -> sub-options to avoid naming conflicts.

I'm not aware of any types that do that currently, but it's something to be aware of.

12:59:57
@mattsturg:matrix.orgMatt Sturgeon *

I always recommend trying to keep the prefix accurate, so that once you have your final list of options you can do "${opt}" or showOption opt.loc and get the correct option attrpath. But you're right, functionally it makes no difference.
Having correct locs can be useful in attrsOf submodule scenarios, too, where we typically add "<name>" to the prefix. Or listOf submodule where we add "*" to the prefix.

This may be controversial, but in my interpretation of getSubOptions, it makes no guarantee of its structure. Imagine a type like either or attrTag, where different submodules could in theory be supported. It may make sense for such a type's getSubOptions to return an attrset of wrapped-type-name -> sub-options to avoid naming conflicts.

I'm not aware of any types that do that currently, but it's something to be aware of.

13:00:23
@toonn:matrix.orgtoonn You mean something like attrsOf (either submodule submodule)? 13:24:25
@mattsturg:matrix.orgMatt Sturgeon

Yeah. Although currently that's not very common because it's not easy to discriminate between submodule-specific module definitions. A more practical example may be

either (attrsOf submoduleA) (listOf submoduleB)
13:34:48
@llakala:matrix.orgllakalais it an axiom that merging a single definition just results in the first definition's value?18:35:04
@mattsturg:matrix.orgMatt Sturgeon merge functions can also transform definitions, but I'd say the value should always be derived from that definition's value. Unless the merge function throws or something. 18:36:24
@mattsturg:matrix.orgMatt Sturgeon * merge functions can also transform definitions,* but I'd say the value should always be derived from that definition's value. Unless the merge function throws or something.
* e.g. types.coercedTo
18:36:49

Show newer messages


Back to Room ListRoom Version: 10