Merge pull request #97119 from Infinisil/types.anything

Introduce `types.anything`
This commit is contained in:
Robert Hensing 2020-09-21 08:49:24 +02:00 committed by GitHub
commit f3893d8b53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 264 additions and 10 deletions

View file

@ -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)}."

View file

@ -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

View file

@ -0,0 +1,12 @@
{ lib, ... }: {
options.value = lib.mkOption {
type = lib.types.anything;
};
config.value = {
outPath = "foo";
err = throw "err";
};
}

View file

@ -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;
}
];
}

View file

@ -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;
}
];
}

View file

@ -0,0 +1,16 @@
{ lib, ... }: {
options.value = lib.mkOption {
type = lib.types.anything;
};
config = lib.mkMerge [
{
value = [ null ];
}
{
value = [ null ];
}
];
}

View file

@ -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";
};
})
];
}
];
}

View file

@ -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;
}
];
}

View file

@ -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";
};

View file

@ -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>
@ -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>