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