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.
···910 else opt // { type = opt.type.substSubModules opt.options; options = []; };
911912913+ /*
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+954 /* Properties. */
955956 mkIf = condition: content:
···1297 importJSON
1298 importTOML
1299 mergeDefinitions
1300+ mergeAttrDefinitionsWithPrio
1301 mergeOptionDecls # should be private?
1302 mkAfter
1303 mkAliasAndWrapDefinitions
+2
lib/tests/modules.sh
···61# Shorthand meta attribute does not duplicate the config
62checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix
630064# Check boolean option.
65checkConfigOutput '^false$' config.enable ./declare-enable.nix
66checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
···61# Shorthand meta attribute does not duplicate the config
62checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix
6364+checkConfigOutput '^true$' config.result ./test-mergeAttrDefinitionsWithPrio.nix
65+66# Check boolean option.
67checkConfigOutput '^false$' config.enable ./declare-enable.nix
68checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix