lib.modules: Add mergeAttrDefinitionsWithPrio

This will let us make assertions involving _module.args.pkgs, which
is not an option but a value attribute, and therefore doesn't have
its own highestPrio to inspect. The new function gives us that info.

+42
lib/modules.nix
···
else opt // { type = opt.type.substSubModules opt.options; options = []; };
/* Properties. */
mkIf = condition: content:
···
importJSON
importTOML
mergeDefinitions
mergeOptionDecls # should be private?
mkAfter
mkAliasAndWrapDefinitions
···
else opt // { type = opt.type.substSubModules opt.options; options = []; };
+
/*
+
Merge an option's definitions in a way that preserves the priority of the
+
individual attributes in the option value.
+
+
This does not account for all option semantics, such as readOnly.
+
+
Type:
+
option -> attrsOf { highestPrio, value }
+
*/
+
mergeAttrDefinitionsWithPrio = opt:
+
let subAttrDefs =
+
lib.concatMap
+
({ value, ... }@def:
+
map
+
(value: def // { inherit value; })
+
(lib.pushDownProperties value)
+
)
+
opt.definitionsWithLocations;
+
defsByAttr =
+
lib.zipAttrs (
+
lib.concatLists (
+
lib.concatMap
+
({ value, ... }@def:
+
map
+
(lib.mapAttrsToList (k: value: { ${k} = def // { inherit value; }; }))
+
(lib.pushDownProperties value)
+
)
+
opt.definitionsWithLocations
+
)
+
);
+
in
+
assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf";
+
lib.mapAttrs
+
(k: v:
+
let merging = lib.mergeDefinitions (opt.loc ++ [k]) opt.type.nestedTypes.elemType v;
+
in {
+
value = merging.mergedValue;
+
inherit (merging.defsFinal') highestPrio;
+
})
+
defsByAttr;
+
/* Properties. */
mkIf = condition: content:
···
importJSON
importTOML
mergeDefinitions
+
mergeAttrDefinitionsWithPrio
mergeOptionDecls # should be private?
mkAfter
mkAliasAndWrapDefinitions
+2
lib/tests/modules.sh
···
# Shorthand meta attribute does not duplicate the config
checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix
# Check boolean option.
checkConfigOutput '^false$' config.enable ./declare-enable.nix
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
···
# Shorthand meta attribute does not duplicate the config
checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix
+
checkConfigOutput '^true$' config.result ./test-mergeAttrDefinitionsWithPrio.nix
+
# Check boolean option.
checkConfigOutput '^false$' config.enable ./declare-enable.nix
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
+21
lib/tests/modules/test-mergeAttrDefinitionsWithPrio.nix
···
···
+
{ lib, options, ... }:
+
+
let
+
defs = lib.modules.mergeAttrDefinitionsWithPrio options._module.args;
+
assertLazy = pos: throw "${pos.file}:${toString pos.line}:${toString pos.column}: The test must not evaluate this the assertLazy thunk, but it did. Unexpected strictness leads to unexpected errors and performance problems.";
+
in
+
+
{
+
options.result = lib.mkOption { };
+
config._module.args = {
+
default = lib.mkDefault (assertLazy __curPos);
+
regular = null;
+
force = lib.mkForce (assertLazy __curPos);
+
unused = assertLazy __curPos;
+
};
+
config.result =
+
assert defs.default.highestPrio == (lib.mkDefault (assertLazy __curPos)).priority;
+
assert defs.regular.highestPrio == lib.modules.defaultOverridePriority;
+
assert defs.force.highestPrio == (lib.mkForce (assertLazy __curPos)).priority;
+
true;
+
}