Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at gcc-offload 1112 lines 43 kB view raw
1# Definitions related to run-time type checking. Used in particular 2# to type-check NixOS configurations. 3{ lib }: 4 5let 6 inherit (lib) 7 elem 8 flip 9 isAttrs 10 isBool 11 isDerivation 12 isFloat 13 isFunction 14 isInt 15 isList 16 isString 17 isStorePath 18 throwIf 19 toDerivation 20 toList 21 ; 22 inherit (lib.lists) 23 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 = v: name: 71 let pos = builtins.unsafeGetAttrPos name v; in 72 if pos == null then "" else " at ${pos.file}:${toString pos.line}:${toString pos.column}"; 73 74 outer_types = 75rec { 76 isType = type: x: (x._type or "") == type; 77 78 setType = typeName: value: value // { 79 _type = typeName; 80 }; 81 82 83 # Default type merging function 84 # takes two type functors and return the merged type 85 defaultTypeMerge = f: f': 86 let 87 mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor; 88 mergedPayload = f.binOp f.payload f'.payload; 89 90 hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null; 91 hasWrapped = assert (f'.wrapped != null) == (f.wrapped != null); f.wrapped != null; 92 93 typeFromPayload = if mergedPayload == null then null else f.type mergedPayload; 94 typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped; 95 in 96 # Abort early: cannot merge different types 97 if f.name != f'.name 98 then null 99 else 100 101 if hasPayload then 102 # Just return the payload if returning wrapped is deprecated 103 if f ? wrappedDeprecationMessage then 104 typeFromPayload 105 else if hasWrapped then 106 # Has both wrapped and payload 107 throw '' 108 Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported. 109 110 Use either `functor.payload` or `functor.wrapped` but not both. 111 112 If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition. 113 '' 114 else 115 typeFromPayload 116 else 117 if hasWrapped then 118 typeFromWrapped 119 else 120 f.type; 121 122 # Default type functor 123 defaultFunctor = name: { 124 inherit name; 125 type = types.${name} or null; 126 wrapped = null; 127 payload = null; 128 binOp = a: b: null; 129 }; 130 131 isOptionType = isType "option-type"; 132 mkOptionType = 133 { # Human-readable representation of the type, should be equivalent to 134 # the type function name. 135 name 136 , # Description of the type, defined recursively by embedding the wrapped type if any. 137 description ? null 138 # A hint for whether or not this description needs parentheses. Possible values: 139 # - "noun": a noun phrase 140 # Example description: "positive integer", 141 # - "conjunction": a phrase with a potentially ambiguous "or" connective 142 # Example description: "int or string" 143 # - "composite": a phrase with an "of" connective 144 # Example description: "list of string" 145 # - "nonRestrictiveClause": a noun followed by a comma and a clause 146 # Example description: "positive integer, meaning >0" 147 # See the `optionDescriptionPhrase` function. 148 , descriptionClass ? null 149 , # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING! 150 # Function applied to each definition that must return false when a definition 151 # does not match the type. It should not check more than the root of the value, 152 # because checking nested values reduces laziness, leading to unnecessary 153 # infinite recursions in the module system. 154 # Further checks of nested values should be performed by throwing in 155 # the merge function. 156 # Strict and deep type checking can be performed by calling lib.deepSeq on 157 # the merged value. 158 # 159 # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change, 160 # https://github.com/NixOS/nixpkgs/pull/173568 and 161 # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this, 162 # https://github.com/NixOS/nixpkgs/issues/191124 and 163 # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore 164 # this disclaimer. 165 check ? (x: true) 166 , # Merge a list of definitions together into a single value. 167 # This function is called with two arguments: the location of 168 # the option in the configuration as a list of strings 169 # (e.g. ["boot" "loader "grub" "enable"]), and a list of 170 # definition values and locations (e.g. [ { file = "/foo.nix"; 171 # value = 1; } { file = "/bar.nix"; value = 2 } ]). 172 merge ? mergeDefaultOption 173 , # Whether this type has a value representing nothingness. If it does, 174 # this should be a value of the form { value = <the nothing value>; } 175 # If it doesn't, this should be {} 176 # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`. 177 emptyValue ? {} 178 , # Return a flat attrset of sub-options. Used to generate 179 # documentation. 180 getSubOptions ? prefix: {} 181 , # List of modules if any, or null if none. 182 getSubModules ? null 183 , # Function for building the same option type with a different list of 184 # modules. 185 substSubModules ? m: null 186 , # Function that merge type declarations. 187 # internal, takes a functor as argument and returns the merged type. 188 # returning null means the type is not mergeable 189 typeMerge ? defaultTypeMerge functor 190 , # The type functor. 191 # internal, representation of the type as an attribute set. 192 # name: name of the type 193 # type: type function. 194 # wrapped: the type wrapped in case of compound types. 195 # payload: values of the type, two payloads of the same type must be 196 # combinable with the binOp binary operation. 197 # binOp: binary operation that merge two payloads of the same type. 198 functor ? defaultFunctor name 199 , # The deprecation message to display when this type is used by an option 200 # If null, the type isn't deprecated 201 deprecationMessage ? null 202 , # The types that occur in the definition of this type. This is used to 203 # issue deprecation warnings recursively. Can also be used to reuse 204 # nested types 205 nestedTypes ? {} 206 }: 207 { _type = "option-type"; 208 inherit 209 name check merge emptyValue getSubOptions getSubModules substSubModules 210 typeMerge functor deprecationMessage nestedTypes descriptionClass; 211 description = if description == null then name else description; 212 }; 213 214 # optionDescriptionPhrase :: (str -> bool) -> optionType -> str 215 # 216 # Helper function for producing unambiguous but readable natural language 217 # descriptions of types. 218 # 219 # Parameters 220 # 221 # optionDescriptionPhase unparenthesize optionType 222 # 223 # `unparenthesize`: A function from descriptionClass string to boolean. 224 # It must return true when the class of phrase will fit unambiguously into 225 # the description of the caller. 226 # 227 # `optionType`: The option type to parenthesize or not. 228 # The option whose description we're returning. 229 # 230 # Return value 231 # 232 # The description of the `optionType`, with parentheses if there may be an 233 # ambiguity. 234 optionDescriptionPhrase = unparenthesize: t: 235 if unparenthesize (t.descriptionClass or null) 236 then t.description 237 else "(${t.description})"; 238 239 # When adding new types don't forget to document them in 240 # nixos/doc/manual/development/option-types.section.md! 241 types = rec { 242 243 raw = mkOptionType { 244 name = "raw"; 245 description = "raw value"; 246 descriptionClass = "noun"; 247 check = value: true; 248 merge = mergeOneOption; 249 }; 250 251 anything = mkOptionType { 252 name = "anything"; 253 description = "anything"; 254 descriptionClass = "noun"; 255 check = value: true; 256 merge = loc: defs: 257 let 258 getType = value: 259 if isAttrs value && isStringLike value 260 then "stringCoercibleSet" 261 else builtins.typeOf value; 262 263 # Returns the common type of all definitions, throws an error if they 264 # don't have the same type 265 commonType = foldl' (type: def: 266 if getType def.value == type 267 then type 268 else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" 269 ) (getType (head defs).value) defs; 270 271 mergeFunction = { 272 # Recursively merge attribute sets 273 set = (attrsOf anything).merge; 274 # This is the type of packages, only accept a single definition 275 stringCoercibleSet = mergeOneOption; 276 lambda = loc: defs: arg: anything.merge 277 (loc ++ [ "<function body>" ]) 278 (map (def: { 279 file = def.file; 280 value = def.value arg; 281 }) defs); 282 # Otherwise fall back to only allowing all equal definitions 283 }.${commonType} or mergeEqualOption; 284 in mergeFunction loc defs; 285 }; 286 287 unspecified = mkOptionType { 288 name = "unspecified"; 289 description = "unspecified value"; 290 descriptionClass = "noun"; 291 }; 292 293 bool = mkOptionType { 294 name = "bool"; 295 description = "boolean"; 296 descriptionClass = "noun"; 297 check = isBool; 298 merge = mergeEqualOption; 299 }; 300 301 boolByOr = mkOptionType { 302 name = "boolByOr"; 303 description = "boolean (merged using or)"; 304 descriptionClass = "noun"; 305 check = isBool; 306 merge = loc: defs: 307 foldl' 308 (result: def: 309 # Under the assumption that .check always runs before merge, we can assume that all defs.*.value 310 # have been forced, and therefore we assume we don't introduce order-dependent strictness here 311 result || def.value 312 ) 313 false 314 defs; 315 }; 316 317 int = mkOptionType { 318 name = "int"; 319 description = "signed integer"; 320 descriptionClass = "noun"; 321 check = isInt; 322 merge = mergeEqualOption; 323 }; 324 325 # Specialized subdomains of int 326 ints = 327 let 328 betweenDesc = lowest: highest: 329 "${toString lowest} and ${toString highest} (both inclusive)"; 330 between = lowest: highest: 331 assert lib.assertMsg (lowest <= highest) 332 "ints.between: lowest must be smaller than highest"; 333 addCheck int (x: x >= lowest && x <= highest) // { 334 name = "intBetween"; 335 description = "integer between ${betweenDesc lowest highest}"; 336 }; 337 ign = lowest: highest: name: docStart: 338 between lowest highest // { 339 inherit name; 340 description = docStart + "; between ${betweenDesc lowest highest}"; 341 }; 342 unsign = bit: range: ign 0 (range - 1) 343 "unsignedInt${toString bit}" "${toString bit} bit unsigned integer"; 344 sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1) 345 "signedInt${toString bit}" "${toString bit} bit signed integer"; 346 347 in { 348 # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md 349 /** 350 An int with a fixed range. 351 352 # Example 353 :::{.example} 354 ## `lib.types.ints.between` usage example 355 356 ```nix 357 (ints.between 0 100).check (-1) 358 => false 359 (ints.between 0 100).check (101) 360 => false 361 (ints.between 0 0).check 0 362 => true 363 ``` 364 365 ::: 366 */ 367 inherit between; 368 369 unsigned = addCheck types.int (x: x >= 0) // { 370 name = "unsignedInt"; 371 description = "unsigned integer, meaning >=0"; 372 descriptionClass = "nonRestrictiveClause"; 373 }; 374 positive = addCheck types.int (x: x > 0) // { 375 name = "positiveInt"; 376 description = "positive integer, meaning >0"; 377 descriptionClass = "nonRestrictiveClause"; 378 }; 379 u8 = unsign 8 256; 380 u16 = unsign 16 65536; 381 # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808) 382 # the smallest int Nix accepts is -2^63 (-9223372036854775807) 383 u32 = unsign 32 4294967296; 384 # u64 = unsign 64 18446744073709551616; 385 386 s8 = sign 8 256; 387 s16 = sign 16 65536; 388 s32 = sign 32 4294967296; 389 }; 390 391 # Alias of u16 for a port number 392 port = ints.u16; 393 394 float = mkOptionType { 395 name = "float"; 396 description = "floating point number"; 397 descriptionClass = "noun"; 398 check = isFloat; 399 merge = mergeEqualOption; 400 }; 401 402 number = either int float; 403 404 numbers = let 405 betweenDesc = lowest: highest: 406 "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)"; 407 in { 408 between = lowest: highest: 409 assert lib.assertMsg (lowest <= highest) 410 "numbers.between: lowest must be smaller than highest"; 411 addCheck number (x: x >= lowest && x <= highest) // { 412 name = "numberBetween"; 413 description = "integer or floating point number between ${betweenDesc lowest highest}"; 414 }; 415 416 nonnegative = addCheck number (x: x >= 0) // { 417 name = "numberNonnegative"; 418 description = "nonnegative integer or floating point number, meaning >=0"; 419 descriptionClass = "nonRestrictiveClause"; 420 }; 421 positive = addCheck number (x: x > 0) // { 422 name = "numberPositive"; 423 description = "positive integer or floating point number, meaning >0"; 424 descriptionClass = "nonRestrictiveClause"; 425 }; 426 }; 427 428 str = mkOptionType { 429 name = "str"; 430 description = "string"; 431 descriptionClass = "noun"; 432 check = isString; 433 merge = mergeEqualOption; 434 }; 435 436 nonEmptyStr = mkOptionType { 437 name = "nonEmptyStr"; 438 description = "non-empty string"; 439 descriptionClass = "noun"; 440 check = x: str.check x && builtins.match "[ \t\n]*" x == null; 441 inherit (str) merge; 442 }; 443 444 # Allow a newline character at the end and trim it in the merge function. 445 singleLineStr = 446 let 447 inherit (strMatching "[^\n\r]*\n?") check merge; 448 in 449 mkOptionType { 450 name = "singleLineStr"; 451 description = "(optionally newline-terminated) single-line string"; 452 descriptionClass = "noun"; 453 inherit check; 454 merge = loc: defs: 455 lib.removeSuffix "\n" (merge loc defs); 456 }; 457 458 strMatching = pattern: mkOptionType { 459 name = "strMatching ${escapeNixString pattern}"; 460 description = "string matching the pattern ${pattern}"; 461 descriptionClass = "noun"; 462 check = x: str.check x && builtins.match pattern x != null; 463 inherit (str) merge; 464 functor = defaultFunctor "strMatching" // { 465 type = payload: strMatching payload.pattern; 466 payload = { inherit pattern; }; 467 binOp = lhs: rhs: if lhs == rhs then lhs else null; 468 }; 469 }; 470 471 # Merge multiple definitions by concatenating them (with the given 472 # separator between the values). 473 separatedString = sep: mkOptionType rec { 474 name = "separatedString"; 475 description = if sep == "" 476 then "Concatenated string" # for types.string. 477 else "strings concatenated with ${builtins.toJSON sep}" 478 ; 479 descriptionClass = "noun"; 480 check = isString; 481 merge = loc: defs: concatStringsSep sep (getValues defs); 482 functor = (defaultFunctor name) // { 483 payload = { inherit sep; }; 484 type = payload: types.separatedString payload.sep; 485 binOp = lhs: rhs: 486 if lhs.sep == rhs.sep then { inherit (lhs) sep; } 487 else null; 488 }; 489 }; 490 491 lines = separatedString "\n"; 492 commas = separatedString ","; 493 envVar = separatedString ":"; 494 495 # Deprecated; should not be used because it quietly concatenates 496 # strings, which is usually not what you want. 497 # We use a lib.warn because `deprecationMessage` doesn't trigger in nested types such as `attrsOf string` 498 string = lib.warn 499 "The type `types.string` is deprecated. See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types." 500 (separatedString "" // { 501 name = "string"; 502 }); 503 504 passwdEntry = entryType: addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) // { 505 name = "passwdEntry ${entryType.name}"; 506 description = "${optionDescriptionPhrase (class: class == "noun") entryType}, not containing newlines or colons"; 507 descriptionClass = "nonRestrictiveClause"; 508 }; 509 510 attrs = mkOptionType { 511 name = "attrs"; 512 description = "attribute set"; 513 check = isAttrs; 514 merge = loc: foldl' (res: def: res // def.value) {}; 515 emptyValue = { value = {}; }; 516 }; 517 518 # A package is a top-level store path (/nix/store/hash-name). This includes: 519 # - derivations 520 # - more generally, attribute sets with an `outPath` or `__toString` attribute 521 # pointing to a store path, e.g. flake inputs 522 # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo) 523 # - hardcoded store path literals (/nix/store/hash-foo) or strings without context 524 # ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath. 525 # If you don't need a *top-level* store path, consider using pathInStore instead. 526 package = mkOptionType { 527 name = "package"; 528 descriptionClass = "noun"; 529 check = x: isDerivation x || isStorePath x; 530 merge = loc: defs: 531 let res = mergeOneOption loc defs; 532 in if builtins.isPath res || (builtins.isString res && ! builtins.hasContext res) 533 then toDerivation res 534 else res; 535 }; 536 537 shellPackage = package // { 538 check = x: isDerivation x && hasAttr "shellPath" x; 539 }; 540 541 pkgs = addCheck 542 (unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs // { 543 name = "pkgs"; 544 descriptionClass = "noun"; 545 description = "Nixpkgs package set"; 546 }) 547 (x: (x._type or null) == "pkgs"); 548 549 path = mkOptionType { 550 name = "path"; 551 descriptionClass = "noun"; 552 check = x: isStringLike x && builtins.substring 0 1 (toString x) == "/"; 553 merge = mergeEqualOption; 554 }; 555 556 pathInStore = mkOptionType { 557 name = "pathInStore"; 558 description = "path in the Nix store"; 559 descriptionClass = "noun"; 560 check = x: isStringLike x && builtins.match "${builtins.storeDir}/[^.].*" (toString x) != null; 561 merge = mergeEqualOption; 562 }; 563 564 listOf = elemType: mkOptionType rec { 565 name = "listOf"; 566 description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; 567 descriptionClass = "composite"; 568 check = isList; 569 merge = loc: defs: 570 map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def: 571 imap1 (m: def': 572 (mergeDefinitions 573 (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) 574 elemType 575 [{ inherit (def) file; value = def'; }] 576 ).optionalValue 577 ) def.value 578 ) defs))); 579 emptyValue = { value = []; }; 580 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); 581 getSubModules = elemType.getSubModules; 582 substSubModules = m: listOf (elemType.substSubModules m); 583 functor = (defaultFunctor name) // { wrapped = elemType; }; 584 nestedTypes.elemType = elemType; 585 }; 586 587 nonEmptyListOf = elemType: 588 let list = addCheck (types.listOf elemType) (l: l != []); 589 in list // { 590 description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}"; 591 emptyValue = { }; # no .value attr, meaning unset 592 substSubModules = m: nonEmptyListOf (elemType.substSubModules m); 593 }; 594 595 attrsOf = elemType: attrsWith { inherit elemType; }; 596 597 # A version of attrsOf that's lazy in its values at the expense of 598 # conditional definitions not working properly. E.g. defining a value with 599 # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with 600 # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an 601 # error that it's not defined. Use only if conditional definitions don't make sense. 602 lazyAttrsOf = elemType: attrsWith { inherit elemType; lazy = true; }; 603 604 # base type for lazyAttrsOf and attrsOf 605 attrsWith = 606 let 607 # Push down position info. 608 pushPositions = map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value); 609 binOp = lhs: rhs: 610 let 611 elemType = lhs.elemType.typeMerge rhs.elemType.functor; 612 lazy = 613 if lhs.lazy == rhs.lazy then 614 lhs.lazy 615 else 616 null; 617 placeholder = 618 if lhs.placeholder == rhs.placeholder then 619 lhs.placeholder 620 else if lhs.placeholder == "name" then 621 rhs.placeholder 622 else if rhs.placeholder == "name" then 623 lhs.placeholder 624 else 625 null; 626 in 627 if elemType == null || lazy == null || placeholder == null then 628 null 629 else 630 { 631 inherit elemType lazy placeholder; 632 }; 633 in 634 { 635 elemType, 636 lazy ? false, 637 placeholder ? "name", 638 }: 639 mkOptionType { 640 name = if lazy then "lazyAttrsOf" else "attrsOf"; 641 description = (if lazy then "lazy attribute set" else "attribute set") + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; 642 descriptionClass = "composite"; 643 check = isAttrs; 644 merge = if lazy then ( 645 # Lazy merge Function 646 loc: defs: 647 zipAttrsWith (name: defs: 648 let merged = mergeDefinitions (loc ++ [name]) elemType defs; 649 # mergedValue will trigger an appropriate error when accessed 650 in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue 651 ) 652 # Push down position info. 653 (pushPositions defs) 654 ) else ( 655 # Non-lazy merge Function 656 loc: defs: 657 mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs: 658 (mergeDefinitions (loc ++ [name]) elemType (defs)).optionalValue 659 ) 660 # Push down position info. 661 (pushPositions defs))) 662 ); 663 emptyValue = { value = {}; }; 664 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${placeholder}>"]); 665 getSubModules = elemType.getSubModules; 666 substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy placeholder; }; 667 functor = defaultFunctor "attrsWith" // { 668 wrappedDeprecationMessage = { loc }: lib.warn '' 669 The deprecated `type.functor.wrapped` attribute of the option `${showOption loc}` is accessed, use `type.nestedTypes.elemType` instead. 670 '' elemType; 671 payload = { 672 # Important!: Add new function attributes here in case of future changes 673 inherit elemType lazy placeholder; 674 }; 675 inherit binOp; 676 }; 677 nestedTypes.elemType = elemType; 678 }; 679 680 # TODO: deprecate this in the future: 681 loaOf = elemType: types.attrsOf elemType // { 682 name = "loaOf"; 683 deprecationMessage = "Mixing lists with attribute values is no longer" 684 + " possible; please use `types.attrsOf` instead. See" 685 + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation."; 686 nestedTypes.elemType = elemType; 687 }; 688 689 attrTag = tags: 690 let tags_ = tags; in 691 let 692 tags = 693 mapAttrs 694 (n: opt: 695 builtins.addErrorContext "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}" ( 696 throwIf (opt._type or null != "option") 697 "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${ 698 if opt?_type then 699 if opt._type == "option-type" 700 then "was a bare type, not wrapped in mkOption." 701 else "was of type ${lib.strings.escapeNixString opt._type}." 702 else "was not."}" 703 opt // { 704 declarations = opt.declarations or ( 705 let pos = builtins.unsafeGetAttrPos n tags_; 706 in if pos == null then [] else [ pos.file ] 707 ); 708 declarationPositions = opt.declarationPositions or ( 709 let pos = builtins.unsafeGetAttrPos n tags_; 710 in if pos == null then [] else [ pos ] 711 ); 712 } 713 )) 714 tags_; 715 choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags); 716 in 717 mkOptionType { 718 name = "attrTag"; 719 description = "attribute-tagged union"; 720 descriptionClass = "noun"; 721 getSubOptions = prefix: 722 mapAttrs 723 (tagName: tagOption: { 724 "${lib.showOption prefix}" = 725 tagOption // { 726 loc = prefix ++ [ tagName ]; 727 }; 728 }) 729 tags; 730 check = v: isAttrs v && length (attrNames v) == 1 && tags?${head (attrNames v)}; 731 merge = loc: defs: 732 let 733 choice = head (attrNames (head defs).value); 734 checkedValueDefs = map 735 (def: 736 assert (length (attrNames def.value)) == 1; 737 if (head (attrNames def.value)) != choice 738 then throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}." 739 else { inherit (def) file; value = def.value.${choice}; }) 740 defs; 741 in 742 if tags?${choice} 743 then 744 { ${choice} = 745 (lib.modules.evalOptionValue 746 (loc ++ [choice]) 747 tags.${choice} 748 checkedValueDefs 749 ).value; 750 } 751 else 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)}."; 752 nestedTypes = tags; 753 functor = defaultFunctor "attrTag" // { 754 type = { tags, ... }: types.attrTag tags; 755 payload = { inherit tags; }; 756 binOp = 757 let 758 # Add metadata in the format that submodules work with 759 wrapOptionDecl = 760 option: { options = option; _file = "<attrTag {...}>"; pos = null; }; 761 in 762 a: b: { 763 tags = a.tags // b.tags // 764 mapAttrs 765 (tagName: bOpt: 766 lib.mergeOptionDecls 767 # FIXME: loc is not accurate; should include prefix 768 # Fortunately, it's only used for error messages, where a "relative" location is kinda ok. 769 # It is also returned though, but use of the attribute seems rare? 770 [tagName] 771 [ (wrapOptionDecl a.tags.${tagName}) (wrapOptionDecl bOpt) ] 772 // { 773 # mergeOptionDecls is not idempotent in these attrs: 774 declarations = a.tags.${tagName}.declarations ++ bOpt.declarations; 775 declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions; 776 } 777 ) 778 (builtins.intersectAttrs a.tags b.tags); 779 }; 780 }; 781 }; 782 783 uniq = unique { message = ""; }; 784 785 unique = { message }: type: mkOptionType rec { 786 name = "unique"; 787 inherit (type) description descriptionClass check; 788 merge = mergeUniqueOption { inherit message; inherit (type) merge; }; 789 emptyValue = type.emptyValue; 790 getSubOptions = type.getSubOptions; 791 getSubModules = type.getSubModules; 792 substSubModules = m: uniq (type.substSubModules m); 793 functor = (defaultFunctor name) // { wrapped = type; }; 794 nestedTypes.elemType = type; 795 }; 796 797 # Null or value of ... 798 nullOr = elemType: mkOptionType rec { 799 name = "nullOr"; 800 description = "null or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType}"; 801 descriptionClass = "conjunction"; 802 check = x: x == null || elemType.check x; 803 merge = loc: defs: 804 let nrNulls = count (def: def.value == null) defs; in 805 if nrNulls == length defs then null 806 else if nrNulls != 0 then 807 throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." 808 else elemType.merge loc defs; 809 emptyValue = { value = null; }; 810 getSubOptions = elemType.getSubOptions; 811 getSubModules = elemType.getSubModules; 812 substSubModules = m: nullOr (elemType.substSubModules m); 813 functor = (defaultFunctor name) // { wrapped = elemType; }; 814 nestedTypes.elemType = elemType; 815 }; 816 817 functionTo = elemType: mkOptionType { 818 name = "functionTo"; 819 description = "function that evaluates to a(n) ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; 820 descriptionClass = "composite"; 821 check = isFunction; 822 merge = loc: defs: 823 fnArgs: (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue; 824 getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]); 825 getSubModules = elemType.getSubModules; 826 substSubModules = m: functionTo (elemType.substSubModules m); 827 functor = (defaultFunctor "functionTo") // { wrapped = elemType; }; 828 nestedTypes.elemType = elemType; 829 }; 830 831 # A submodule (like typed attribute set). See NixOS manual. 832 submodule = modules: submoduleWith { 833 shorthandOnlyDefinesConfig = true; 834 modules = toList modules; 835 }; 836 837 # A module to be imported in some other part of the configuration. 838 deferredModule = deferredModuleWith { }; 839 840 # A module to be imported in some other part of the configuration. 841 # `staticModules`' options will be added to the documentation, unlike 842 # options declared via `config`. 843 deferredModuleWith = attrs@{ staticModules ? [] }: mkOptionType { 844 name = "deferredModule"; 845 description = "module"; 846 descriptionClass = "noun"; 847 check = x: isAttrs x || isFunction x || path.check x; 848 merge = loc: defs: { 849 imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs; 850 }; 851 inherit (submoduleWith { modules = staticModules; }) 852 getSubOptions 853 getSubModules; 854 substSubModules = m: deferredModuleWith (attrs // { 855 staticModules = m; 856 }); 857 functor = defaultFunctor "deferredModuleWith" // { 858 type = types.deferredModuleWith; 859 payload = { 860 inherit staticModules; 861 }; 862 binOp = lhs: rhs: { 863 staticModules = lhs.staticModules ++ rhs.staticModules; 864 }; 865 }; 866 }; 867 868 # The type of a type! 869 optionType = mkOptionType { 870 name = "optionType"; 871 description = "optionType"; 872 descriptionClass = "noun"; 873 check = value: value._type or null == "option-type"; 874 merge = loc: defs: 875 if length defs == 1 876 then (head defs).value 877 else let 878 # Prepares the type definitions for mergeOptionDecls, which 879 # annotates submodules types with file locations 880 optionModules = map ({ value, file }: 881 { 882 _file = file; 883 # There's no way to merge types directly from the module system, 884 # but we can cheat a bit by just declaring an option with the type 885 options = lib.mkOption { 886 type = value; 887 }; 888 } 889 ) defs; 890 # Merges all the types into a single one, including submodule merging. 891 # This also propagates file information to all submodules 892 mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules); 893 in mergedOption.type; 894 }; 895 896 submoduleWith = 897 { modules 898 , specialArgs ? {} 899 , shorthandOnlyDefinesConfig ? false 900 , description ? null 901 , class ? null 902 }@attrs: 903 let 904 inherit (lib.modules) evalModules; 905 906 allModules = defs: map ({ value, file }: 907 if isAttrs value && shorthandOnlyDefinesConfig 908 then { _file = file; config = value; } 909 else { _file = file; imports = [ value ]; } 910 ) defs; 911 912 base = evalModules { 913 inherit class specialArgs; 914 modules = [{ 915 # This is a work-around for the fact that some sub-modules, 916 # such as the one included in an attribute set, expects an "args" 917 # attribute to be given to the sub-module. As the option 918 # evaluation does not have any specific attribute name yet, we 919 # provide a default for the documentation and the freeform type. 920 # 921 # This is necessary as some option declaration might use the 922 # "name" attribute given as argument of the submodule and use it 923 # as the default of option declarations. 924 # 925 # We use lookalike unicode single angle quotation marks because 926 # of the docbook transformation the options receive. In all uses 927 # &gt; and &lt; wouldn't be encoded correctly so the encoded values 928 # would be used, and use of `<` and `>` would break the XML document. 929 # It shouldn't cause an issue since this is cosmetic for the manual. 930 _module.args.name = lib.mkOptionDefault "name"; 931 }] ++ modules; 932 }; 933 934 freeformType = base._module.freeformType; 935 936 name = "submodule"; 937 938 in 939 mkOptionType { 940 inherit name; 941 description = 942 if description != null then description 943 else freeformType.description or name; 944 check = x: isAttrs x || isFunction x || path.check x; 945 merge = loc: defs: 946 (base.extendModules { 947 modules = [ { _module.args.name = last loc; } ] ++ allModules defs; 948 prefix = loc; 949 }).config; 950 emptyValue = { value = {}; }; 951 getSubOptions = prefix: (base.extendModules 952 { inherit prefix; }).options // optionalAttrs (freeformType != null) { 953 # Expose the sub options of the freeform type. Note that the option 954 # discovery doesn't care about the attribute name used here, so this 955 # is just to avoid conflicts with potential options from the submodule 956 _freeformOptions = freeformType.getSubOptions prefix; 957 }; 958 getSubModules = modules; 959 substSubModules = m: submoduleWith (attrs // { 960 modules = m; 961 }); 962 nestedTypes = lib.optionalAttrs (freeformType != null) { 963 freeformType = freeformType; 964 }; 965 functor = defaultFunctor name // { 966 type = types.submoduleWith; 967 payload = { 968 inherit modules class specialArgs shorthandOnlyDefinesConfig description; 969 }; 970 binOp = lhs: rhs: { 971 class = 972 # `or null` was added for backwards compatibility only. `class` is 973 # always set in the current version of the module system. 974 if lhs.class or null == null then rhs.class or null 975 else if rhs.class or null == null then lhs.class or null 976 else if lhs.class or null == rhs.class then lhs.class or null 977 else throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\"."; 978 modules = lhs.modules ++ rhs.modules; 979 specialArgs = 980 let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs; 981 in if intersecting == {} 982 then lhs.specialArgs // rhs.specialArgs 983 else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\""; 984 shorthandOnlyDefinesConfig = 985 if lhs.shorthandOnlyDefinesConfig == null 986 then rhs.shorthandOnlyDefinesConfig 987 else if rhs.shorthandOnlyDefinesConfig == null 988 then lhs.shorthandOnlyDefinesConfig 989 else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig 990 then lhs.shorthandOnlyDefinesConfig 991 else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values"; 992 description = 993 if lhs.description == null 994 then rhs.description 995 else if rhs.description == null 996 then lhs.description 997 else if lhs.description == rhs.description 998 then lhs.description 999 else throw "A submoduleWith option is declared multiple times with conflicting descriptions"; 1000 }; 1001 }; 1002 }; 1003 1004 # A value from a set of allowed ones. 1005 enum = values: 1006 let 1007 inherit (lib.lists) unique; 1008 show = v: 1009 if builtins.isString v then ''"${v}"'' 1010 else if builtins.isInt v then builtins.toString v 1011 else if builtins.isBool v then boolToString v 1012 else ''<${builtins.typeOf v}>''; 1013 in 1014 mkOptionType rec { 1015 name = "enum"; 1016 description = 1017 # Length 0 or 1 enums may occur in a design pattern with type merging 1018 # where an "interface" module declares an empty enum and other modules 1019 # provide implementations, each extending the enum with their own 1020 # identifier. 1021 if values == [] then 1022 "impossible (empty enum)" 1023 else if builtins.length values == 1 then 1024 "value ${show (builtins.head values)} (singular enum)" 1025 else 1026 "one of ${concatMapStringsSep ", " show values}"; 1027 descriptionClass = 1028 if builtins.length values < 2 1029 then "noun" 1030 else "conjunction"; 1031 check = flip elem values; 1032 merge = mergeEqualOption; 1033 functor = (defaultFunctor name) // { 1034 payload = { inherit values; }; 1035 type = payload: types.enum payload.values; 1036 binOp = a: b: { values = unique (a.values ++ b.values); }; 1037 }; 1038 }; 1039 1040 # Either value of type `t1` or `t2`. 1041 either = t1: t2: mkOptionType rec { 1042 name = "either"; 1043 description = 1044 if t1.descriptionClass or null == "nonRestrictiveClause" 1045 then 1046 # Plain, but add comma 1047 "${t1.description}, or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2}" 1048 else 1049 "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction" || class == "composite") t2}"; 1050 descriptionClass = "conjunction"; 1051 check = x: t1.check x || t2.check x; 1052 merge = loc: defs: 1053 let 1054 defList = map (d: d.value) defs; 1055 in 1056 if all (x: t1.check x) defList 1057 then t1.merge loc defs 1058 else if all (x: t2.check x) defList 1059 then t2.merge loc defs 1060 else mergeOneOption loc defs; 1061 typeMerge = f': 1062 let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor; 1063 mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor; 1064 in 1065 if (name == f'.name) && (mt1 != null) && (mt2 != null) 1066 then functor.type mt1 mt2 1067 else null; 1068 functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; }; 1069 nestedTypes.left = t1; 1070 nestedTypes.right = t2; 1071 }; 1072 1073 # Any of the types in the given list 1074 oneOf = ts: 1075 let 1076 head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts; 1077 tail' = tail ts; 1078 in foldl' either head' tail'; 1079 1080 # Either value of type `coercedType` or `finalType`, the former is 1081 # converted to `finalType` using `coerceFunc`. 1082 coercedTo = coercedType: coerceFunc: finalType: 1083 assert lib.assertMsg (coercedType.getSubModules == null) 1084 "coercedTo: coercedType must not have submodules (its a ${ 1085 coercedType.description})"; 1086 mkOptionType rec { 1087 name = "coercedTo"; 1088 description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${optionDescriptionPhrase (class: class == "noun") coercedType} convertible to it"; 1089 check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x; 1090 merge = loc: defs: 1091 let 1092 coerceVal = val: 1093 if coercedType.check val then coerceFunc val 1094 else val; 1095 in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); 1096 emptyValue = finalType.emptyValue; 1097 getSubOptions = finalType.getSubOptions; 1098 getSubModules = finalType.getSubModules; 1099 substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); 1100 typeMerge = t: null; 1101 functor = (defaultFunctor name) // { wrapped = finalType; }; 1102 nestedTypes.coercedType = coercedType; 1103 nestedTypes.finalType = finalType; 1104 }; 1105 1106 # Augment the given type with an additional type check function. 1107 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; }; 1108 1109 }; 1110}; 1111 1112in outer_types // outer_types.types