diff --git a/lib/options.nix b/lib/options.nix index 0cda30fc3544..6191a876cdd4 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -590,7 +590,16 @@ rec { ); } // - optionalAttrs (opt ? defaultText || opt ? default || ((opt.type or { }).emptyValue or { }) ? value) + optionalAttrs + ( + opt ? defaultText + || opt ? default + # Render emptyValue-based defaults, but only for types without + # submodules (e.g. types.submodule). Submodules may evaluate to + # error without user defs, and their sub-options are documented + # individually, so best to skip those here. + || ((opt.type or { }).emptyValue or { }) ? value && (opt.type or { }).getSubModules or null == null + ) { default = builtins.addErrorContext diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 765ca31a48e7..515ab169fe43 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -686,6 +686,8 @@ 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 +## submodule emptyValue must evaluate sub-option defaults +checkConfigOutput "ok" config.result ./defaults.nix # types.unique # requires a single definition diff --git a/lib/tests/modules/defaults.nix b/lib/tests/modules/defaults.nix index 56eee2c9c286..160245df27ad 100644 --- a/lib/tests/modules/defaults.nix +++ b/lib/tests/modules/defaults.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +{ lib, config, ... }: let inherit (lib) types; in @@ -19,6 +19,18 @@ in submodule = lib.mkOption { type = types.submodule { }; }; + submoduleWithDefaults = lib.mkOption { + type = types.submodule { + options.enabled = lib.mkOption { + type = types.bool; + default = true; + }; + options.count = lib.mkOption { + type = types.int; + default = 13; + }; + }; + }; unique = lib.mkOption { type = types.unique { message = "hi"; } (types.listOf types.int); }; @@ -29,5 +41,16 @@ in int = lib.mkOption { type = types.int; }; + + result = lib.mkOption { + type = types.str; + default = + assert + config.submoduleWithDefaults == { + enabled = true; + count = 13; + }; + "ok"; + }; }; } diff --git a/lib/types.nix b/lib/types.nix index 9fb99b9cc66e..f2c5f7897cdc 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -1320,7 +1320,7 @@ rec { }; }; emptyValue = { - value = { }; + value = base.config; }; getSubOptions = prefix: