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