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.

+65
+42
lib/modules.nix
··· 910 910 else opt // { type = opt.type.substSubModules opt.options; options = []; }; 911 911 912 912 913 + /* 914 + Merge an option's definitions in a way that preserves the priority of the 915 + individual attributes in the option value. 916 + 917 + This does not account for all option semantics, such as readOnly. 918 + 919 + Type: 920 + option -> attrsOf { highestPrio, value } 921 + */ 922 + mergeAttrDefinitionsWithPrio = opt: 923 + let subAttrDefs = 924 + lib.concatMap 925 + ({ value, ... }@def: 926 + map 927 + (value: def // { inherit value; }) 928 + (lib.pushDownProperties value) 929 + ) 930 + opt.definitionsWithLocations; 931 + defsByAttr = 932 + lib.zipAttrs ( 933 + lib.concatLists ( 934 + lib.concatMap 935 + ({ value, ... }@def: 936 + map 937 + (lib.mapAttrsToList (k: value: { ${k} = def // { inherit value; }; })) 938 + (lib.pushDownProperties value) 939 + ) 940 + opt.definitionsWithLocations 941 + ) 942 + ); 943 + in 944 + assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf"; 945 + lib.mapAttrs 946 + (k: v: 947 + let merging = lib.mergeDefinitions (opt.loc ++ [k]) opt.type.nestedTypes.elemType v; 948 + in { 949 + value = merging.mergedValue; 950 + inherit (merging.defsFinal') highestPrio; 951 + }) 952 + defsByAttr; 953 + 913 954 /* Properties. */ 914 955 915 956 mkIf = condition: content: ··· 1256 1297 importJSON 1257 1298 importTOML 1258 1299 mergeDefinitions 1300 + mergeAttrDefinitionsWithPrio 1259 1301 mergeOptionDecls # should be private? 1260 1302 mkAfter 1261 1303 mkAliasAndWrapDefinitions
+2
lib/tests/modules.sh
··· 61 61 # Shorthand meta attribute does not duplicate the config 62 62 checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix 63 63 64 + checkConfigOutput '^true$' config.result ./test-mergeAttrDefinitionsWithPrio.nix 65 + 64 66 # Check boolean option. 65 67 checkConfigOutput '^false$' config.enable ./declare-enable.nix 66 68 checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
+21
lib/tests/modules/test-mergeAttrDefinitionsWithPrio.nix
··· 1 + { lib, options, ... }: 2 + 3 + let 4 + defs = lib.modules.mergeAttrDefinitionsWithPrio options._module.args; 5 + 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."; 6 + in 7 + 8 + { 9 + options.result = lib.mkOption { }; 10 + config._module.args = { 11 + default = lib.mkDefault (assertLazy __curPos); 12 + regular = null; 13 + force = lib.mkForce (assertLazy __curPos); 14 + unused = assertLazy __curPos; 15 + }; 16 + config.result = 17 + assert defs.default.highestPrio == (lib.mkDefault (assertLazy __curPos)).priority; 18 + assert defs.regular.highestPrio == lib.modules.defaultOverridePriority; 19 + assert defs.force.highestPrio == (lib.mkForce (assertLazy __curPos)).priority; 20 + true; 21 + }