lib.types: fix loaOf behavior for long lists

Assigning a list of 10 or more elements to an option having the type
`loaOf a` produces a configuration value that is not honoring the
order of the original list. This commit fixes this and a related issue
arising when 10 or more lists are merged into this type of option.

+6
lib/tests/modules.sh
···
checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
cat <<EOF
====== module tests ======
$pass Pass
···
checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
+
# Check loaOf with long list.
+
checkConfigOutput "1 2 3 4 5 6 7 8 9 10" config.result ./loaOf-with-long-list.nix
+
+
# Check loaOf with many merges of lists.
+
checkConfigOutput "1 2 3 4 5 6 7 8 9 10" config.result ./loaOf-with-many-list-merges.nix
+
cat <<EOF
====== module tests ======
$pass Pass
+19
lib/tests/modules/loaOf-with-long-list.nix
···
···
+
{ config, lib, ... }:
+
+
{
+
options = {
+
loaOfInt = lib.mkOption {
+
type = lib.types.loaOf lib.types.int;
+
};
+
+
result = lib.mkOption {
+
type = lib.types.str;
+
};
+
};
+
+
config = {
+
loaOfInt = [ 1 2 3 4 5 6 7 8 9 10 ];
+
+
result = toString (lib.attrValues config.loaOfInt);
+
};
+
}
+19
lib/tests/modules/loaOf-with-many-list-merges.nix
···
···
+
{ config, lib, ... }:
+
+
{
+
options = {
+
loaOfInt = lib.mkOption {
+
type = lib.types.loaOf lib.types.int;
+
};
+
+
result = lib.mkOption {
+
type = lib.types.str;
+
};
+
};
+
+
config = {
+
loaOfInt = lib.mkMerge (map lib.singleton [ 1 2 3 4 5 6 7 8 9 10 ]);
+
+
result = toString (lib.attrValues config.loaOfInt);
+
};
+
}
+20 -9
lib/types.nix
···
# List or attribute set of ...
loaOf = elemType:
let
-
convertIfList = defIdx: def:
if isList def.value then
-
{ inherit (def) file;
-
value = listToAttrs (
-
imap1 (elemIdx: elem:
-
{ name = elem.name or "unnamed-${toString defIdx}.${toString elemIdx}";
-
value = elem;
-
}) def.value);
-
}
else
def;
listOnly = listOf elemType;
···
name = "loaOf";
description = "list or attribute set of ${elemType.description}s";
check = x: isList x || isAttrs x;
-
merge = loc: defs: attrOnly.merge loc (imap1 convertIfList defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: loaOf (elemType.substSubModules m);
···
# List or attribute set of ...
loaOf = elemType:
let
+
convertAllLists = defs:
+
let
+
padWidth = stringLength (toString (length defs));
+
unnamedPrefix = i: "unnamed-" + fixedWidthNumber padWidth i + ".";
+
in
+
imap1 (i: convertIfList (unnamedPrefix i)) defs;
+
+
convertIfList = unnamedPrefix: def:
if isList def.value then
+
let
+
padWidth = stringLength (toString (length def.value));
+
unnamed = i: unnamedPrefix + fixedWidthNumber padWidth i;
+
in
+
{ inherit (def) file;
+
value = listToAttrs (
+
imap1 (elemIdx: elem:
+
{ name = elem.name or (unnamed elemIdx);
+
value = elem;
+
}) def.value);
+
}
else
def;
listOnly = listOf elemType;
···
name = "loaOf";
description = "list or attribute set of ${elemType.description}s";
check = x: isList x || isAttrs x;
+
merge = loc: defs: attrOnly.merge loc (convertAllLists defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: loaOf (elemType.substSubModules m);