nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at release-25.11 1701 lines 61 kB view raw
1# Definitions related to run-time type checking. Used in particular 2# to type-check NixOS configurations. 3{ lib }: 4 5let 6 inherit (lib) 7 elem 8 flip 9 isAttrs 10 isBool 11 isDerivation 12 isFloat 13 isFunction 14 isInt 15 isList 16 isString 17 isStorePath 18 throwIf 19 toDerivation 20 toList 21 ; 22 inherit (lib.lists) 23 concatLists 24 count 25 elemAt 26 filter 27 foldl' 28 head 29 imap1 30 last 31 length 32 tail 33 ; 34 inherit (lib.attrsets) 35 attrNames 36 filterAttrs 37 hasAttr 38 mapAttrs 39 optionalAttrs 40 zipAttrsWith 41 ; 42 inherit (lib.options) 43 getFiles 44 getValues 45 mergeDefaultOption 46 mergeEqualOption 47 mergeOneOption 48 mergeUniqueOption 49 showFiles 50 showDefs 51 showOption 52 ; 53 inherit (lib.strings) 54 concatMapStringsSep 55 concatStringsSep 56 escapeNixString 57 hasInfix 58 isStringLike 59 ; 60 inherit (lib.trivial) 61 boolToString 62 ; 63 64 inherit (lib.modules) 65 mergeDefinitions 66 fixupOptionType 67 mergeOptionDecls 68 ; 69 inherit (lib.fileset) 70 isFileset 71 unions 72 empty 73 ; 74 75 inAttrPosSuffix = 76 v: name: 77 let 78 pos = builtins.unsafeGetAttrPos name v; 79 in 80 if pos == null then "" else " at ${pos.file}:${toString pos.line}:${toString pos.column}"; 81 82 # Internal functor to help for migrating functor.wrapped to functor.payload.elemType 83 # Note that individual attributes can be overridden if needed. 84 elemTypeFunctor = 85 name: 86 { elemType, ... }@payload: 87 { 88 inherit name payload; 89 wrappedDeprecationMessage = makeWrappedDeprecationMessage payload; 90 type = outer_types.types.${name}; 91 binOp = 92 a: b: 93 let 94 merged = a.elemType.typeMerge b.elemType.functor; 95 in 96 if merged == null then null else { elemType = merged; }; 97 }; 98 makeWrappedDeprecationMessage = 99 payload: 100 { loc }: 101 lib.warn '' 102 The deprecated `${lib.optionalString (loc != null) "type."}functor.wrapped` attribute ${ 103 lib.optionalString (loc != null) "of the option `${showOption loc}` " 104 }is accessed, use `${lib.optionalString (loc != null) "type."}nestedTypes.elemType` instead. 105 '' payload.elemType; 106 107 checkDefsForError = 108 check: loc: defs: 109 let 110 invalidDefs = filter (def: !check def.value) defs; 111 in 112 if invalidDefs != [ ] then { message = "Definition values: ${showDefs invalidDefs}"; } else null; 113 114 # Check that a type with v2 merge has a coherent check attribute. 115 # Throws an error if the type uses an ad-hoc `type // { check }` override. 116 # Returns the last argument like `seq`, allowing usage: checkV2MergeCoherence loc type expr 117 checkV2MergeCoherence = 118 loc: type: result: 119 if type.check.isV2MergeCoherent or false then 120 result 121 else 122 throw '' 123 The option `${showOption loc}' has a type `${type.description}' that uses 124 an ad-hoc `type // { check = ...; }' override, which is incompatible with 125 the v2 merge mechanism. 126 127 Please use `lib.types.addCheck` instead of `type // { check }' to add 128 custom validation. For example: 129 130 lib.types.addCheck baseType (value: /* your check */) 131 132 instead of: 133 134 baseType // { check = value: /* your check */; } 135 ''; 136 137 outer_types = rec { 138 isType = type: x: (x._type or "") == type; 139 140 setType = 141 typeName: value: 142 value 143 // { 144 _type = typeName; 145 }; 146 147 # Default type merging function 148 # takes two type functors and return the merged type 149 defaultTypeMerge = 150 f: f': 151 let 152 mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor; 153 mergedPayload = f.binOp f.payload f'.payload; 154 155 hasPayload = 156 assert (f'.payload != null) == (f.payload != null); 157 f.payload != null; 158 hasWrapped = 159 assert (f'.wrapped != null) == (f.wrapped != null); 160 f.wrapped != null; 161 162 typeFromPayload = if mergedPayload == null then null else f.type mergedPayload; 163 typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped; 164 in 165 # Abort early: cannot merge different types 166 if f.name != f'.name then 167 null 168 else 169 170 if hasPayload then 171 # Just return the payload if returning wrapped is deprecated 172 if f ? wrappedDeprecationMessage then 173 typeFromPayload 174 else if hasWrapped then 175 # Has both wrapped and payload 176 throw '' 177 Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported. 178 179 Use either `functor.payload` or `functor.wrapped` but not both. 180 181 If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition. 182 '' 183 else 184 typeFromPayload 185 else if hasWrapped then 186 typeFromWrapped 187 else 188 f.type; 189 190 # Default type functor 191 defaultFunctor = name: { 192 inherit name; 193 type = types.${name} or null; 194 wrapped = null; 195 payload = null; 196 binOp = a: b: null; 197 }; 198 199 isOptionType = isType "option-type"; 200 mkOptionType = 201 { 202 # Human-readable representation of the type, should be equivalent to 203 # the type function name. 204 name, 205 # Description of the type, defined recursively by embedding the wrapped type if any. 206 description ? null, 207 # A hint for whether or not this description needs parentheses. Possible values: 208 # - "noun": a noun phrase 209 # Example description: "positive integer", 210 # - "conjunction": a phrase with a potentially ambiguous "or" connective 211 # Example description: "int or string" 212 # - "composite": a phrase with an "of" connective 213 # Example description: "list of string" 214 # - "nonRestrictiveClause": a noun followed by a comma and a clause 215 # Example description: "positive integer, meaning >0" 216 # See the `optionDescriptionPhrase` function. 217 descriptionClass ? null, 218 # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING! 219 # Function applied to each definition that must return false when a definition 220 # does not match the type. It should not check more than the root of the value, 221 # because checking nested values reduces laziness, leading to unnecessary 222 # infinite recursions in the module system. 223 # Further checks of nested values should be performed by throwing in 224 # the merge function. 225 # Strict and deep type checking can be performed by calling lib.deepSeq on 226 # the merged value. 227 # 228 # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change, 229 # https://github.com/NixOS/nixpkgs/pull/173568 and 230 # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this, 231 # https://github.com/NixOS/nixpkgs/issues/191124 and 232 # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore 233 # this disclaimer. 234 check ? (x: true), 235 # Merge a list of definitions together into a single value. 236 # This function is called with two arguments: the location of 237 # the option in the configuration as a list of strings 238 # (e.g. ["boot" "loader "grub" "enable"]), and a list of 239 # definition values and locations (e.g. [ { file = "/foo.nix"; 240 # value = 1; } { file = "/bar.nix"; value = 2 } ]). 241 merge ? mergeDefaultOption, 242 # Whether this type has a value representing nothingness. If it does, 243 # this should be a value of the form { value = <the nothing value>; } 244 # If it doesn't, this should be {} 245 # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`. 246 emptyValue ? { }, 247 # Return a flat attrset of sub-options. Used to generate 248 # documentation. 249 getSubOptions ? prefix: { }, 250 # List of modules if any, or null if none. 251 getSubModules ? null, 252 # Function for building the same option type with a different list of 253 # modules. 254 substSubModules ? m: null, 255 # Function that merge type declarations. 256 # internal, takes a functor as argument and returns the merged type. 257 # returning null means the type is not mergeable 258 typeMerge ? defaultTypeMerge functor, 259 # The type functor. 260 # internal, representation of the type as an attribute set. 261 # name: name of the type 262 # type: type function. 263 # wrapped: the type wrapped in case of compound types. 264 # payload: values of the type, two payloads of the same type must be 265 # combinable with the binOp binary operation. 266 # binOp: binary operation that merge two payloads of the same type. 267 functor ? defaultFunctor name, 268 # The deprecation message to display when this type is used by an option 269 # If null, the type isn't deprecated 270 deprecationMessage ? null, 271 # The types that occur in the definition of this type. This is used to 272 # issue deprecation warnings recursively. Can also be used to reuse 273 # nested types 274 nestedTypes ? { }, 275 }: 276 { 277 _type = "option-type"; 278 inherit 279 name 280 check 281 merge 282 emptyValue 283 getSubOptions 284 getSubModules 285 substSubModules 286 typeMerge 287 deprecationMessage 288 nestedTypes 289 descriptionClass 290 ; 291 functor = 292 if functor ? wrappedDeprecationMessage then 293 functor 294 // { 295 wrapped = functor.wrappedDeprecationMessage { 296 loc = null; 297 }; 298 } 299 else 300 functor; 301 description = if description == null then name else description; 302 }; 303 304 # optionDescriptionPhrase :: (str -> bool) -> optionType -> str 305 # 306 # Helper function for producing unambiguous but readable natural language 307 # descriptions of types. 308 # 309 # Parameters 310 # 311 # optionDescriptionPhase unparenthesize optionType 312 # 313 # `unparenthesize`: A function from descriptionClass string to boolean. 314 # It must return true when the class of phrase will fit unambiguously into 315 # the description of the caller. 316 # 317 # `optionType`: The option type to parenthesize or not. 318 # The option whose description we're returning. 319 # 320 # Returns value 321 # 322 # The description of the `optionType`, with parentheses if there may be an 323 # ambiguity. 324 optionDescriptionPhrase = 325 unparenthesize: t: 326 if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})"; 327 328 noCheckForDocsModule = { 329 # When generating documentation, our goal isn't to check anything. 330 # Quite the opposite in fact. Generating docs is somewhat of a 331 # challenge, evaluating modules in a *lacking* context. Anything 332 # that makes the docs avoid an error is a win. 333 config._module.check = lib.mkForce false; 334 _file = "<built-in module that disables checks for the purpose of documentation generation>"; 335 }; 336 337 # When adding new types don't forget to document them in 338 # nixos/doc/manual/development/option-types.section.md! 339 types = rec { 340 341 raw = mkOptionType { 342 name = "raw"; 343 description = "raw value"; 344 descriptionClass = "noun"; 345 check = value: true; 346 merge = mergeOneOption; 347 }; 348 349 anything = mkOptionType { 350 name = "anything"; 351 description = "anything"; 352 descriptionClass = "noun"; 353 check = value: true; 354 merge = 355 loc: defs: 356 let 357 getType = 358 value: if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value; 359 360 # Returns the common type of all definitions, throws an error if they 361 # don't have the same type 362 commonType = foldl' ( 363 type: def: 364 if getType def.value == type then 365 type 366 else 367 throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" 368 ) (getType (head defs).value) defs; 369 370 mergeFunction = 371 { 372 # Recursively merge attribute sets 373 set = (attrsOf anything).merge; 374 # This is the type of packages, only accept a single definition 375 stringCoercibleSet = mergeOneOption; 376 lambda = 377 loc: defs: arg: 378 anything.merge (loc ++ [ "<function body>" ]) ( 379 map (def: { 380 file = def.file; 381 value = def.value arg; 382 }) defs 383 ); 384 # Otherwise fall back to only allowing all equal definitions 385 } 386 .${commonType} or mergeEqualOption; 387 in 388 mergeFunction loc defs; 389 }; 390 391 unspecified = mkOptionType { 392 name = "unspecified"; 393 description = "unspecified value"; 394 descriptionClass = "noun"; 395 }; 396 397 bool = mkOptionType { 398 name = "bool"; 399 description = "boolean"; 400 descriptionClass = "noun"; 401 check = isBool; 402 merge = mergeEqualOption; 403 }; 404 405 boolByOr = mkOptionType { 406 name = "boolByOr"; 407 description = "boolean (merged using or)"; 408 descriptionClass = "noun"; 409 check = isBool; 410 merge = 411 loc: defs: 412 foldl' ( 413 result: def: 414 # Under the assumption that .check always runs before merge, we can assume that all defs.*.value 415 # have been forced, and therefore we assume we don't introduce order-dependent strictness here 416 result || def.value 417 ) false defs; 418 }; 419 420 int = mkOptionType { 421 name = "int"; 422 description = "signed integer"; 423 descriptionClass = "noun"; 424 check = isInt; 425 merge = mergeEqualOption; 426 }; 427 428 # Specialized subdomains of int 429 ints = 430 let 431 betweenDesc = lowest: highest: "${toString lowest} and ${toString highest} (both inclusive)"; 432 between = 433 lowest: highest: 434 assert lib.assertMsg (lowest <= highest) "ints.between: lowest must be smaller than highest"; 435 addCheck int (x: x >= lowest && x <= highest) 436 // { 437 name = "intBetween"; 438 description = "integer between ${betweenDesc lowest highest}"; 439 }; 440 ign = 441 lowest: highest: name: docStart: 442 between lowest highest 443 // { 444 inherit name; 445 description = docStart + "; between ${betweenDesc lowest highest}"; 446 }; 447 unsign = 448 bit: range: ign 0 (range - 1) "unsignedInt${toString bit}" "${toString bit} bit unsigned integer"; 449 sign = 450 bit: range: 451 ign (0 - (range / 2)) ( 452 range / 2 - 1 453 ) "signedInt${toString bit}" "${toString bit} bit signed integer"; 454 455 in 456 { 457 # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md 458 /** 459 An int with a fixed range. 460 461 # Example 462 :::{.example} 463 ## `lib.types.ints.between` usage example 464 465 ```nix 466 (ints.between 0 100).check (-1) 467 => false 468 (ints.between 0 100).check (101) 469 => false 470 (ints.between 0 0).check 0 471 => true 472 ``` 473 474 ::: 475 */ 476 inherit between; 477 478 unsigned = addCheck types.int (x: x >= 0) // { 479 name = "unsignedInt"; 480 description = "unsigned integer, meaning >=0"; 481 descriptionClass = "nonRestrictiveClause"; 482 }; 483 positive = addCheck types.int (x: x > 0) // { 484 name = "positiveInt"; 485 description = "positive integer, meaning >0"; 486 descriptionClass = "nonRestrictiveClause"; 487 }; 488 u8 = unsign 8 256; 489 u16 = unsign 16 65536; 490 # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808) 491 # the smallest int Nix accepts is -2^63 (-9223372036854775807) 492 u32 = unsign 32 4294967296; 493 # u64 = unsign 64 18446744073709551616; 494 495 s8 = sign 8 256; 496 s16 = sign 16 65536; 497 s32 = sign 32 4294967296; 498 }; 499 500 # Alias of u16 for a port number 501 port = ints.u16; 502 503 float = mkOptionType { 504 name = "float"; 505 description = "floating point number"; 506 descriptionClass = "noun"; 507 check = isFloat; 508 merge = mergeEqualOption; 509 }; 510 511 number = either int float; 512 513 numbers = 514 let 515 betweenDesc = 516 lowest: highest: "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)"; 517 in 518 { 519 between = 520 lowest: highest: 521 assert lib.assertMsg (lowest <= highest) "numbers.between: lowest must be smaller than highest"; 522 addCheck number (x: x >= lowest && x <= highest) 523 // { 524 name = "numberBetween"; 525 description = "integer or floating point number between ${betweenDesc lowest highest}"; 526 }; 527 528 nonnegative = addCheck number (x: x >= 0) // { 529 name = "numberNonnegative"; 530 description = "nonnegative integer or floating point number, meaning >=0"; 531 descriptionClass = "nonRestrictiveClause"; 532 }; 533 positive = addCheck number (x: x > 0) // { 534 name = "numberPositive"; 535 description = "positive integer or floating point number, meaning >0"; 536 descriptionClass = "nonRestrictiveClause"; 537 }; 538 }; 539 540 str = mkOptionType { 541 name = "str"; 542 description = "string"; 543 descriptionClass = "noun"; 544 check = isString; 545 merge = mergeEqualOption; 546 }; 547 548 nonEmptyStr = mkOptionType { 549 name = "nonEmptyStr"; 550 description = "non-empty string"; 551 descriptionClass = "noun"; 552 check = x: str.check x && builtins.match "[ \t\n]*" x == null; 553 inherit (str) merge; 554 }; 555 556 # Allow a newline character at the end and trim it in the merge function. 557 singleLineStr = 558 let 559 inherit (strMatching "[^\n\r]*\n?") check merge; 560 in 561 mkOptionType { 562 name = "singleLineStr"; 563 description = "(optionally newline-terminated) single-line string"; 564 descriptionClass = "noun"; 565 inherit check; 566 merge = loc: defs: lib.removeSuffix "\n" (merge loc defs); 567 }; 568 569 strMatching = 570 pattern: 571 mkOptionType { 572 name = "strMatching ${escapeNixString pattern}"; 573 description = "string matching the pattern ${pattern}"; 574 descriptionClass = "noun"; 575 check = x: str.check x && builtins.match pattern x != null; 576 inherit (str) merge; 577 functor = defaultFunctor "strMatching" // { 578 type = payload: strMatching payload.pattern; 579 payload = { inherit pattern; }; 580 binOp = lhs: rhs: if lhs == rhs then lhs else null; 581 }; 582 }; 583 584 # Merge multiple definitions by concatenating them (with the given 585 # separator between the values). 586 separatedString = 587 sep: 588 mkOptionType rec { 589 name = "separatedString"; 590 description = "strings concatenated with ${builtins.toJSON sep}"; 591 descriptionClass = "noun"; 592 check = isString; 593 merge = loc: defs: concatStringsSep sep (getValues defs); 594 functor = (defaultFunctor name) // { 595 payload = { inherit sep; }; 596 type = payload: types.separatedString payload.sep; 597 binOp = lhs: rhs: if lhs.sep == rhs.sep then { inherit (lhs) sep; } else null; 598 }; 599 }; 600 601 lines = separatedString "\n"; 602 commas = separatedString ","; 603 envVar = separatedString ":"; 604 605 passwdEntry = 606 entryType: 607 addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) 608 // { 609 name = "passwdEntry ${entryType.name}"; 610 description = "${ 611 optionDescriptionPhrase (class: class == "noun") entryType 612 }, not containing newlines or colons"; 613 descriptionClass = "nonRestrictiveClause"; 614 }; 615 616 attrs = mkOptionType { 617 name = "attrs"; 618 description = "attribute set"; 619 check = isAttrs; 620 merge = loc: foldl' (res: def: res // def.value) { }; 621 emptyValue = { 622 value = { }; 623 }; 624 }; 625 626 fileset = mkOptionType { 627 name = "fileset"; 628 description = "fileset"; 629 descriptionClass = "noun"; 630 check = isFileset; 631 merge = loc: defs: unions (map (x: x.value) defs); 632 emptyValue.value = empty; 633 }; 634 635 # A package is a top-level store path (/nix/store/hash-name). This includes: 636 # - derivations 637 # - more generally, attribute sets with an `outPath` or `__toString` attribute 638 # pointing to a store path, e.g. flake inputs 639 # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo) 640 # - hardcoded store path literals (/nix/store/hash-foo) or strings without context 641 # ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath. 642 # If you don't need a *top-level* store path, consider using pathInStore instead. 643 package = mkOptionType { 644 name = "package"; 645 descriptionClass = "noun"; 646 check = x: isDerivation x || isStorePath x; 647 merge = 648 loc: defs: 649 let 650 res = mergeOneOption loc defs; 651 in 652 if builtins.isPath res || (builtins.isString res && !builtins.hasContext res) then 653 toDerivation res 654 else 655 res; 656 }; 657 658 shellPackage = package // { 659 check = x: isDerivation x && hasAttr "shellPath" x; 660 }; 661 662 pkgs = addCheck ( 663 unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs 664 // { 665 name = "pkgs"; 666 descriptionClass = "noun"; 667 description = "Nixpkgs package set"; 668 } 669 ) (x: (x._type or null) == "pkgs"); 670 671 path = pathWith { 672 absolute = true; 673 }; 674 675 pathInStore = pathWith { 676 inStore = true; 677 }; 678 679 externalPath = pathWith { 680 absolute = true; 681 inStore = false; 682 }; 683 684 pathWith = 685 { 686 inStore ? null, 687 absolute ? null, 688 }: 689 throwIf (inStore != null && absolute != null && inStore && !absolute) 690 "In pathWith, inStore means the path must be absolute" 691 mkOptionType 692 { 693 name = "path"; 694 description = ( 695 (if absolute == null then "" else (if absolute then "absolute " else "relative ")) 696 + "path" 697 + ( 698 if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store") 699 ) 700 ); 701 descriptionClass = "noun"; 702 703 merge = mergeEqualOption; 704 functor = defaultFunctor "path" // { 705 type = pathWith; 706 payload = { inherit inStore absolute; }; 707 binOp = lhs: rhs: if lhs == rhs then lhs else null; 708 }; 709 710 check = 711 x: 712 let 713 isInStore = lib.path.hasStorePathPrefix ( 714 if builtins.isPath x then 715 x 716 # Discarding string context is necessary to convert the value to 717 # a path and safe as the result is never used in any derivation. 718 else 719 /. + builtins.unsafeDiscardStringContext x 720 ); 721 isAbsolute = builtins.substring 0 1 (toString x) == "/"; 722 isExpectedType = ( 723 if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on. 724 ); 725 in 726 isExpectedType 727 && (inStore == null || inStore == isInStore) 728 && (absolute == null || absolute == isAbsolute); 729 }; 730 731 listOf = 732 elemType: 733 mkOptionType rec { 734 name = "listOf"; 735 description = "list of ${ 736 optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType 737 }"; 738 descriptionClass = "composite"; 739 check = { 740 __functor = _self: isList; 741 isV2MergeCoherent = true; 742 }; 743 merge = { 744 __functor = 745 self: loc: defs: 746 (self.v2 { inherit loc defs; }).value; 747 v2 = 748 { loc, defs }: 749 let 750 evals = filter (x: x.optionalValue ? value) ( 751 concatLists ( 752 imap1 ( 753 n: def: 754 imap1 ( 755 m: def': 756 (mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [ 757 { 758 inherit (def) file; 759 value = def'; 760 } 761 ]) 762 ) def.value 763 ) defs 764 ) 765 ); 766 in 767 { 768 headError = checkDefsForError check loc defs; 769 value = map (x: x.optionalValue.value or x.mergedValue) evals; 770 valueMeta.list = map (v: v.checkedAndMerged.valueMeta) evals; 771 }; 772 }; 773 emptyValue = { 774 value = [ ]; 775 }; 776 getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]); 777 getSubModules = elemType.getSubModules; 778 substSubModules = m: listOf (elemType.substSubModules m); 779 functor = (elemTypeFunctor name { inherit elemType; }) // { 780 type = payload: types.listOf payload.elemType; 781 }; 782 nestedTypes.elemType = elemType; 783 }; 784 785 nonEmptyListOf = 786 elemType: 787 let 788 list = addCheck (types.listOf elemType) (l: l != [ ]); 789 in 790 list 791 // { 792 description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}"; 793 emptyValue = { }; # no .value attr, meaning unset 794 substSubModules = m: nonEmptyListOf (elemType.substSubModules m); 795 }; 796 797 attrsOf = elemType: attrsWith { inherit elemType; }; 798 799 # A version of attrsOf that's lazy in its values at the expense of 800 # conditional definitions not working properly. E.g. defining a value with 801 # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with 802 # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an 803 # error that it's not defined. Use only if conditional definitions don't make sense. 804 lazyAttrsOf = 805 elemType: 806 attrsWith { 807 inherit elemType; 808 lazy = true; 809 }; 810 811 # base type for lazyAttrsOf and attrsOf 812 attrsWith = 813 let 814 # Push down position info. 815 pushPositions = map ( 816 def: 817 mapAttrs (n: v: { 818 inherit (def) file; 819 value = v; 820 }) def.value 821 ); 822 binOp = 823 lhs: rhs: 824 let 825 elemType = lhs.elemType.typeMerge rhs.elemType.functor; 826 lazy = if lhs.lazy == rhs.lazy then lhs.lazy else null; 827 placeholder = 828 if lhs.placeholder == rhs.placeholder then 829 lhs.placeholder 830 else if lhs.placeholder == "name" then 831 rhs.placeholder 832 else if rhs.placeholder == "name" then 833 lhs.placeholder 834 else 835 null; 836 in 837 if elemType == null || lazy == null || placeholder == null then 838 null 839 else 840 { 841 inherit elemType lazy placeholder; 842 }; 843 in 844 { 845 elemType, 846 lazy ? false, 847 placeholder ? "name", 848 }: 849 mkOptionType rec { 850 name = if lazy then "lazyAttrsOf" else "attrsOf"; 851 description = 852 (if lazy then "lazy attribute set" else "attribute set") 853 + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; 854 descriptionClass = "composite"; 855 check = { 856 __functor = _self: isAttrs; 857 isV2MergeCoherent = true; 858 }; 859 merge = { 860 __functor = 861 self: loc: defs: 862 (self.v2 { inherit loc defs; }).value; 863 v2 = 864 { loc, defs }: 865 let 866 evals = 867 if lazy then 868 zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs) 869 else 870 # Filtering makes the merge function more strict 871 # Meaning it is less lazy 872 filterAttrs (n: v: v.optionalValue ? value) ( 873 zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs) 874 ); 875 in 876 { 877 headError = checkDefsForError check loc defs; 878 value = mapAttrs ( 879 n: v: 880 if lazy then 881 v.optionalValue.value or elemType.emptyValue.value or v.mergedValue 882 else 883 v.optionalValue.value 884 ) evals; 885 valueMeta.attrs = mapAttrs (n: v: v.checkedAndMerged.valueMeta) evals; 886 }; 887 }; 888 889 emptyValue = { 890 value = { }; 891 }; 892 getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]); 893 getSubModules = elemType.getSubModules; 894 substSubModules = 895 m: 896 attrsWith { 897 elemType = elemType.substSubModules m; 898 inherit lazy placeholder; 899 }; 900 functor = 901 (elemTypeFunctor "attrsWith" { 902 inherit elemType lazy placeholder; 903 }) 904 // { 905 # Custom type merging required because of the "placeholder" attribute 906 inherit binOp; 907 }; 908 nestedTypes.elemType = elemType; 909 }; 910 911 # TODO: deprecate this in the future: 912 loaOf = 913 elemType: 914 types.attrsOf elemType 915 // { 916 name = "loaOf"; 917 deprecationMessage = 918 "Mixing lists with attribute values is no longer" 919 + " possible; please use `types.attrsOf` instead. See" 920 + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation."; 921 nestedTypes.elemType = elemType; 922 }; 923 924 attrTag = 925 tags: 926 let 927 tags_ = tags; 928 in 929 let 930 tags = mapAttrs ( 931 n: opt: 932 builtins.addErrorContext 933 "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}" 934 ( 935 throwIf (opt._type or null != "option") 936 "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${ 937 if opt ? _type then 938 if opt._type == "option-type" then 939 "was a bare type, not wrapped in mkOption." 940 else 941 "was of type ${lib.strings.escapeNixString opt._type}." 942 else 943 "was not." 944 }" 945 opt 946 // { 947 declarations = 948 opt.declarations or ( 949 let 950 pos = builtins.unsafeGetAttrPos n tags_; 951 in 952 if pos == null then [ ] else [ pos.file ] 953 ); 954 declarationPositions = 955 opt.declarationPositions or ( 956 let 957 pos = builtins.unsafeGetAttrPos n tags_; 958 in 959 if pos == null then [ ] else [ pos ] 960 ); 961 } 962 ) 963 ) tags_; 964 choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags); 965 in 966 mkOptionType { 967 name = "attrTag"; 968 description = "attribute-tagged union"; 969 descriptionClass = "noun"; 970 getSubOptions = 971 prefix: mapAttrs (tagName: tagOption: tagOption // { loc = prefix ++ [ tagName ]; }) tags; 972 check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)}; 973 merge = 974 loc: defs: 975 let 976 choice = head (attrNames (head defs).value); 977 checkedValueDefs = map ( 978 def: 979 assert (length (attrNames def.value)) == 1; 980 if (head (attrNames def.value)) != choice then 981 throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}." 982 else 983 { 984 inherit (def) file; 985 value = def.value.${choice}; 986 } 987 ) defs; 988 in 989 if tags ? ${choice} then 990 { 991 ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value; 992 } 993 else 994 throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}."; 995 nestedTypes = tags; 996 functor = defaultFunctor "attrTag" // { 997 type = { tags, ... }: types.attrTag tags; 998 payload = { inherit tags; }; 999 binOp = 1000 let 1001 # Add metadata in the format that submodules work with 1002 wrapOptionDecl = option: { 1003 options = option; 1004 _file = "<attrTag {...}>"; 1005 pos = null; 1006 }; 1007 in 1008 a: b: { 1009 tags = 1010 a.tags 1011 // b.tags 1012 // mapAttrs ( 1013 tagName: bOpt: 1014 lib.mergeOptionDecls 1015 # FIXME: loc is not accurate; should include prefix 1016 # Fortunately, it's only used for error messages, where a "relative" location is kinda ok. 1017 # It is also returned though, but use of the attribute seems rare? 1018 [ tagName ] 1019 [ 1020 (wrapOptionDecl a.tags.${tagName}) 1021 (wrapOptionDecl bOpt) 1022 ] 1023 // { 1024 # mergeOptionDecls is not idempotent in these attrs: 1025 declarations = a.tags.${tagName}.declarations ++ bOpt.declarations; 1026 declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions; 1027 } 1028 ) (builtins.intersectAttrs a.tags b.tags); 1029 }; 1030 }; 1031 }; 1032 1033 # A value produced by `lib.mkLuaInline` 1034 luaInline = mkOptionType { 1035 name = "luaInline"; 1036 description = "inline lua"; 1037 descriptionClass = "noun"; 1038 check = x: x._type or null == "lua-inline"; 1039 merge = mergeEqualOption; 1040 }; 1041 1042 uniq = unique { message = ""; }; 1043 1044 unique = 1045 { message }: 1046 type: 1047 mkOptionType rec { 1048 name = "unique"; 1049 inherit (type) description descriptionClass check; 1050 merge = mergeUniqueOption { 1051 inherit message; 1052 inherit (type) merge; 1053 }; 1054 emptyValue = type.emptyValue; 1055 getSubOptions = type.getSubOptions; 1056 getSubModules = type.getSubModules; 1057 substSubModules = m: uniq (type.substSubModules m); 1058 functor = elemTypeFunctor name { elemType = type; } // { 1059 type = payload: types.unique { inherit message; } payload.elemType; 1060 }; 1061 nestedTypes.elemType = type; 1062 }; 1063 1064 # Null or value of ... 1065 nullOr = 1066 elemType: 1067 mkOptionType rec { 1068 name = "nullOr"; 1069 description = "null or ${ 1070 optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType 1071 }"; 1072 descriptionClass = "conjunction"; 1073 check = x: x == null || elemType.check x; 1074 merge = 1075 loc: defs: 1076 let 1077 nrNulls = count (def: def.value == null) defs; 1078 in 1079 if nrNulls == length defs then 1080 null 1081 else if nrNulls != 0 then 1082 throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." 1083 else 1084 elemType.merge loc defs; 1085 emptyValue = { 1086 value = null; 1087 }; 1088 getSubOptions = elemType.getSubOptions; 1089 getSubModules = elemType.getSubModules; 1090 substSubModules = m: nullOr (elemType.substSubModules m); 1091 functor = (elemTypeFunctor name { inherit elemType; }) // { 1092 type = payload: types.nullOr payload.elemType; 1093 }; 1094 nestedTypes.elemType = elemType; 1095 }; 1096 1097 functionTo = 1098 elemType: 1099 mkOptionType { 1100 name = "functionTo"; 1101 description = "function that evaluates to a(n) ${ 1102 optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType 1103 }"; 1104 descriptionClass = "composite"; 1105 check = isFunction; 1106 merge = loc: defs: { 1107 # An argument attribute has a default when it has a default in all definitions 1108 __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) ( 1109 lib.map (fn: lib.functionArgs fn.value) defs 1110 ); 1111 __functor = 1112 _: callerArgs: 1113 (mergeDefinitions (loc ++ [ "<function body>" ]) elemType ( 1114 map (fn: { 1115 inherit (fn) file; 1116 value = fn.value callerArgs; 1117 }) defs 1118 )).mergedValue; 1119 }; 1120 getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]); 1121 getSubModules = elemType.getSubModules; 1122 substSubModules = m: functionTo (elemType.substSubModules m); 1123 functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // { 1124 type = payload: types.functionTo payload.elemType; 1125 }; 1126 nestedTypes.elemType = elemType; 1127 }; 1128 1129 # A submodule (like typed attribute set). See NixOS manual. 1130 submodule = 1131 modules: 1132 submoduleWith { 1133 shorthandOnlyDefinesConfig = true; 1134 modules = toList modules; 1135 }; 1136 1137 # A module to be imported in some other part of the configuration. 1138 deferredModule = deferredModuleWith { }; 1139 1140 # A module to be imported in some other part of the configuration. 1141 # `staticModules`' options will be added to the documentation, unlike 1142 # options declared via `config`. 1143 deferredModuleWith = 1144 attrs@{ 1145 staticModules ? [ ], 1146 }: 1147 mkOptionType { 1148 name = "deferredModule"; 1149 description = "module"; 1150 descriptionClass = "noun"; 1151 check = x: isAttrs x || isFunction x || path.check x; 1152 merge = loc: defs: { 1153 imports = 1154 staticModules 1155 ++ map ( 1156 def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value 1157 ) defs; 1158 }; 1159 inherit (submoduleWith { modules = staticModules; }) 1160 getSubOptions 1161 getSubModules 1162 ; 1163 substSubModules = 1164 m: 1165 deferredModuleWith ( 1166 attrs 1167 // { 1168 staticModules = m; 1169 } 1170 ); 1171 functor = defaultFunctor "deferredModuleWith" // { 1172 type = types.deferredModuleWith; 1173 payload = { 1174 inherit staticModules; 1175 }; 1176 binOp = lhs: rhs: { 1177 staticModules = lhs.staticModules ++ rhs.staticModules; 1178 }; 1179 }; 1180 }; 1181 1182 # The type of a type! 1183 optionType = mkOptionType { 1184 name = "optionType"; 1185 description = "optionType"; 1186 descriptionClass = "noun"; 1187 check = value: value._type or null == "option-type"; 1188 merge = 1189 loc: defs: 1190 if length defs == 1 then 1191 (head defs).value 1192 else 1193 let 1194 # Prepares the type definitions for mergeOptionDecls, which 1195 # annotates submodules types with file locations 1196 optionModules = map ( 1197 { value, file }: 1198 { 1199 _file = file; 1200 # There's no way to merge types directly from the module system, 1201 # but we can cheat a bit by just declaring an option with the type 1202 options = lib.mkOption { 1203 type = value; 1204 }; 1205 } 1206 ) defs; 1207 # Merges all the types into a single one, including submodule merging. 1208 # This also propagates file information to all submodules 1209 mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules); 1210 in 1211 mergedOption.type; 1212 }; 1213 1214 submoduleWith = 1215 { 1216 modules, 1217 specialArgs ? { }, 1218 shorthandOnlyDefinesConfig ? false, 1219 description ? null, 1220 class ? null, 1221 }@attrs: 1222 let 1223 inherit (lib.modules) evalModules; 1224 1225 allModules = 1226 defs: 1227 map ( 1228 { value, file }: 1229 if isAttrs value && shorthandOnlyDefinesConfig then 1230 { 1231 _file = file; 1232 config = value; 1233 } 1234 else 1235 { 1236 _file = file; 1237 imports = [ value ]; 1238 } 1239 ) defs; 1240 1241 base = evalModules { 1242 inherit class specialArgs; 1243 modules = [ 1244 { 1245 # This is a work-around for the fact that some sub-modules, 1246 # such as the one included in an attribute set, expects an "args" 1247 # attribute to be given to the sub-module. As the option 1248 # evaluation does not have any specific attribute name yet, we 1249 # provide a default for the documentation and the freeform type. 1250 # 1251 # This is necessary as some option declaration might use the 1252 # "name" attribute given as argument of the submodule and use it 1253 # as the default of option declarations. 1254 # 1255 # We use lookalike unicode single angle quotation marks because 1256 # of the docbook transformation the options receive. In all uses 1257 # &gt; and &lt; wouldn't be encoded correctly so the encoded values 1258 # would be used, and use of `<` and `>` would break the XML document. 1259 # It shouldn't cause an issue since this is cosmetic for the manual. 1260 _module.args.name = lib.mkOptionDefault "name"; 1261 } 1262 ] 1263 ++ modules; 1264 }; 1265 1266 freeformType = base._module.freeformType; 1267 1268 name = "submodule"; 1269 1270 check = { 1271 __functor = _self: x: isAttrs x || isFunction x || path.check x; 1272 isV2MergeCoherent = true; 1273 }; 1274 in 1275 mkOptionType { 1276 inherit name; 1277 description = 1278 if description != null then 1279 description 1280 else 1281 let 1282 docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; }; 1283 in 1284 if docsEval._module.freeformType ? description then 1285 "open ${name} of ${ 1286 optionDescriptionPhrase ( 1287 class: class == "noun" || class == "composite" 1288 ) docsEval._module.freeformType 1289 }" 1290 else 1291 name; 1292 inherit check; 1293 merge = { 1294 __functor = 1295 self: loc: defs: 1296 (self.v2 { inherit loc defs; }).value; 1297 v2 = 1298 { loc, defs }: 1299 let 1300 configuration = base.extendModules { 1301 modules = [ { _module.args.name = last loc; } ] ++ allModules defs; 1302 prefix = loc; 1303 }; 1304 in 1305 { 1306 headError = checkDefsForError check loc defs; 1307 value = configuration.config; 1308 valueMeta = { inherit configuration; }; 1309 }; 1310 }; 1311 emptyValue = { 1312 value = { }; 1313 }; 1314 getSubOptions = 1315 prefix: 1316 let 1317 docsEval = ( 1318 base.extendModules { 1319 inherit prefix; 1320 modules = [ noCheckForDocsModule ]; 1321 } 1322 ); 1323 # Intentionally shadow the freeformType from the possibly *checked* 1324 # configuration. See `noCheckForDocsModule` comment. 1325 inherit (docsEval._module) freeformType; 1326 in 1327 docsEval.options 1328 // optionalAttrs (freeformType != null) { 1329 # Expose the sub options of the freeform type. Note that the option 1330 # discovery doesn't care about the attribute name used here, so this 1331 # is just to avoid conflicts with potential options from the submodule 1332 _freeformOptions = freeformType.getSubOptions prefix; 1333 }; 1334 getSubModules = modules; 1335 substSubModules = 1336 m: 1337 submoduleWith ( 1338 attrs 1339 // { 1340 modules = m; 1341 } 1342 ); 1343 nestedTypes = lib.optionalAttrs (freeformType != null) { 1344 freeformType = freeformType; 1345 }; 1346 functor = defaultFunctor name // { 1347 type = types.submoduleWith; 1348 payload = { 1349 inherit 1350 modules 1351 class 1352 specialArgs 1353 shorthandOnlyDefinesConfig 1354 description 1355 ; 1356 }; 1357 binOp = lhs: rhs: { 1358 class = 1359 # `or null` was added for backwards compatibility only. `class` is 1360 # always set in the current version of the module system. 1361 if lhs.class or null == null then 1362 rhs.class or null 1363 else if rhs.class or null == null then 1364 lhs.class or null 1365 else if lhs.class or null == rhs.class then 1366 lhs.class or null 1367 else 1368 throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\"."; 1369 modules = lhs.modules ++ rhs.modules; 1370 specialArgs = 1371 let 1372 intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs; 1373 in 1374 if intersecting == { } then 1375 lhs.specialArgs // rhs.specialArgs 1376 else 1377 throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\""; 1378 shorthandOnlyDefinesConfig = 1379 if lhs.shorthandOnlyDefinesConfig == null then 1380 rhs.shorthandOnlyDefinesConfig 1381 else if rhs.shorthandOnlyDefinesConfig == null then 1382 lhs.shorthandOnlyDefinesConfig 1383 else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig then 1384 lhs.shorthandOnlyDefinesConfig 1385 else 1386 throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values"; 1387 description = 1388 if lhs.description == null then 1389 rhs.description 1390 else if rhs.description == null then 1391 lhs.description 1392 else if lhs.description == rhs.description then 1393 lhs.description 1394 else 1395 throw "A submoduleWith option is declared multiple times with conflicting descriptions"; 1396 }; 1397 }; 1398 }; 1399 1400 # A value from a set of allowed ones. 1401 enum = 1402 values: 1403 let 1404 inherit (lib.lists) unique; 1405 show = 1406 v: 1407 if builtins.isString v then 1408 ''"${v}"'' 1409 else if builtins.isInt v then 1410 toString v 1411 else if builtins.isBool v then 1412 boolToString v 1413 else 1414 "<${builtins.typeOf v}>"; 1415 in 1416 mkOptionType rec { 1417 name = "enum"; 1418 description = 1419 # Length 0 or 1 enums may occur in a design pattern with type merging 1420 # where an "interface" module declares an empty enum and other modules 1421 # provide implementations, each extending the enum with their own 1422 # identifier. 1423 if values == [ ] then 1424 "impossible (empty enum)" 1425 else if builtins.length values == 1 then 1426 "value ${show (builtins.head values)} (singular enum)" 1427 else 1428 "one of ${concatMapStringsSep ", " show values}"; 1429 descriptionClass = if builtins.length values < 2 then "noun" else "conjunction"; 1430 check = flip elem values; 1431 merge = mergeEqualOption; 1432 functor = (defaultFunctor name) // { 1433 payload = { inherit values; }; 1434 type = payload: types.enum payload.values; 1435 binOp = a: b: { values = unique (a.values ++ b.values); }; 1436 }; 1437 }; 1438 1439 # Either value of type `t1` or `t2`. 1440 either = 1441 t1: t2: 1442 mkOptionType rec { 1443 name = "either"; 1444 description = 1445 if t1.descriptionClass or null == "nonRestrictiveClause" then 1446 # Plain, but add comma 1447 "${t1.description}, or ${ 1448 optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2 1449 }" 1450 else 1451 "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${ 1452 optionDescriptionPhrase ( 1453 class: class == "noun" || class == "conjunction" || class == "composite" 1454 ) t2 1455 }"; 1456 descriptionClass = "conjunction"; 1457 check = { 1458 __functor = _self: x: t1.check x || t2.check x; 1459 isV2MergeCoherent = true; 1460 }; 1461 merge = { 1462 __functor = 1463 self: loc: defs: 1464 (self.v2 { inherit loc defs; }).value; 1465 v2 = 1466 { loc, defs }: 1467 let 1468 t1CheckedAndMerged = 1469 if t1.merge ? v2 then 1470 checkV2MergeCoherence loc t1 (t1.merge.v2 { inherit loc defs; }) 1471 else 1472 { 1473 value = t1.merge loc defs; 1474 headError = checkDefsForError t1.check loc defs; 1475 valueMeta = { }; 1476 }; 1477 t2CheckedAndMerged = 1478 if t2.merge ? v2 then 1479 checkV2MergeCoherence loc t2 (t2.merge.v2 { inherit loc defs; }) 1480 else 1481 { 1482 value = t2.merge loc defs; 1483 headError = checkDefsForError t2.check loc defs; 1484 valueMeta = { }; 1485 }; 1486 1487 checkedAndMerged = 1488 if t1CheckedAndMerged.headError == null then 1489 t1CheckedAndMerged 1490 else if t2CheckedAndMerged.headError == null then 1491 t2CheckedAndMerged 1492 else 1493 rec { 1494 valueMeta = { 1495 inherit headError; 1496 }; 1497 headError = { 1498 message = "The option `${showOption loc}` is neither a value of type `${t1.description}` nor `${t2.description}`, Definition values: ${showDefs defs}"; 1499 }; 1500 value = lib.warn '' 1501 One or more definitions did not pass the type-check of the 'either' type. 1502 ${headError.message} 1503 If `either`, `oneOf` or similar is used in freeformType, ensure that it is preceded by an 'attrsOf' such as: `freeformType = types.attrsOf (types.either t1 t2)`. 1504 Otherwise consider using the correct type for the option `${showOption loc}`. This will be an error in Nixpkgs 26.05. 1505 '' (mergeOneOption loc defs); 1506 }; 1507 in 1508 checkedAndMerged; 1509 }; 1510 typeMerge = 1511 f': 1512 let 1513 mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor; 1514 mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor; 1515 in 1516 if (name == f'.name) && (mt1 != null) && (mt2 != null) then functor.type mt1 mt2 else null; 1517 functor = elemTypeFunctor name { 1518 elemType = [ 1519 t1 1520 t2 1521 ]; 1522 }; 1523 nestedTypes.left = t1; 1524 nestedTypes.right = t2; 1525 }; 1526 1527 # Any of the types in the given list 1528 oneOf = 1529 ts: 1530 let 1531 head' = 1532 if ts == [ ] then throw "types.oneOf needs to get at least one type in its argument" else head ts; 1533 tail' = tail ts; 1534 in 1535 foldl' either head' tail'; 1536 1537 # Either value of type `coercedType` or `finalType`, the former is 1538 # converted to `finalType` using `coerceFunc`. 1539 coercedTo = 1540 coercedType: coerceFunc: finalType: 1541 assert lib.assertMsg ( 1542 coercedType.getSubModules == null 1543 ) "coercedTo: coercedType must not have submodules (its a ${coercedType.description})"; 1544 mkOptionType rec { 1545 name = "coercedTo"; 1546 description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${ 1547 optionDescriptionPhrase (class: class == "noun") coercedType 1548 } convertible to it"; 1549 check = { 1550 __functor = _self: x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x; 1551 isV2MergeCoherent = true; 1552 }; 1553 merge = { 1554 __functor = 1555 self: loc: defs: 1556 (self.v2 { inherit loc defs; }).value; 1557 v2 = 1558 { loc, defs }: 1559 let 1560 finalDefs = ( 1561 map ( 1562 def: 1563 def 1564 // { 1565 value = 1566 let 1567 merged = 1568 if coercedType.merge ? v2 then 1569 checkV2MergeCoherence loc coercedType ( 1570 coercedType.merge.v2 { 1571 inherit loc; 1572 defs = [ def ]; 1573 } 1574 ) 1575 else 1576 null; 1577 in 1578 if coercedType.merge ? v2 then 1579 if merged.headError == null then coerceFunc def.value else def.value 1580 else if coercedType.check def.value then 1581 coerceFunc def.value 1582 else 1583 def.value; 1584 } 1585 ) defs 1586 ); 1587 in 1588 if finalType.merge ? v2 then 1589 checkV2MergeCoherence loc finalType ( 1590 finalType.merge.v2 { 1591 inherit loc; 1592 defs = finalDefs; 1593 } 1594 ) 1595 else 1596 { 1597 value = finalType.merge loc finalDefs; 1598 valueMeta = { }; 1599 headError = checkDefsForError check loc defs; 1600 }; 1601 }; 1602 emptyValue = finalType.emptyValue; 1603 getSubOptions = finalType.getSubOptions; 1604 getSubModules = finalType.getSubModules; 1605 substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); 1606 typeMerge = t: null; 1607 functor = (defaultFunctor name) // { 1608 wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; }; 1609 }; 1610 nestedTypes.coercedType = coercedType; 1611 nestedTypes.finalType = finalType; 1612 }; 1613 1614 /** 1615 Augment the given type with an additional type check function. 1616 1617 :::{.warning} 1618 This function has some broken behavior see: [#396021](https://github.com/NixOS/nixpkgs/issues/396021) 1619 Fixing is not trivial, we appreciate any help! 1620 ::: 1621 */ 1622 addCheck = 1623 elemType: check: 1624 if elemType.merge ? v2 then 1625 elemType 1626 // { 1627 check = { 1628 __functor = _self: x: elemType.check x && check x; 1629 isV2MergeCoherent = true; 1630 }; 1631 merge = { 1632 __functor = 1633 self: loc: defs: 1634 (self.v2 { inherit loc defs; }).value; 1635 v2 = 1636 { loc, defs }: 1637 let 1638 orig = checkV2MergeCoherence loc elemType (elemType.merge.v2 { inherit loc defs; }); 1639 headError' = if orig.headError != null then orig.headError else checkDefsForError check loc defs; 1640 in 1641 orig 1642 // { 1643 headError = headError'; 1644 }; 1645 }; 1646 } 1647 else 1648 elemType 1649 // { 1650 check = x: elemType.check x && check x; 1651 }; 1652 }; 1653 1654 /** 1655 Merges two option types together. 1656 1657 :::{.note} 1658 Uses the type merge function of the first type, to merge it with the second type. 1659 1660 Usually types can only be merged if they are of the same type 1661 ::: 1662 1663 # Inputs 1664 1665 : `a` (option type): The first option type. 1666 : `b` (option type): The second option type. 1667 1668 # Returns 1669 1670 - The merged option type. 1671 - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged. 1672 1673 # Examples 1674 :::{.example} 1675 ## `lib.types.mergeTypes` usage example 1676 ```nix 1677 let 1678 enumAB = lib.types.enum ["A" "B"]; 1679 enumXY = lib.types.enum ["X" "Y"]; 1680 # This operation could be notated as: [ A ] | [ B ] -> [ A B ] 1681 merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ] 1682 in 1683 assert merged.check "A"; # true 1684 assert merged.check "B"; # true 1685 assert merged.check "X"; # true 1686 assert merged.check "Y"; # true 1687 merged.check "C" # false 1688 ``` 1689 ::: 1690 */ 1691 mergeTypes = 1692 a: b: 1693 assert isOptionType a && isOptionType b; 1694 let 1695 merged = a.typeMerge b.functor; 1696 in 1697 if merged == null then setType "merge-error" { error = "Cannot merge types"; } else merged; 1698 }; 1699 1700in 1701outer_types // outer_types.types