!wfudwzqQUiJYJnqfSY:nixos.org

NixOS Module System

203 Members
48 Servers

Load older messages


SenderMessageTime
29 May 2026
@toonn:matrix.orgtoonn So I ran into a `nullOr submodule` option (pixelfed.nginx) and the only way I've found to get at its options is `(head (head (services.pixelfed.nginx.type.functor.payload 10:26:46
@toonn:matrix.orgtoonn .elemType.functor.payload.modules)).imports).options` 10:26:50
@toonn:matrix.orgtoonn Is there a better way? I've been avoiding getSubOptions because it claims to be for documentation. I started out using `optionAttrSetToDocList` but that was too focused on documentation, meaning I needed to parse descriptions to get integer bounds information and such. 10:28:59
@mattsturg:matrix.orgMatt SturgeonI've just opened https://github.com/NixOS/nixpkgs/pull/52551910:29:37
@mattsturg:matrix.orgMatt Sturgeon getSubOptions doesn't help you if you want access to the actual options' definitions, highestPrio, etc from the merged submodule. Because getSubOptions is intended for documentation purposes, it only merges modules from the type instantiation; modules from config definitions are not considered. 10:31:31
@mattsturg:matrix.orgMatt Sturgeon

E.g.

{
  options.foo = mkOption {
    type = submodule {
      # This module is in `getSubOptions` _and_ `valueMeta.configuration`
    };
  };
  config.foo = {
    # This module is only in `valueMeta.configuration`, not `getSubOptions`
  };
}
10:38:39
@mattsturg:matrix.orgMatt Sturgeon * I've just opened https://github.com/NixOS/nixpkgs/pull/525519 to address nullOr's v2 support, but there are other wrapper types we need to address too. 10:44:25
@mattsturg:matrix.orgMatt Sturgeon * toonn I've just opened https://github.com/NixOS/nixpkgs/pull/525519 to address nullOr's v2 support, but there are other wrapper types we need to address too. 10:44:48
@winston:winston.shwinston joined the room.11:06:10
@toonn:matrix.orgtoonn I am only interested in the definition of options though. I don't care what options someone specifies as part of an open submodule. Though I am interested in any freeformType restricting such extra options. 11:21:15
@toonn:matrix.orgtoonn So without nullOr's v2 support I am in fact stuck digging in the functor.payload, right? 11:21:58
@mattsturg:matrix.orgMatt Sturgeon

I feel like there's a subtle confusion going on here with what we mean by "definition".

A submodule is just another configuration, within a wider configuration, evaluated mostly in isolation.

Both the outer and sub configurations have modules, and those modules can contain config definitions for their respective configurations.

When I say valueMeta.configuration considers modules from config definitions, I mean config definitions in the outer configuration; definitions that define modules for the submodule to evaluate.

11:24:55
@mattsturg:matrix.orgMatt Sturgeon *

I feel like there's a subtle confusion going on here with what we mean by "definition".

A submodule is just another configuration, within a wider configuration, evaluated mostly in isolation.

The submodule type merges to its configuration's config value, and the configuration as a whole is exposed through valueMeta, which comes from v2 check+merge.

Both the outer and sub configurations have modules, and those modules can contain config definitions for their respective configurations.

When I say valueMeta considers modules from config definitions, I mean config definitions in the outer configuration; definitions that define modules for the submodule to evaluate.

11:27:52
@mattsturg:matrix.orgMatt Sturgeon

To oversimplify, a submodule-type's lifecycle is:

  1. the type is instantiated, with initial modules
    • options declared and defaults defined are visible to type.getSubOptions
  2. an option is declared (in a host configuration) using the type
  3. the option is defined (either via config or default, in the host configuration)
    • these definitions are modules
  4. the option is merged, using the type's merge function
    1. it extends its base configuration with the defined modules
    2. it exposes the final configuration via valueMeta = { inherit configuration; }
    3. it evaluates the final value as configuration.config
11:34:29
@toonn:matrix.orgtoonn OK, so when I say definition, I mean the options defined in <nixpkgs/nixos/modules>. 11:36:49
@toonn:matrix.orgtoonn I am trying to derive possible options and their types for NixOS modules. 11:37:40
@mattsturg:matrix.orgMatt Sturgeon Just to double check, are we also confusing declaration vs definition? options.foo = lib.mkOption {} is a declaration, config.foo = {} is a definition. 11:37:57
@toonn:matrix.orgtoonn Sure, I'm interested in declared options and their types. 11:39:40
@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

Show newer messages


Back to Room ListRoom Version: 10