diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index efa2d2034090..fbbf95e7c30f 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -717,6 +717,10 @@ 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 +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 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..43cef14aee2b --- /dev/null +++ b/lib/tests/modules/option.nix @@ -0,0 +1,27 @@ +{ + 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 + }; +} diff --git a/lib/types.nix b/lib/types.nix index 25286303f11e..b3c40208c2c9 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.