From 7334dd90969d8e0075c56b708497bdbba14defda Mon Sep 17 00:00:00 2001 From: cinereal Date: Sat, 14 Mar 2026 15:08:39 -0700 Subject: [PATCH 1/2] lib.types: add types.option Signed-off-by: cinereal --- lib/tests/modules.sh | 3 +++ lib/tests/modules/option.nix | 15 +++++++++++++++ lib/types.nix | 7 +++++++ .../manual/development/option-types.section.md | 16 ++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 lib/tests/modules/option.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index aa9d3443fb6a..f567c33d8d98 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -704,6 +704,9 @@ checkConfigError 'In module .*/options-type-error-configuration.nix: expected an # Check that that merging of option collisions doesn't depend on type being set checkConfigError 'The option .group..*would be a parent of the following options, but its type .. does not support nested options.\n\s*- option.s. with prefix .group.enable..*' config.group.enable ./merge-typeless-option.nix +# types.optionDeclaration +checkConfigOutput '^10$' config.anOption ./option.nix + # Test that types.optionType merges types correctly checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix diff --git a/lib/tests/modules/option.nix b/lib/tests/modules/option.nix new file mode 100644 index 000000000000..8bc992e987c9 --- /dev/null +++ b/lib/tests/modules/option.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: +{ + options = { + theOption = lib.mkOption { + type = lib.types.optionDeclaration; + }; + anOption = config.theOption; + }; + config = { + theOption = lib.mkOption { + type = lib.types.int; + }; + anOption = 10; + }; +} diff --git a/lib/types.nix b/lib/types.nix index 4a859694a6e8..93d34c8d6c76 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -1180,6 +1180,13 @@ rec { }; }; + optionDeclaration = mkOptionType { + name = "optionDeclaration"; + description = "option declaration"; + descriptionClass = "noun"; + check = opt: isType "option" opt && !(opt ? value); + }; + # The type of a type! optionType = mkOptionType { name = "optionType"; diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index 48dba62aabbc..cc195abcc37b 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -137,6 +137,22 @@ Users must still be careful about how they reference these paths. multiple option definitions are correctly merged together. The main use case is as the type of the `_module.freeformType` option. +`types.optionDeclaration` + +: The type of a module system option declaration, as created by `lib.mkOption`. + This allows an option to hold another option declaration as its value, which + can then be spliced into a module's `options` attrset. Note that this only + accepts option declarations, not evaluated options (i.e. options that have + been processed by `evalModules` and have a `value` field). + + ::: {.warning} + Use of this type is a form of metaprogramming that makes modules harder + to reason about, since options and their types become dynamic values + rather than statically declared structure. Prefer conventional module + patterns where possible, and only reach for `types.optionDeclaration` when the + added complexity is justified. + ::: + `types.attrs` : A free-form attribute set. From 17c056fa24cd332e064e561f7b3950fe22214120 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 3 Apr 2026 10:53:47 +0200 Subject: [PATCH 2/2] lib.types.optionDeclaration: test error --- lib/tests/modules.sh | 1 + lib/tests/modules/option.nix | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index f567c33d8d98..87b335a742d6 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -706,6 +706,7 @@ checkConfigError 'The option .group..*would be a parent of the following options # types.optionDeclaration checkConfigOutput '^10$' config.anOption ./option.nix +checkConfigError 'A definition for option .aBadOptionDef. is not of type .option declaration.' config.aBadOptionDef ./option.nix # Test that types.optionType merges types correctly checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix diff --git a/lib/tests/modules/option.nix b/lib/tests/modules/option.nix index 8bc992e987c9..43cef14aee2b 100644 --- a/lib/tests/modules/option.nix +++ b/lib/tests/modules/option.nix @@ -1,15 +1,27 @@ -{ config, lib, ... }: +{ + config, + lib, + options, + ... +}: { options = { theOption = lib.mkOption { type = lib.types.optionDeclaration; }; anOption = config.theOption; + aBadOptionDef = lib.mkOption { + type = lib.types.optionDeclaration; + description = '' + This option is perfectly fine, but will have a bad definition. + ''; + }; }; config = { theOption = lib.mkOption { type = lib.types.int; }; anOption = 10; + aBadOptionDef = options.theOption; # Not a declaration }; }