lib/types: Introduce lazyAttrsOf (#70138)
lib/types: Introduce lazyAttrsOf
This commit is contained in:
commit
5239b328f8
@ -41,7 +41,13 @@ rec {
|
|||||||
|
|
||||||
options = {
|
options = {
|
||||||
_module.args = mkOption {
|
_module.args = mkOption {
|
||||||
type = types.attrsOf types.unspecified;
|
# Because things like `mkIf` are entirely useless for
|
||||||
|
# `_module.args` (because there's no way modules can check which
|
||||||
|
# arguments were passed), we'll use `lazyAttrsOf` which drops
|
||||||
|
# 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;
|
||||||
internal = true;
|
internal = true;
|
||||||
description = "Arguments passed to each module.";
|
description = "Arguments passed to each module.";
|
||||||
};
|
};
|
||||||
@ -365,16 +371,9 @@ rec {
|
|||||||
else
|
else
|
||||||
mergeDefinitions loc opt.type defs';
|
mergeDefinitions loc opt.type defs';
|
||||||
|
|
||||||
|
|
||||||
# The value with a check that it is defined
|
|
||||||
valueDefined = if res.isDefined then res.mergedValue else
|
|
||||||
# (nixos-option detects this specific error message and gives it special
|
|
||||||
# handling. If changed here, please change it there too.)
|
|
||||||
throw "The option `${showOption loc}' is used but not defined.";
|
|
||||||
|
|
||||||
# Apply the 'apply' function to the merged value. This allows options to
|
# Apply the 'apply' function to the merged value. This allows options to
|
||||||
# yield a value computed from the definitions
|
# yield a value computed from the definitions
|
||||||
value = if opt ? apply then opt.apply valueDefined else valueDefined;
|
value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
|
||||||
|
|
||||||
in opt //
|
in opt //
|
||||||
{ value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
|
{ value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||||
@ -408,11 +407,17 @@ rec {
|
|||||||
};
|
};
|
||||||
defsFinal = defsFinal'.values;
|
defsFinal = defsFinal'.values;
|
||||||
|
|
||||||
# Type-check the remaining definitions, and merge them.
|
# Type-check the remaining definitions, and merge them. Or throw if no definitions.
|
||||||
mergedValue = foldl' (res: def:
|
mergedValue =
|
||||||
if type.check def.value then res
|
if isDefined then
|
||||||
else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.")
|
foldl' (res: def:
|
||||||
(type.merge loc defsFinal) defsFinal;
|
if type.check def.value then res
|
||||||
|
else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'."
|
||||||
|
) (type.merge loc defsFinal) defsFinal
|
||||||
|
else
|
||||||
|
# (nixos-option detects this specific error message and gives it special
|
||||||
|
# handling. If changed here, please change it there too.)
|
||||||
|
throw "The option `${showOption loc}' is used but not defined.";
|
||||||
|
|
||||||
isDefined = defsFinal != [];
|
isDefined = defsFinal != [];
|
||||||
|
|
||||||
|
@ -186,6 +186,15 @@ checkConfigError 'The option .* defined in .* does not exist' config.enable ./di
|
|||||||
# Check that imports can depend on derivations
|
# Check that imports can depend on derivations
|
||||||
checkConfigOutput "true" config.enable ./import-from-store.nix
|
checkConfigOutput "true" config.enable ./import-from-store.nix
|
||||||
|
|
||||||
|
# Check attrsOf and lazyAttrsOf. Only lazyAttrsOf should be lazy, and only
|
||||||
|
# attrsOf should work with conditional definitions
|
||||||
|
# In addition, lazyAttrsOf should honor an options emptyValue
|
||||||
|
checkConfigError "is not lazy" config.isLazy ./declare-attrsOf.nix ./attrsOf-lazy-check.nix
|
||||||
|
checkConfigOutput "true" config.isLazy ./declare-lazyAttrsOf.nix ./attrsOf-lazy-check.nix
|
||||||
|
checkConfigOutput "true" config.conditionalWorks ./declare-attrsOf.nix ./attrsOf-conditional-check.nix
|
||||||
|
checkConfigOutput "false" config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
|
||||||
|
checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
====== module tests ======
|
====== module tests ======
|
||||||
$pass Pass
|
$pass Pass
|
||||||
|
7
lib/tests/modules/attrsOf-conditional-check.nix
Normal file
7
lib/tests/modules/attrsOf-conditional-check.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{ lib, config, ... }: {
|
||||||
|
options.conditionalWorks = lib.mkOption {
|
||||||
|
default = ! config.value ? foo;
|
||||||
|
};
|
||||||
|
|
||||||
|
config.value.foo = lib.mkIf false "should not be defined";
|
||||||
|
}
|
7
lib/tests/modules/attrsOf-lazy-check.nix
Normal file
7
lib/tests/modules/attrsOf-lazy-check.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{ lib, config, ... }: {
|
||||||
|
options.isLazy = lib.mkOption {
|
||||||
|
default = ! config.value ? foo;
|
||||||
|
};
|
||||||
|
|
||||||
|
config.value.bar = throw "is not lazy";
|
||||||
|
}
|
6
lib/tests/modules/declare-attrsOf.nix
Normal file
6
lib/tests/modules/declare-attrsOf.nix
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{ lib, ... }: {
|
||||||
|
options.value = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
}
|
6
lib/tests/modules/declare-lazyAttrsOf.nix
Normal file
6
lib/tests/modules/declare-lazyAttrsOf.nix
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{ lib, ... }: {
|
||||||
|
options.value = lib.mkOption {
|
||||||
|
type = lib.types.lazyAttrsOf (lib.types.str // { emptyValue.value = "empty"; });
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
}
|
@ -65,6 +65,11 @@ rec {
|
|||||||
# definition values and locations (e.g. [ { file = "/foo.nix";
|
# definition values and locations (e.g. [ { file = "/foo.nix";
|
||||||
# value = 1; } { file = "/bar.nix"; value = 2 } ]).
|
# value = 1; } { file = "/bar.nix"; value = 2 } ]).
|
||||||
merge ? mergeDefaultOption
|
merge ? mergeDefaultOption
|
||||||
|
, # Whether this type has a value representing nothingness. If it does,
|
||||||
|
# this should be a value of the form { value = <the nothing value>; }
|
||||||
|
# If it doesn't, this should be {}
|
||||||
|
# This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
|
||||||
|
emptyValue ? {}
|
||||||
, # Return a flat list of sub-options. Used to generate
|
, # Return a flat list of sub-options. Used to generate
|
||||||
# documentation.
|
# documentation.
|
||||||
getSubOptions ? prefix: {}
|
getSubOptions ? prefix: {}
|
||||||
@ -88,7 +93,7 @@ rec {
|
|||||||
functor ? defaultFunctor name
|
functor ? defaultFunctor name
|
||||||
}:
|
}:
|
||||||
{ _type = "option-type";
|
{ _type = "option-type";
|
||||||
inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor;
|
inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor;
|
||||||
description = if description == null then name else description;
|
description = if description == null then name else description;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -225,6 +230,7 @@ rec {
|
|||||||
description = "attribute set";
|
description = "attribute set";
|
||||||
check = isAttrs;
|
check = isAttrs;
|
||||||
merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
|
merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
|
||||||
|
emptyValue = { value = {}; };
|
||||||
};
|
};
|
||||||
|
|
||||||
# derivation is a reserved keyword.
|
# derivation is a reserved keyword.
|
||||||
@ -265,6 +271,7 @@ rec {
|
|||||||
) def.value
|
) def.value
|
||||||
else
|
else
|
||||||
throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
|
throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
|
||||||
|
emptyValue = { value = {}; };
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: listOf (elemType.substSubModules m);
|
substSubModules = m: listOf (elemType.substSubModules m);
|
||||||
@ -273,7 +280,10 @@ rec {
|
|||||||
|
|
||||||
nonEmptyListOf = elemType:
|
nonEmptyListOf = elemType:
|
||||||
let list = addCheck (types.listOf elemType) (l: l != []);
|
let list = addCheck (types.listOf elemType) (l: l != []);
|
||||||
in list // { description = "non-empty " + list.description; };
|
in list // {
|
||||||
|
description = "non-empty " + list.description;
|
||||||
|
# Note: emptyValue is left as is, because another module may define an element.
|
||||||
|
};
|
||||||
|
|
||||||
attrsOf = elemType: mkOptionType rec {
|
attrsOf = elemType: mkOptionType rec {
|
||||||
name = "attrsOf";
|
name = "attrsOf";
|
||||||
@ -285,12 +295,37 @@ rec {
|
|||||||
)
|
)
|
||||||
# Push down position info.
|
# Push down position info.
|
||||||
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
|
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
|
||||||
|
emptyValue = { value = {}; };
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: attrsOf (elemType.substSubModules m);
|
substSubModules = m: attrsOf (elemType.substSubModules m);
|
||||||
functor = (defaultFunctor name) // { wrapped = elemType; };
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# A version of attrsOf that's lazy in its values at the expense of
|
||||||
|
# conditional definitions not working properly. E.g. defining a value with
|
||||||
|
# `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
|
||||||
|
# attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
|
||||||
|
# error that it's not defined. Use only if conditional definitions don't make sense.
|
||||||
|
lazyAttrsOf = elemType: mkOptionType rec {
|
||||||
|
name = "lazyAttrsOf";
|
||||||
|
description = "lazy attribute set of ${elemType.description}s";
|
||||||
|
check = isAttrs;
|
||||||
|
merge = loc: defs:
|
||||||
|
zipAttrsWith (name: defs:
|
||||||
|
let merged = mergeDefinitions (loc ++ [name]) elemType defs;
|
||||||
|
# mergedValue will trigger an appropriate error when accessed
|
||||||
|
in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
|
||||||
|
)
|
||||||
|
# Push down position info.
|
||||||
|
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
|
||||||
|
emptyValue = { value = {}; };
|
||||||
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
||||||
|
getSubModules = elemType.getSubModules;
|
||||||
|
substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
|
};
|
||||||
|
|
||||||
# List or attribute set of ...
|
# List or attribute set of ...
|
||||||
loaOf = elemType:
|
loaOf = elemType:
|
||||||
let
|
let
|
||||||
@ -339,6 +374,7 @@ rec {
|
|||||||
description = "list or attribute set of ${elemType.description}s";
|
description = "list or attribute set of ${elemType.description}s";
|
||||||
check = x: isList x || isAttrs x;
|
check = x: isList x || isAttrs x;
|
||||||
merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs);
|
merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs);
|
||||||
|
emptyValue = { value = {}; };
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: loaOf (elemType.substSubModules m);
|
substSubModules = m: loaOf (elemType.substSubModules m);
|
||||||
@ -350,6 +386,7 @@ rec {
|
|||||||
name = "uniq";
|
name = "uniq";
|
||||||
inherit (elemType) description check;
|
inherit (elemType) description check;
|
||||||
merge = mergeOneOption;
|
merge = mergeOneOption;
|
||||||
|
emptyValue = elemType.emptyValue;
|
||||||
getSubOptions = elemType.getSubOptions;
|
getSubOptions = elemType.getSubOptions;
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: uniq (elemType.substSubModules m);
|
substSubModules = m: uniq (elemType.substSubModules m);
|
||||||
@ -367,6 +404,7 @@ rec {
|
|||||||
else if nrNulls != 0 then
|
else if nrNulls != 0 then
|
||||||
throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
|
throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
|
||||||
else elemType.merge loc defs;
|
else elemType.merge loc defs;
|
||||||
|
emptyValue = { value = null; };
|
||||||
getSubOptions = elemType.getSubOptions;
|
getSubOptions = elemType.getSubOptions;
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: nullOr (elemType.substSubModules m);
|
substSubModules = m: nullOr (elemType.substSubModules m);
|
||||||
@ -407,6 +445,7 @@ rec {
|
|||||||
args.name = last loc;
|
args.name = last loc;
|
||||||
prefix = loc;
|
prefix = loc;
|
||||||
}).config;
|
}).config;
|
||||||
|
emptyValue = { value = {}; };
|
||||||
getSubOptions = prefix: (evalModules
|
getSubOptions = prefix: (evalModules
|
||||||
{ inherit modules prefix specialArgs;
|
{ inherit modules prefix specialArgs;
|
||||||
# This is a work-around due to the fact that some sub-modules,
|
# This is a work-around due to the fact that some sub-modules,
|
||||||
@ -515,6 +554,7 @@ rec {
|
|||||||
if finalType.check val then val
|
if finalType.check val then val
|
||||||
else coerceFunc val;
|
else coerceFunc val;
|
||||||
in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
|
in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
|
||||||
|
emptyValue = finalType.emptyValue;
|
||||||
getSubOptions = finalType.getSubOptions;
|
getSubOptions = finalType.getSubOptions;
|
||||||
getSubModules = finalType.getSubModules;
|
getSubModules = finalType.getSubModules;
|
||||||
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
|
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
|
||||||
|
@ -352,6 +352,36 @@
|
|||||||
An attribute set of where all the values are of
|
An attribute set of where all the values are of
|
||||||
<replaceable>t</replaceable> type. Multiple definitions result in the
|
<replaceable>t</replaceable> type. Multiple definitions result in the
|
||||||
joined attribute set.
|
joined attribute set.
|
||||||
|
<note><para>
|
||||||
|
This type is <emphasis>strict</emphasis> in its values, which in turn
|
||||||
|
means attributes cannot depend on other attributes. See <varname>
|
||||||
|
types.lazyAttrsOf</varname> for a lazy version.
|
||||||
|
</para></note>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
<varname>types.lazyAttrsOf</varname> <replaceable>t</replaceable>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
An attribute set of where all the values are of
|
||||||
|
<replaceable>t</replaceable> type. Multiple definitions result in the
|
||||||
|
joined attribute set. This is the lazy version of <varname>types.attrsOf
|
||||||
|
</varname>, allowing attributes to depend on each other.
|
||||||
|
<warning><para>
|
||||||
|
This version does not fully support conditional definitions! With an
|
||||||
|
option <varname>foo</varname> of this type and a definition
|
||||||
|
<literal>foo.attr = lib.mkIf false 10</literal>, evaluating
|
||||||
|
<literal>foo ? attr</literal> will return <literal>true</literal>
|
||||||
|
even though it should be false. Accessing the value will then throw
|
||||||
|
an error. For types <replaceable>t</replaceable> that have an
|
||||||
|
<literal>emptyValue</literal> defined, that value will be returned
|
||||||
|
instead of throwing an error. So if the type of <literal>foo.attr</literal>
|
||||||
|
was <literal>lazyAttrsOf (nullOr int)</literal>, <literal>null</literal>
|
||||||
|
would be returned instead for the same <literal>mkIf false</literal> definition.
|
||||||
|
</para></warning>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user