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