From 3a0417a948f6eedd0c4dbe3a463107a0aa751611 Mon Sep 17 00:00:00 2001 From: Matt Sturgeon Date: Wed, 8 Jan 2025 20:05:41 +0000 Subject: [PATCH] various fixes --- lib/fields.nix | 5 ++-- lib/tests/modules.sh | 4 ++-- .../modules/declare-record-bad-default.nix | 8 +++---- .../modules/declare-record-optional-field.nix | 15 ++++++------ lib/tests/modules/declare-record-wildcard.nix | 8 +++---- lib/tests/modules/declare-record.nix | 8 +++---- lib/types/record.nix | 23 ++++++++++++++----- 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/lib/fields.nix b/lib/fields.nix index 6021370859c57..08ab97b5889e5 100644 --- a/lib/fields.nix +++ b/lib/fields.nix @@ -26,9 +26,8 @@ internal ? null, # Whether the field shows up in the manual. Default: true. Use false to hide the field and any sub-options from submodules. Use "shallow" to hide only sub-options. visible ? null, + # Whether the field is omitted from the final record when undefined. Default false. + optional ? null, }@attrs: - assert lib.assertMsg ( - (attrs.optional or false) -> (!attrs ? default) - ) "mkField: `optional` is true, but a `default` is provided"; attrs // { _type = "field"; }; } diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 9735a1e3bad03..2545666bb90a9 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -119,7 +119,7 @@ checkConfigError 'define-record-mallory.nix.: "beginning of time"' config.people checkConfigOutput '^true$' config.people.bob.isCool ./declare-record.nix ./define-record-alice.nix ./define-record-bob.nix # record field bad default definition -checkConfigError 'In .the default value of option people.mallory.: "yeah"' config.people.mallory.isCool ./declare-record-bad-default.nix ./define-record-mallory.nix +checkConfigError 'In .the default value of field people.mallory.: "yeah"' config.people.mallory.isCool ./declare-record-bad-default.nix ./define-record-mallory.nix checkConfigError 'A definition for option .people.mallory.isCool. is not of type .boolean.. Definition values:' config.people.mallory.isCool ./declare-record-bad-default.nix ./define-record-mallory.nix # record field works in presence of wildcard @@ -129,7 +129,7 @@ checkConfigOutput '^2016$' config.people.alice.nixerSince ./declare-record-wildc checkConfigOutput '^true$' config.people.alice.mechKeyboard ./declare-record-wildcard.nix ./define-record-alice-prefs.nix # record definition without corresponding field -checkConfigError 'A definition for option .people.mike. has an unknown field' config.people.mike.age ./declare-record.nix ./define-record-mike.nix +checkConfigError 'A definition for option .people.mike. has an unknown fields' config.people.mike.age ./declare-record.nix ./define-record-mike.nix # record optional field without definition checkConfigError "attribute 'age' in selection path 'config.people.alice.age' not found" config.people.alice.age ./declare-record-optional-field.nix ./define-record-alice.nix # record optional field with definition diff --git a/lib/tests/modules/declare-record-bad-default.nix b/lib/tests/modules/declare-record-bad-default.nix index e8e9ee747a6ac..4fcd7c5186a3d 100644 --- a/lib/tests/modules/declare-record-bad-default.nix +++ b/lib/tests/modules/declare-record-bad-default.nix @@ -1,13 +1,13 @@ { lib, ... }: let - inherit (lib) mkOption types; + inherit (lib) mkField mkOption types; person = types.record { fields = { - nixerSince = mkOption { type = types.int; }; - name = mkOption { type = types.str; }; - isCool = mkOption { + nixerSince = mkField { type = types.int; }; + name = mkField { type = types.str; }; + isCool = mkField { type = types.bool; default = "yeah"; }; diff --git a/lib/tests/modules/declare-record-optional-field.nix b/lib/tests/modules/declare-record-optional-field.nix index 669578610eeb7..145e3c1992435 100644 --- a/lib/tests/modules/declare-record-optional-field.nix +++ b/lib/tests/modules/declare-record-optional-field.nix @@ -1,17 +1,18 @@ { lib, ... }: let - inherit (lib) mkOption types; + inherit (lib) mkField mkOption types; person = types.record { fields = { - nixerSince = mkOption { type = types.int; }; - name = mkOption { type = types.str; }; + nixerSince = mkField { type = types.int; }; + name = mkField { type = types.str; }; + age = mkField { + type = types.ints.unsigned; + optional = true; + }; }; - optionalFields = { - age = mkOption { type = types.ints.unsigned; }; - }; - wildcard = mkOption { type = types.bool; }; + freeformType = types.bool; }; in diff --git a/lib/tests/modules/declare-record-wildcard.nix b/lib/tests/modules/declare-record-wildcard.nix index e514f3c560fe9..4602be1aac737 100644 --- a/lib/tests/modules/declare-record-wildcard.nix +++ b/lib/tests/modules/declare-record-wildcard.nix @@ -1,14 +1,14 @@ { lib, ... }: let - inherit (lib) mkOption types; + inherit (lib) mkField mkOption types; person = types.record { fields = { - nixerSince = mkOption { type = types.int; }; - name = mkOption { type = types.str; }; + nixerSince = mkField { type = types.int; }; + name = mkField { type = types.str; }; }; - wildcard = mkOption { type = types.bool; }; + freeformType = types.bool; }; in diff --git a/lib/tests/modules/declare-record.nix b/lib/tests/modules/declare-record.nix index 70e88c3231f92..2996ec73cf1c0 100644 --- a/lib/tests/modules/declare-record.nix +++ b/lib/tests/modules/declare-record.nix @@ -1,13 +1,13 @@ { lib, ... }: let - inherit (lib) mkOption types; + inherit (lib) mkField mkOption types; person = types.record { fields = { - nixerSince = mkOption { type = types.int; }; - name = mkOption { type = types.str; }; - isCool = mkOption { + nixerSince = mkField { type = types.int; }; + name = mkField { type = types.str; }; + isCool = mkField { type = types.bool; default = true; }; diff --git a/lib/types/record.nix b/lib/types/record.nix index 102ca5e2f1ebb..63cdd8212c8b0 100644 --- a/lib/types/record.nix +++ b/lib/types/record.nix @@ -15,6 +15,14 @@ let isAttrs ; + inherit (lib.options) + showDefs + ; + + inherit (lib.strings) + escapeNixIdentifier + ; + record = { fields ? { }, @@ -24,7 +32,9 @@ let checkField = name: field: if field._type or null != "field" then - throw "Record field `${lib.escapeNixIdentifier name}` must be declared with `mkField`." + throw "Record field `${escapeNixIdentifier name}` must be declared with `mkField`." + else if (field.optional or false) && (field ? default) then + throw "Record field `${escapeNixIdentifier name}` is optional, but a `default` is provided." else field; @@ -59,17 +69,18 @@ let fieldValues = concatMapAttrs ( fieldName: field: let - fieldOption = mergeDefinitions (loc ++ [ fieldName ]) fieldOption.type ( + mergedOption = mergeDefinitions (loc ++ [ fieldName ]) field.type ( data.${fieldName} or [ ] ++ optional (field ? default) { - value = lib.mkOptionDefault fieldOption.default; + value = lib.mkOptionDefault field.default; file = "the default value of field ${showOption loc}"; } ); + isRequired = !field.optional or false; in builtins.addErrorContext "while evaluating the field `${fieldName}' of option `${showOption loc}'" ( - optionalAttrs (!field.optional || fieldOption.isDefined) { - ${fieldName} = fieldOption.mergedValue; + optionalAttrs (isRequired || mergedOption.isDefined) { + ${fieldName} = mergedOption.mergedValue; } ) ) checkedFields; @@ -86,7 +97,7 @@ let else throw '' A definition for option `${showOption loc}' has an unknown fields: - ${lib.concatMapAttrsStringSep "\n" (name: defs: "`${name}'${lib.showDefs defs}") extraData}''; + ${lib.concatMapAttrsStringSep "\n" (name: defs: "`${name}'${showDefs defs}") extraData}''; in if freeformType == null then checkedExtraDefs else fieldValues // extraValues; nestedTypes = lib.optionalAttrs (freeformType != null) {