diff --git a/lib/modules.nix b/lib/modules.nix index c67ea2247ca7..ff286ad7c4b2 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -1246,6 +1246,8 @@ let allInvalid = filter (def: !type.check def.value) defsFinal; in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}" + else if type.emptyValue ? value then + type.emptyValue.value else # (nixos-option detects this specific error message and gives it special # handling. If changed here, please change it there too.) diff --git a/lib/options.nix b/lib/options.nix index c348491f0457..0cda30fc3544 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -589,11 +589,16 @@ rec { renderOptionValue opt.example ); } - // optionalAttrs (opt ? defaultText || opt ? default) { - default = builtins.addErrorContext "while evaluating the ${ - if opt ? defaultText then "defaultText" else "default value" - } of option `${name}`" (renderOptionValue (opt.defaultText or opt.default)); - } + // + optionalAttrs (opt ? defaultText || opt ? default || ((opt.type or { }).emptyValue or { }) ? value) + { + default = + builtins.addErrorContext + "while evaluating the ${ + if opt ? defaultText then "defaultText" else "default value" + } of option `${name}`" + (renderOptionValue (opt.defaultText or opt.default or opt.type.emptyValue.value)); + } // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 1a69d7dfb484..da0979756720 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -3393,6 +3393,44 @@ runTests { ]; }; + testEmptyValueOption = { + expr = + let + module = + { lib, ... }: + { + options = { + "empty-value" = lib.mkOption { + type = lib.mkOptionType { + name = "propagate-empty-value-to-default"; + emptyValue.value = 2; + }; + }; + }; + }; + eval = evalModules { + modules = [ module ]; + }; + in + filter (o: o.name == "empty-value") (optionAttrSetToDocList eval.options); + expected = [ + { + declarations = [ ]; + default = { + _type = "literalExpression"; + text = "2"; + }; + description = null; + internal = false; + loc = [ "empty-value" ]; + name = "empty-value"; + readOnly = false; + type = "propagate-empty-value-to-default"; + visible = true; + } + ]; + }; + testDocOptionVisiblity = { expr = let diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index aa9d3443fb6a..bc89d4c20851 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -674,6 +674,17 @@ checkConfigOutput "{}" config.submodule.a ./emptyValues.nix checkConfigError 'The option .int.a. was accessed but has no value defined. Try setting the option.' config.int.a ./emptyValues.nix checkConfigError 'The option .nonEmptyList.a. was accessed but has no value defined. Try setting the option.' config.nonEmptyList.a ./emptyValues.nix +## defaults +checkConfigOutput "\[\]" config.list ./defaults.nix +checkConfigOutput "{}" config.attrs ./defaults.nix +checkConfigOutput "{}" config.attrsOf ./defaults.nix +checkConfigOutput "null" config.null ./defaults.nix +checkConfigOutput "{}" config.submodule ./defaults.nix +checkConfigOutput "\[\]" config.unique ./defaults.nix +checkConfigOutput "\[\]" config.coercedTo ./defaults.nix +# These types don't have empty values +checkConfigError 'The option .int. was accessed but has no value defined. Try setting the option.' config.int ./defaults.nix + # types.unique # requires a single definition checkConfigError 'The option .examples\.merged. is defined multiple times while it.s expected to be unique' config.examples.merged.a ./types-unique.nix diff --git a/lib/tests/modules/defaults.nix b/lib/tests/modules/defaults.nix new file mode 100644 index 000000000000..56eee2c9c286 --- /dev/null +++ b/lib/tests/modules/defaults.nix @@ -0,0 +1,33 @@ +{ lib, ... }: +let + inherit (lib) types; +in +{ + options = { + list = lib.mkOption { + type = types.listOf types.int; + }; + attrs = lib.mkOption { + type = types.attrs; + }; + attrsOf = lib.mkOption { + type = types.attrsOf types.int; + }; + null = lib.mkOption { + type = types.nullOr types.int; + }; + submodule = lib.mkOption { + type = types.submodule { }; + }; + unique = lib.mkOption { + type = types.unique { message = "hi"; } (types.listOf types.int); + }; + coercedTo = lib.mkOption { + type = types.coercedTo (types.attrsOf types.int) builtins.attrNames (types.listOf types.str); + }; + # no empty value + int = lib.mkOption { + type = types.int; + }; + }; +}