lib.types.unique: Check inner type deeply

This doesn't change uniq. Why not?

- In NixOS it seems that uniq is only used with
simple types that are fully checked by t.check.

- It exists for much longer and is used more widely.

- I believe we should deprecate it, because unique was
already better.

- unique can be a proving ground.

+66 -6
+28 -5
lib/options.nix
··· 254 254 else if all isInt list && all (x: x == head list) list then head list 255 255 else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; 256 256 257 + /* 258 + Require a single definition. 259 + 260 + WARNING: Does not perform nested checks, as this does not run the merge function! 261 + */ 257 262 mergeOneOption = mergeUniqueOption { message = ""; }; 258 263 259 - mergeUniqueOption = { message }: loc: defs: 260 - if length defs == 1 261 - then (head defs).value 262 - else assert length defs > 1; 263 - throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}"; 264 + /* 265 + Require a single definition. 266 + 267 + NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc). 268 + */ 269 + mergeUniqueOption = args@{ message, merge ? null }: 270 + let 271 + notUnique = loc: defs: 272 + assert length defs > 1; 273 + throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}"; 274 + in 275 + if merge == null 276 + # The inner conditional could be factored out, but this way we take advantage of partial application. 277 + then 278 + loc: defs: 279 + if length defs == 1 280 + then (head defs).value 281 + else notUnique loc defs 282 + else 283 + loc: defs: 284 + if length defs == 1 285 + then merge loc defs 286 + else notUnique loc defs; 264 287 265 288 /* "Merge" option definitions by checking that they all have the same value. */ 266 289 mergeEqualOption = loc: defs:
+10
lib/tests/modules.sh
··· 406 406 checkConfigError 'The option .int.a. is used but not defined' config.int.a ./emptyValues.nix 407 407 checkConfigError 'The option .nonEmptyList.a. is used but not defined' config.nonEmptyList.a ./emptyValues.nix 408 408 409 + # types.unique 410 + # requires a single definition 411 + checkConfigError 'The option .examples\.merged. is defined multiple times while it.s expected to be unique' config.examples.merged.a ./types-unique.nix 412 + # user message is printed 413 + checkConfigError 'We require a single definition, because seeing the whole value at once helps us maintain critical invariants of our system.' config.examples.merged.a ./types-unique.nix 414 + # let the inner merge function check the values (on demand) 415 + checkConfigError 'A definition for option .examples\.badLazyType\.a. is not of type .string.' config.examples.badLazyType.a ./types-unique.nix 416 + # overriding still works (unlike option uniqueness) 417 + checkConfigOutput '^"bee"$' config.examples.override.b ./types-unique.nix 418 + 409 419 ## types.raw 410 420 checkConfigOutput '^true$' config.unprocessedNestingEvaluates.success ./raw.nix 411 421 checkConfigOutput "10" config.processedToplevel ./raw.nix
+27
lib/tests/modules/types-unique.nix
··· 1 + { lib, ... }: 2 + let 3 + inherit (lib) mkOption types; 4 + in 5 + { 6 + options.examples = mkOption { 7 + type = types.lazyAttrsOf 8 + (types.unique 9 + { message = "We require a single definition, because seeing the whole value at once helps us maintain critical invariants of our system."; } 10 + (types.attrsOf types.str)); 11 + }; 12 + imports = [ 13 + { examples.merged = { b = "bee"; }; } 14 + { examples.override = lib.mkForce { b = "bee"; }; } 15 + ]; 16 + config.examples = { 17 + merged = { 18 + a = "aye"; 19 + }; 20 + override = { 21 + a = "aye"; 22 + }; 23 + badLazyType = { 24 + a = true; 25 + }; 26 + }; 27 + }
+1 -1
lib/types.nix
··· 629 629 unique = { message }: type: mkOptionType rec { 630 630 name = "unique"; 631 631 inherit (type) description descriptionClass check; 632 - merge = mergeUniqueOption { inherit message; }; 632 + merge = mergeUniqueOption { inherit message; inherit (type) merge; }; 633 633 emptyValue = type.emptyValue; 634 634 getSubOptions = type.getSubOptions; 635 635 getSubModules = type.getSubModules;