Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce types.anything #97119

Merged
merged 5 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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)}."
Expand Down
29 changes: 29 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions lib/tests/modules/types-anything/attrs-coercible.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{ lib, ... }: {

options.value = lib.mkOption {
type = lib.types.anything;
};

config.value = {
outPath = "foo";
err = throw "err";
};

}
26 changes: 26 additions & 0 deletions lib/tests/modules/types-anything/equal-atoms.nix
Original file line number Diff line number Diff line change
@@ -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;
}
];

}
17 changes: 17 additions & 0 deletions lib/tests/modules/types-anything/functions.nix
Original file line number Diff line number Diff line change
@@ -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;
}
];

}
16 changes: 16 additions & 0 deletions lib/tests/modules/types-anything/lists.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{ lib, ... }: {

options.value = lib.mkOption {
type = lib.types.anything;
};

config = lib.mkMerge [
{
value = [ null ];
}
{
value = [ null ];
}
];

}
44 changes: 44 additions & 0 deletions lib/tests/modules/types-anything/mk-mods.nix
Original file line number Diff line number Diff line change
@@ -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";
};
})
];
}
];

}
22 changes: 22 additions & 0 deletions lib/tests/modules/types-anything/nested-attrs.nix
Original file line number Diff line number Diff line change
@@ -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;
}
];

}
36 changes: 36 additions & 0 deletions lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;
roberth marked this conversation as resolved.
Show resolved Hide resolved
# 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";
};
Expand Down
68 changes: 58 additions & 10 deletions nixos/doc/manual/development/option-types.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@
</para>

<variablelist>
<varlistentry>
<term>
<varname>types.attrs</varname>
</term>
<listitem>
<para>
A free-form attribute set.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<varname>types.bool</varname>
Expand Down Expand Up @@ -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>
Expand Down