From 063540e83fefe7a7880620bdef43a0ec736bb5ad Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 2 Aug 2021 21:18:40 +0200 Subject: [PATCH 1/6] lib/types: Fix emptyValue of listOf and nonEmptyListOf An empty list is [], not {}! Also, non-empty lists shouldn't have a default of an empty list! --- lib/types.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index 244cbb6b5354d..ef38d0d3a8814 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -377,7 +377,7 @@ rec { ).optionalValue ) def.value ) defs))); - emptyValue = { value = {}; }; + emptyValue = { value = []; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); getSubModules = elemType.getSubModules; substSubModules = m: listOf (elemType.substSubModules m); @@ -389,7 +389,7 @@ rec { let list = addCheck (types.listOf elemType) (l: l != []); in list // { description = "non-empty " + list.description; - # Note: emptyValue is left as is, because another module may define an element. + emptyValue = { }; }; attrsOf = elemType: mkOptionType rec { From 102d57d5c524e56b352d50049e3d9caabd48933b Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 2 Aug 2021 21:13:39 +0200 Subject: [PATCH 2/6] lib/modules: Default to emptyValue of types if no definitions This patch makes it so that if an option has no definitions, but its type specifies an `emptyValue`, that is used instead of throwing an error. This means that for e.g. `attrsOf`, there's no need to specify a default of `default = {}` anymore. Other types that have such a default are `nullOr` (default `null`), `submodule` (default `{}`) and `listOf` (default `[]`). --- lib/modules.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/modules.nix b/lib/modules.nix index e3bb27aa94621..1bc35df91df80 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -646,6 +646,8 @@ rec { if all (def: type.check def.value) defsFinal then type.merge loc defsFinal else 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.) From a7b5322e4ebb2db25ea4ad24da3305861ada00bf Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Fri, 15 Oct 2021 16:48:38 +0200 Subject: [PATCH 3/6] lib/tests: Add tests for emptyValue --- lib/tests/modules.sh | 9 +++++++++ lib/tests/modules/emptyValues.nix | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 lib/tests/modules/emptyValues.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 590937da5b8f0..8746d88b6081d 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -284,6 +284,15 @@ checkConfigOutput '^"a b"$' config.resultFoo ./declare-variants.nix ./define-var checkConfigOutput '^"a y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix checkConfigOutput '^"a b c"$' config.resultFooFoo ./declare-variants.nix ./define-variant.nix +## emptyValue's +checkConfigOutput "[ ]" config.list ./emptyValues.nix +checkConfigOutput "{ }" config.attrs ./emptyValues.nix +checkConfigOutput "null" config.null ./emptyValues.nix +checkConfigOutput "{ }" config.submodule ./emptyValues.nix +# These types don't have empty values +checkConfigError 'The option .int. is used but not defined' config.int ./emptyValues.nix +checkConfigError 'The option .nonEmptyList. is used but not defined' config.nonEmptyList ./emptyValues.nix + cat < Date: Mon, 2 Aug 2021 21:42:45 +0200 Subject: [PATCH 4/6] lib/types: Introduce types.raw for unprocessed values --- lib/tests/modules.sh | 6 ++++ lib/tests/modules/raw.nix | 30 +++++++++++++++++++ lib/types.nix | 7 +++++ .../development/option-types.section.md | 9 ++++++ .../development/option-types.section.xml | 16 ++++++++++ 5 files changed, 68 insertions(+) create mode 100644 lib/tests/modules/raw.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 8746d88b6081d..3e6ba42d843d7 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -293,6 +293,12 @@ checkConfigOutput "{ }" config.submodule ./emptyValues.nix checkConfigError 'The option .int. is used but not defined' config.int ./emptyValues.nix checkConfigError 'The option .nonEmptyList. is used but not defined' config.nonEmptyList ./emptyValues.nix +## types.raw +checkConfigOutput "{ foo = ; }" config.unprocessedNesting ./raw.nix +checkConfigOutput "10" config.processedToplevel ./raw.nix +checkConfigError "The unique option .multiple. is defined multiple times" config.multiple ./raw.nix +checkConfigOutput "bar" config.priorities ./raw.nix + cat < + + + types.raw + + + + A type that accepts exactly a single arbitrary value. This + type is recommended for options whose only value is defined + by the same module as the option, because its structure is + known ahead-of time and doesn’t need to be typechecked + internally. Especially useful for values where type checking + would be too expensive, or where type checking is handled in + another way. + + + types.attrs From 070b8d9c03546c87430b8fefe91d00ca736e8d39 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 2 Aug 2021 21:43:41 +0200 Subject: [PATCH 5/6] lib/types: Introduce types.unconditional Allows replacing `types.lazyAttrsOf elemType` with `types.attrsOf (types.unconditional elemType)`. Also works with lists: `types.listOf (types.unconditional elemType)`. --- lib/modules.nix | 6 ++- lib/tests/modules.sh | 10 +++++ lib/tests/modules/unconditional.nix | 40 +++++++++++++++++++ lib/types.nix | 34 +++++----------- .../development/option-types.section.md | 13 ++++++ .../development/option-types.section.xml | 25 ++++++++++++ 6 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 lib/tests/modules/unconditional.nix diff --git a/lib/modules.nix b/lib/modules.nix index 1bc35df91df80..fd507c58f4bc8 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -642,7 +642,7 @@ rec { # Type-check the remaining definitions, and merge them. Or throw if no definitions. mergedValue = - if isDefined then + if defsFinal != [] then if all (def: type.check def.value) defsFinal then type.merge loc defsFinal else 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}" @@ -653,7 +653,9 @@ rec { # handling. If changed here, please change it there too.) throw "The option `${showOption loc}' is used but not defined."; - isDefined = defsFinal != []; + # Note: We use `or true` in case `lib.types` from an older nixpkgs version + # that doesn't set `conditional` is used. Avoids some trouble down the road + isDefined = type.conditional or true -> defsFinal != []; optionalValue = if isDefined then { value = mergedValue; } diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 3e6ba42d843d7..e40af61f7e03c 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -299,6 +299,16 @@ checkConfigOutput "10" config.processedToplevel ./raw.nix checkConfigError "The unique option .multiple. is defined multiple times" config.multiple ./raw.nix checkConfigOutput "bar" config.priorities ./raw.nix +## types.unconditional +checkConfigOutput "10" config.ifTrue ./unconditional.nix +checkConfigError "The option .ifFalse. is used but not defined." config.ifFalse ./unconditional.nix +checkConfigOutput "10" config.attrsIfTrue.foo ./unconditional.nix +checkConfigError "The option .attrsIfFalse.foo. is used but not defined." config.attrsIfFalse.foo ./unconditional.nix +checkConfigOutput "[ \"foo\" ]" config.attrKeys ./unconditional.nix +checkConfigOutput "10" config.listIfTrue.0 ./unconditional.nix +checkConfigError "The option .listIfFalse.\[definition 1-entry 1\]. is used but not defined." config.listIfFalse.0 ./unconditional.nix +checkConfigOutput "1" config.listLength ./unconditional.nix + cat <"]); - getSubModules = elemType.getSubModules; - substSubModules = m: lazyAttrsOf (elemType.substSubModules m); - functor = (defaultFunctor name) // { wrapped = elemType; }; - nestedTypes.elemType = elemType; - }; + lazyAttrsOf = elemType: attrsOf (unconditional elemType); # TODO: drop this in the future: loaOf = elemType: types.attrsOf elemType // { diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index 042bf67068d6d..af05fafd2629c 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -210,6 +210,19 @@ Value types are types that take a value parameter. requires using a function: `the-submodule = { ... }: { options = { ... }; }`. +`types.unconditional` *`t`* + +: The same as *`t`*, but without full support for conditional definitions + using `lib.mkIf `. This has the advantage that when used as the + element type in `types.attrsOf` and `types.listOf`, the attribute keys and + the list length respectively can be known without evaluating each + individual attribute/list value, keeping these structures lazy. + For instance, this prevents a case of infinite recursion by allowing elements + in an attribute set to reference other elements of the same attribute set + via the `config` module argument. + If `lib.mkIf ` is used on such a type, the value is assumed to exist, + but an error is thrown when evaluated and the condition is false. + ## Composed Types {#sec-option-types-composed} Composed types are types that take a type as parameter. `listOf diff --git a/nixos/doc/manual/from_md/development/option-types.section.xml b/nixos/doc/manual/from_md/development/option-types.section.xml index adffdb69987e0..a6fbe9a7f47c4 100644 --- a/nixos/doc/manual/from_md/development/option-types.section.xml +++ b/nixos/doc/manual/from_md/development/option-types.section.xml @@ -408,6 +408,31 @@ + + + types.unconditional + t + + + + The same as t, but + without full support for conditional definitions using + lib.mkIf <cond>. This has the + advantage that when used as the element type in + types.attrsOf and + types.listOf, the attribute keys and the + list length respectively can be known without evaluating + each individual attribute/list value, keeping these + structures lazy. For instance, this prevents a case of + infinite recursion by allowing elements in an attribute set + to reference other elements of the same attribute set via + the config module argument. If + lib.mkIf <cond> is used on such a + type, the value is assumed to exist, but an error is thrown + when evaluated and the condition is false. + + +
From 8e483d678f2350682a2be6610b468cfad7833c0d Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Mon, 2 Aug 2021 21:47:34 +0200 Subject: [PATCH 6/6] lib/modules: Use types.raw for _module.args Fixes https://github.com/NixOS/nixpkgs/issues/53458, as types.raw doesn't allow setting multiple values --- lib/modules.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules.nix b/lib/modules.nix index fd507c58f4bc8..0b18cbe1c1364 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -137,7 +137,7 @@ rec { # support for that, in turn it's lazy in its values. This means e.g. # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't # start a download when `pkgs` wasn't evaluated. - type = types.lazyAttrsOf types.unspecified; + type = types.lazyAttrsOf types.raw; internal = true; description = "Arguments passed to each module."; };