summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Hensing <roberth@users.noreply.github.com>2020-09-21 08:49:24 +0200
committerGitHub <noreply@github.com>2020-09-21 08:49:24 +0200
commitf3893d8b534dfb61472c09bd9c8a836599320e65 (patch)
tree3bdaac5d6359666bf249579beefbab7832adfa87
parentc06b0edde22ce82caa8a996df7d4505d71770287 (diff)
parent310699319b422210b70ebacf676007172c67c90b (diff)
Merge pull request #97119 from Infinisil/types.anything
Introduce `types.anything`
-rw-r--r--lib/options.nix4
-rwxr-xr-xlib/tests/modules.sh29
-rw-r--r--lib/tests/modules/types-anything/attrs-coercible.nix12
-rw-r--r--lib/tests/modules/types-anything/equal-atoms.nix26
-rw-r--r--lib/tests/modules/types-anything/functions.nix17
-rw-r--r--lib/tests/modules/types-anything/lists.nix16
-rw-r--r--lib/tests/modules/types-anything/mk-mods.nix44
-rw-r--r--lib/tests/modules/types-anything/nested-attrs.nix22
-rw-r--r--lib/types.nix36
-rw-r--r--nixos/doc/manual/development/option-types.xml68
10 files changed, 264 insertions, 10 deletions
diff --git a/lib/options.nix b/lib/options.nix
index 38f4f1329f21..0494a597ab80 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)}."
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 943deebe3c09..cfe474d4ded2 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 '<LAMBDA>' 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 <<EOF
====== module tests ======
$pass Pass
diff --git a/lib/tests/modules/types-anything/attrs-coercible.nix b/lib/tests/modules/types-anything/attrs-coercible.nix
new file mode 100644
index 000000000000..085cbd638f17
--- /dev/null
+++ b/lib/tests/modules/types-anything/attrs-coercible.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config.value = {
+ outPath = "foo";
+ err = throw "err";
+ };
+
+}
diff --git a/lib/tests/modules/types-anything/equal-atoms.nix b/lib/tests/modules/types-anything/equal-atoms.nix
new file mode 100644
index 000000000000..972711201a09
--- /dev/null
+++ b/lib/tests/modules/types-anything/equal-atoms.nix
@@ -0,0 +1,26 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.int = 0;
+ value.bool = false;
+ value.string = "";
+ value.path = /.;
+ value.null = null;
+ value.float = 0.1;
+ }
+ {
+ value.int = 0;
+ value.bool = false;
+ value.string = "";
+ value.path = /.;
+ value.null = null;
+ value.float = 0.1;
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/functions.nix b/lib/tests/modules/types-anything/functions.nix
new file mode 100644
index 000000000000..079518913918
--- /dev/null
+++ b/lib/tests/modules/types-anything/functions.nix
@@ -0,0 +1,17 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.single-lambda = x: x;
+ value.multiple-lambdas = x: x;
+ }
+ {
+ value.multiple-lambdas = x: x;
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/lists.nix b/lib/tests/modules/types-anything/lists.nix
new file mode 100644
index 000000000000..bd846afd3d18
--- /dev/null
+++ b/lib/tests/modules/types-anything/lists.nix
@@ -0,0 +1,16 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value = [ null ];
+ }
+ {
+ value = [ null ];
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/mk-mods.nix b/lib/tests/modules/types-anything/mk-mods.nix
new file mode 100644
index 000000000000..f84ad01df017
--- /dev/null
+++ b/lib/tests/modules/types-anything/mk-mods.nix
@@ -0,0 +1,44 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.mkiffalse = lib.mkIf false {};
+ }
+ {
+ value.mkiftrue = lib.mkIf true {};
+ }
+ {
+ value.mkdefault = lib.mkDefault 0;
+ }
+ {
+ value.mkdefault = 1;
+ }
+ {
+ value.mkmerge = lib.mkMerge [
+ {}
+ ];
+ }
+ {
+ value.mkbefore = lib.mkBefore true;
+ }
+ {
+ value.nested = lib.mkMerge [
+ {
+ foo = lib.mkDefault 0;
+ bar = lib.mkIf false 0;
+ }
+ (lib.mkIf true {
+ foo = lib.mkIf true (lib.mkForce 1);
+ bar = {
+ baz = lib.mkDefault "baz";
+ };
+ })
+ ];
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/nested-attrs.nix b/lib/tests/modules/types-anything/nested-attrs.nix
new file mode 100644
index 000000000000..e57d33ef8717
--- /dev/null
+++ b/lib/tests/modules/types-anything/nested-attrs.nix
@@ -0,0 +1,22 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.foo = null;
+ }
+ {
+ value.l1.foo = null;
+ }
+ {
+ value.l1.l2.foo = null;
+ }
+ {
+ value.l1.l2.l3.foo = null;
+ }
+ ];
+
+}
diff --git a/lib/types.nix b/lib/types.nix
index ef2c78082f8d..aae45366b8fb 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -104,6 +104,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";
};
diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml
index 5a6dae6e9912..3d2191e2f3f3 100644
--- a/nixos/doc/manual/development/option-types.xml
+++ b/nixos/doc/manual/development/option-types.xml
@@ -23,16 +23,6 @@
<variablelist>
<varlistentry>
<term>
- <varname>types.attrs</varname>
- </term>
- <listitem>
- <para>
- A free-form attribute set.
- </para>
- </listitem>
- </varlistentry>
- <varlistentry>
- <term>
<varname>types.bool</varname>
</term>
<listitem>
@@ -64,6 +54,64 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <varname>types.anything</varname>
+ </term>
+ <listitem>
+ <para>
+ A type that accepts any value and recursively merges attribute sets together.
+ This type is recommended when the option type is unknown.
+ <example xml:id="ex-types-anything">
+ <title><literal>types.anything</literal> Example</title>
+ <para>
+ Two definitions of this type like
+<programlisting>
+{
+ str = lib.mkDefault "foo";
+ pkg.hello = pkgs.hello;
+ fun.fun = x: x + 1;
+}
+</programlisting>
+<programlisting>
+{
+ str = lib.mkIf true "bar";
+ pkg.gcc = pkgs.gcc;
+ fun.fun = lib.mkForce (x: x + 2);
+}
+</programlisting>
+ will get merged to
+<programlisting>
+{
+ str = "bar";
+ pkg.gcc = pkgs.gcc;
+ pkg.hello = pkgs.hello;
+ fun.fun = x: x + 2;
+}
+</programlisting>
+ </para>
+ </example>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <varname>types.attrs</varname>
+ </term>
+ <listitem>
+ <para>
+ A free-form attribute set.
+ <warning><para>
+ 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 <literal>lib.mkDefault</literal>, <literal>lib.mkIf
+ </literal> and co. For allowing arbitrary attribute sets, prefer
+ <literal>types.attrsOf types.anything</literal> instead which doesn't
+ have these problems.
+ </para></warning>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>