From b3810166c7dc5c75a5961e9c92726361a1ffc316 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Tue, 15 Sep 2020 20:45:42 +0200 Subject: [PATCH 1/5] nixos/doc: Soft deprecate types.attrs --- nixos/doc/manual/development/option-types.xml | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml index 5a6dae6e9912e..9827615349d97 100644 --- a/nixos/doc/manual/development/option-types.xml +++ b/nixos/doc/manual/development/option-types.xml @@ -21,16 +21,6 @@ - - - types.attrs - - - - A free-form attribute set. - - - types.bool @@ -64,6 +54,24 @@ + + + types.attrs + + + + A free-form attribute set. + + This type will be deprecated in the future because it doesn't recurse + into attribute sets, silently drops earlier attribute definitions, and + doesn't discharge lib.mkDefault, lib.mkIf + and co. For allowing arbitrary attribute sets, prefer + types.attrsOf types.anything instead which doesn't + have these problems. + + + + From 6e7bc2c6c90335e7bb7d7ca8cef77c58f0e37444 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Tue, 15 Sep 2020 20:30:48 +0200 Subject: [PATCH 2/5] lib/options: Fix mergeEqualOption for singular functions Previously it would error out for a single function definition --- lib/options.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/options.nix b/lib/options.nix index 38f4f1329f212..0494a597ab806 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -107,6 +107,10 @@ rec { /* "Merge" option definitions by checking that they all have the same value. */ mergeEqualOption = loc: defs: if defs == [] then abort "This case should never happen." + # Return early if we only have one element + # This also makes it work for functions, because the foldl' below would try + # to compare the first element with itself, which is false for functions + else if length defs == 1 then (elemAt defs 0).value else foldl' (val: def: if def.value != val then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." From 67551f46fbc20b3b96ff27503b659a8f3fedb421 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Fri, 4 Sep 2020 13:18:22 +0200 Subject: [PATCH 3/5] lib/types: Introduce types.anything This new type has unsurprising merge behavior: Only attribute sets are merged together (recursively), and only if they don't conflict. This is in contrast to the existing types: - types.attrs is problematic because later definitions completely override attributes of earlier definitions, and it doesn't support mkIf and co. - types.unspecified is very similar to types.attrs, but it has smart merging behavior that often doesn't make sense, and it doesn't support all types --- lib/types.nix | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/types.nix b/lib/types.nix index 17e7a939fe3d3..a5f4d87e3efde 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -101,6 +101,42 @@ rec { # When adding new types don't forget to document them in # nixos/doc/manual/development/option-types.xml! types = rec { + + anything = mkOptionType { + name = "anything"; + description = "anything"; + check = value: true; + merge = loc: defs: + let + getType = value: + if isAttrs value && isCoercibleToString value + then "stringCoercibleSet" + else builtins.typeOf value; + + # Returns the common type of all definitions, throws an error if they + # don't have the same type + commonType = foldl' (type: def: + if getType def.value == type + then type + else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" + ) (getType (head defs).value) defs; + + mergeFunction = { + # Recursively merge attribute sets + set = (attrsOf anything).merge; + # Safe and deterministic behavior for lists is to only accept one definition + # listOf only used to apply mkIf and co. + list = + if length defs > 1 + then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." + else (listOf anything).merge; + # This is the type of packages, only accept a single definition + stringCoercibleSet = mergeOneOption; + # Otherwise fall back to only allowing all equal definitions + }.${commonType} or mergeEqualOption; + in mergeFunction loc defs; + }; + unspecified = mkOptionType { name = "unspecified"; }; From 6a7d250007baedeb4de34e8ce490d49160cb63a8 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Fri, 4 Sep 2020 15:27:26 +0200 Subject: [PATCH 4/5] lib/tests: Add tests for types.anything --- lib/tests/modules.sh | 29 ++++++++++++ .../types-anything/attrs-coercible.nix | 12 +++++ .../modules/types-anything/equal-atoms.nix | 26 +++++++++++ .../modules/types-anything/functions.nix | 17 +++++++ lib/tests/modules/types-anything/lists.nix | 16 +++++++ lib/tests/modules/types-anything/mk-mods.nix | 44 +++++++++++++++++++ .../modules/types-anything/nested-attrs.nix | 22 ++++++++++ 7 files changed, 166 insertions(+) create mode 100644 lib/tests/modules/types-anything/attrs-coercible.nix create mode 100644 lib/tests/modules/types-anything/equal-atoms.nix create mode 100644 lib/tests/modules/types-anything/functions.nix create mode 100644 lib/tests/modules/types-anything/lists.nix create mode 100644 lib/tests/modules/types-anything/mk-mods.nix create mode 100644 lib/tests/modules/types-anything/nested-attrs.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 943deebe3c093..cfe474d4ded25 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -233,6 +233,35 @@ checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf. checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix checkConfigOutput 24 config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix +## types.anything +# Check that attribute sets are merged recursively +checkConfigOutput null config.value.foo ./types-anything/nested-attrs.nix +checkConfigOutput null config.value.l1.foo ./types-anything/nested-attrs.nix +checkConfigOutput null config.value.l1.l2.foo ./types-anything/nested-attrs.nix +checkConfigOutput null config.value.l1.l2.l3.foo ./types-anything/nested-attrs.nix +# Attribute sets that are coercible to strings shouldn't be recursed into +checkConfigOutput foo config.value.outPath ./types-anything/attrs-coercible.nix +# Multiple lists aren't concatenated together +checkConfigError 'The option .* has conflicting definitions' config.value ./types-anything/lists.nix +# Check that all equalizable atoms can be used as long as all definitions are equal +checkConfigOutput 0 config.value.int ./types-anything/equal-atoms.nix +checkConfigOutput false config.value.bool ./types-anything/equal-atoms.nix +checkConfigOutput '""' config.value.string ./types-anything/equal-atoms.nix +checkConfigOutput / config.value.path ./types-anything/equal-atoms.nix +checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix +checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix +# Functions can't be merged together +checkConfigError "The option .* has conflicting definitions" config.value.multiple-lambdas ./types-anything/functions.nix +checkConfigOutput '' config.value.single-lambda ./types-anything/functions.nix +# Check that all mk* modifiers are applied +checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix +checkConfigOutput '{ }' config.value.mkiftrue ./types-anything/mk-mods.nix +checkConfigOutput 1 config.value.mkdefault ./types-anything/mk-mods.nix +checkConfigOutput '{ }' config.value.mkmerge ./types-anything/mk-mods.nix +checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix +checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix +checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix + cat < Date: Tue, 15 Sep 2020 20:42:34 +0200 Subject: [PATCH 5/5] nixos/doc: Add docs for types.anything --- nixos/doc/manual/development/option-types.xml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml index 9827615349d97..3d2191e2f3f31 100644 --- a/nixos/doc/manual/development/option-types.xml +++ b/nixos/doc/manual/development/option-types.xml @@ -54,6 +54,46 @@ + + + types.anything + + + + A type that accepts any value and recursively merges attribute sets together. + This type is recommended when the option type is unknown. + + <literal>types.anything</literal> Example + + Two definitions of this type like + +{ + str = lib.mkDefault "foo"; + pkg.hello = pkgs.hello; + fun.fun = x: x + 1; +} + + +{ + str = lib.mkIf true "bar"; + pkg.gcc = pkgs.gcc; + fun.fun = lib.mkForce (x: x + 2); +} + + will get merged to + +{ + str = "bar"; + pkg.gcc = pkgs.gcc; + pkg.hello = pkgs.hello; + fun.fun = x: x + 2; +} + + + + + + types.attrs