at 22.05-pre 25 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 functionArgs 10 isAttrs 11 isBool 12 isDerivation 13 isFloat 14 isFunction 15 isInt 16 isList 17 isString 18 isStorePath 19 setFunctionArgs 20 toDerivation 21 toList 22 ; 23 inherit (lib.lists) 24 all 25 concatLists 26 count 27 elemAt 28 filter 29 foldl' 30 head 31 imap1 32 last 33 length 34 tail 35 unique 36 ; 37 inherit (lib.attrsets) 38 attrNames 39 filterAttrs 40 hasAttr 41 mapAttrs 42 optionalAttrs 43 zipAttrsWith 44 ; 45 inherit (lib.options) 46 getFiles 47 getValues 48 mergeDefaultOption 49 mergeEqualOption 50 mergeOneOption 51 showFiles 52 showOption 53 ; 54 inherit (lib.strings) 55 concatMapStringsSep 56 concatStringsSep 57 escapeNixString 58 isCoercibleToString 59 ; 60 inherit (lib.trivial) 61 boolToString 62 ; 63 64 inherit (lib.modules) mergeDefinitions; 65 outer_types = 66rec { 67 isType = type: x: (x._type or "") == type; 68 69 setType = typeName: value: value // { 70 _type = typeName; 71 }; 72 73 74 # Default type merging function 75 # takes two type functors and return the merged type 76 defaultTypeMerge = f: f': 77 let wrapped = f.wrapped.typeMerge f'.wrapped.functor; 78 payload = f.binOp f.payload f'.payload; 79 in 80 # cannot merge different types 81 if f.name != f'.name 82 then null 83 # simple types 84 else if (f.wrapped == null && f'.wrapped == null) 85 && (f.payload == null && f'.payload == null) 86 then f.type 87 # composed types 88 else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null) 89 then f.type wrapped 90 # value types 91 else if (f.payload != null && f'.payload != null) && (payload != null) 92 then f.type payload 93 else null; 94 95 # Default type functor 96 defaultFunctor = name: { 97 inherit name; 98 type = types.${name} or null; 99 wrapped = null; 100 payload = null; 101 binOp = a: b: null; 102 }; 103 104 isOptionType = isType "option-type"; 105 mkOptionType = 106 { # Human-readable representation of the type, should be equivalent to 107 # the type function name. 108 name 109 , # Description of the type, defined recursively by embedding the wrapped type if any. 110 description ? null 111 , # Function applied to each definition that should return true if 112 # its type-correct, false otherwise. 113 check ? (x: true) 114 , # Merge a list of definitions together into a single value. 115 # This function is called with two arguments: the location of 116 # the option in the configuration as a list of strings 117 # (e.g. ["boot" "loader "grub" "enable"]), and a list of 118 # definition values and locations (e.g. [ { file = "/foo.nix"; 119 # value = 1; } { file = "/bar.nix"; value = 2 } ]). 120 merge ? mergeDefaultOption 121 , # Whether this type has a value representing nothingness. If it does, 122 # this should be a value of the form { value = <the nothing value>; } 123 # If it doesn't, this should be {} 124 # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`. 125 emptyValue ? {} 126 , # Return a flat list of sub-options. Used to generate 127 # documentation. 128 getSubOptions ? prefix: {} 129 , # List of modules if any, or null if none. 130 getSubModules ? null 131 , # Function for building the same option type with a different list of 132 # modules. 133 substSubModules ? m: null 134 , # Function that merge type declarations. 135 # internal, takes a functor as argument and returns the merged type. 136 # returning null means the type is not mergeable 137 typeMerge ? defaultTypeMerge functor 138 , # The type functor. 139 # internal, representation of the type as an attribute set. 140 # name: name of the type 141 # type: type function. 142 # wrapped: the type wrapped in case of compound types. 143 # payload: values of the type, two payloads of the same type must be 144 # combinable with the binOp binary operation. 145 # binOp: binary operation that merge two payloads of the same type. 146 functor ? defaultFunctor name 147 , # The deprecation message to display when this type is used by an option 148 # If null, the type isn't deprecated 149 deprecationMessage ? null 150 , # The types that occur in the definition of this type. This is used to 151 # issue deprecation warnings recursively. Can also be used to reuse 152 # nested types 153 nestedTypes ? {} 154 }: 155 { _type = "option-type"; 156 inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage nestedTypes; 157 description = if description == null then name else description; 158 }; 159 160 161 # When adding new types don't forget to document them in 162 # nixos/doc/manual/development/option-types.xml! 163 types = rec { 164 165 anything = mkOptionType { 166 name = "anything"; 167 description = "anything"; 168 check = value: true; 169 merge = loc: defs: 170 let 171 getType = value: 172 if isAttrs value && isCoercibleToString value 173 then "stringCoercibleSet" 174 else builtins.typeOf value; 175 176 # Returns the common type of all definitions, throws an error if they 177 # don't have the same type 178 commonType = foldl' (type: def: 179 if getType def.value == type 180 then type 181 else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" 182 ) (getType (head defs).value) defs; 183 184 mergeFunction = { 185 # Recursively merge attribute sets 186 set = (attrsOf anything).merge; 187 # Safe and deterministic behavior for lists is to only accept one definition 188 # listOf only used to apply mkIf and co. 189 list = 190 if length defs > 1 191 then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." 192 else (listOf anything).merge; 193 # This is the type of packages, only accept a single definition 194 stringCoercibleSet = mergeOneOption; 195 lambda = loc: defs: arg: anything.merge 196 (loc ++ [ "<function body>" ]) 197 (map (def: { 198 file = def.file; 199 value = def.value arg; 200 }) defs); 201 # Otherwise fall back to only allowing all equal definitions 202 }.${commonType} or mergeEqualOption; 203 in mergeFunction loc defs; 204 }; 205 206 unspecified = mkOptionType { 207 name = "unspecified"; 208 }; 209 210 bool = mkOptionType { 211 name = "bool"; 212 description = "boolean"; 213 check = isBool; 214 merge = mergeEqualOption; 215 }; 216 217 int = mkOptionType { 218 name = "int"; 219 description = "signed integer"; 220 check = isInt; 221 merge = mergeEqualOption; 222 }; 223 224 # Specialized subdomains of int 225 ints = 226 let 227 betweenDesc = lowest: highest: 228 "${toString lowest} and ${toString highest} (both inclusive)"; 229 between = lowest: highest: 230 assert lib.assertMsg (lowest <= highest) 231 "ints.between: lowest must be smaller than highest"; 232 addCheck int (x: x >= lowest && x <= highest) // { 233 name = "intBetween"; 234 description = "integer between ${betweenDesc lowest highest}"; 235 }; 236 ign = lowest: highest: name: docStart: 237 between lowest highest // { 238 inherit name; 239 description = docStart + "; between ${betweenDesc lowest highest}"; 240 }; 241 unsign = bit: range: ign 0 (range - 1) 242 "unsignedInt${toString bit}" "${toString bit} bit unsigned integer"; 243 sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1) 244 "signedInt${toString bit}" "${toString bit} bit signed integer"; 245 246 in { 247 /* An int with a fixed range. 248 * 249 * Example: 250 * (ints.between 0 100).check (-1) 251 * => false 252 * (ints.between 0 100).check (101) 253 * => false 254 * (ints.between 0 0).check 0 255 * => true 256 */ 257 inherit between; 258 259 unsigned = addCheck types.int (x: x >= 0) // { 260 name = "unsignedInt"; 261 description = "unsigned integer, meaning >=0"; 262 }; 263 positive = addCheck types.int (x: x > 0) // { 264 name = "positiveInt"; 265 description = "positive integer, meaning >0"; 266 }; 267 u8 = unsign 8 256; 268 u16 = unsign 16 65536; 269 # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808) 270 # the smallest int Nix accepts is -2^63 (-9223372036854775807) 271 u32 = unsign 32 4294967296; 272 # u64 = unsign 64 18446744073709551616; 273 274 s8 = sign 8 256; 275 s16 = sign 16 65536; 276 s32 = sign 32 4294967296; 277 }; 278 279 # Alias of u16 for a port number 280 port = ints.u16; 281 282 float = mkOptionType { 283 name = "float"; 284 description = "floating point number"; 285 check = isFloat; 286 merge = mergeEqualOption; 287 }; 288 289 str = mkOptionType { 290 name = "str"; 291 description = "string"; 292 check = isString; 293 merge = mergeEqualOption; 294 }; 295 296 nonEmptyStr = mkOptionType { 297 name = "nonEmptyStr"; 298 description = "non-empty string"; 299 check = x: str.check x && builtins.match "[ \t\n]*" x == null; 300 inherit (str) merge; 301 }; 302 303 strMatching = pattern: mkOptionType { 304 name = "strMatching ${escapeNixString pattern}"; 305 description = "string matching the pattern ${pattern}"; 306 check = x: str.check x && builtins.match pattern x != null; 307 inherit (str) merge; 308 }; 309 310 # Merge multiple definitions by concatenating them (with the given 311 # separator between the values). 312 separatedString = sep: mkOptionType rec { 313 name = "separatedString"; 314 description = if sep == "" 315 then "Concatenated string" # for types.string. 316 else "strings concatenated with ${builtins.toJSON sep}" 317 ; 318 check = isString; 319 merge = loc: defs: concatStringsSep sep (getValues defs); 320 functor = (defaultFunctor name) // { 321 payload = sep; 322 binOp = sepLhs: sepRhs: 323 if sepLhs == sepRhs then sepLhs 324 else null; 325 }; 326 }; 327 328 lines = separatedString "\n"; 329 commas = separatedString ","; 330 envVar = separatedString ":"; 331 332 # Deprecated; should not be used because it quietly concatenates 333 # strings, which is usually not what you want. 334 string = separatedString "" // { 335 name = "string"; 336 deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types."; 337 }; 338 339 attrs = mkOptionType { 340 name = "attrs"; 341 description = "attribute set"; 342 check = isAttrs; 343 merge = loc: foldl' (res: def: res // def.value) {}; 344 emptyValue = { value = {}; }; 345 }; 346 347 # derivation is a reserved keyword. 348 package = mkOptionType { 349 name = "package"; 350 check = x: isDerivation x || isStorePath x; 351 merge = loc: defs: 352 let res = mergeOneOption loc defs; 353 in if isDerivation res then res else toDerivation res; 354 }; 355 356 shellPackage = package // { 357 check = x: isDerivation x && hasAttr "shellPath" x; 358 }; 359 360 path = mkOptionType { 361 name = "path"; 362 check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/"; 363 merge = mergeEqualOption; 364 }; 365 366 listOf = elemType: mkOptionType rec { 367 name = "listOf"; 368 description = "list of ${elemType.description}s"; 369 check = isList; 370 merge = loc: defs: 371 map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def: 372 imap1 (m: def': 373 (mergeDefinitions 374 (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) 375 elemType 376 [{ inherit (def) file; value = def'; }] 377 ).optionalValue 378 ) def.value 379 ) defs))); 380 emptyValue = { value = {}; }; 381 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); 382 getSubModules = elemType.getSubModules; 383 substSubModules = m: listOf (elemType.substSubModules m); 384 functor = (defaultFunctor name) // { wrapped = elemType; }; 385 nestedTypes.elemType = elemType; 386 }; 387 388 nonEmptyListOf = elemType: 389 let list = addCheck (types.listOf elemType) (l: l != []); 390 in list // { 391 description = "non-empty " + list.description; 392 # Note: emptyValue is left as is, because another module may define an element. 393 }; 394 395 attrsOf = elemType: mkOptionType rec { 396 name = "attrsOf"; 397 description = "attribute set of ${elemType.description}s"; 398 check = isAttrs; 399 merge = loc: defs: 400 mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs: 401 (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue 402 ) 403 # Push down position info. 404 (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs))); 405 emptyValue = { value = {}; }; 406 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]); 407 getSubModules = elemType.getSubModules; 408 substSubModules = m: attrsOf (elemType.substSubModules m); 409 functor = (defaultFunctor name) // { wrapped = elemType; }; 410 nestedTypes.elemType = elemType; 411 }; 412 413 # A version of attrsOf that's lazy in its values at the expense of 414 # conditional definitions not working properly. E.g. defining a value with 415 # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with 416 # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an 417 # error that it's not defined. Use only if conditional definitions don't make sense. 418 lazyAttrsOf = elemType: mkOptionType rec { 419 name = "lazyAttrsOf"; 420 description = "lazy attribute set of ${elemType.description}s"; 421 check = isAttrs; 422 merge = loc: defs: 423 zipAttrsWith (name: defs: 424 let merged = mergeDefinitions (loc ++ [name]) elemType defs; 425 # mergedValue will trigger an appropriate error when accessed 426 in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue 427 ) 428 # Push down position info. 429 (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs); 430 emptyValue = { value = {}; }; 431 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]); 432 getSubModules = elemType.getSubModules; 433 substSubModules = m: lazyAttrsOf (elemType.substSubModules m); 434 functor = (defaultFunctor name) // { wrapped = elemType; }; 435 nestedTypes.elemType = elemType; 436 }; 437 438 # TODO: drop this in the future: 439 loaOf = elemType: types.attrsOf elemType // { 440 name = "loaOf"; 441 deprecationMessage = "Mixing lists with attribute values is no longer" 442 + " possible; please use `types.attrsOf` instead. See" 443 + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation."; 444 nestedTypes.elemType = elemType; 445 }; 446 447 # Value of given type but with no merging (i.e. `uniq list`s are not concatenated). 448 uniq = elemType: mkOptionType rec { 449 name = "uniq"; 450 inherit (elemType) description check; 451 merge = mergeOneOption; 452 emptyValue = elemType.emptyValue; 453 getSubOptions = elemType.getSubOptions; 454 getSubModules = elemType.getSubModules; 455 substSubModules = m: uniq (elemType.substSubModules m); 456 functor = (defaultFunctor name) // { wrapped = elemType; }; 457 nestedTypes.elemType = elemType; 458 }; 459 460 # Null or value of ... 461 nullOr = elemType: mkOptionType rec { 462 name = "nullOr"; 463 description = "null or ${elemType.description}"; 464 check = x: x == null || elemType.check x; 465 merge = loc: defs: 466 let nrNulls = count (def: def.value == null) defs; in 467 if nrNulls == length defs then null 468 else if nrNulls != 0 then 469 throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." 470 else elemType.merge loc defs; 471 emptyValue = { value = null; }; 472 getSubOptions = elemType.getSubOptions; 473 getSubModules = elemType.getSubModules; 474 substSubModules = m: nullOr (elemType.substSubModules m); 475 functor = (defaultFunctor name) // { wrapped = elemType; }; 476 nestedTypes.elemType = elemType; 477 }; 478 479 functionTo = elemType: mkOptionType { 480 name = "functionTo"; 481 description = "function that evaluates to a(n) ${elemType.name}"; 482 check = isFunction; 483 merge = loc: defs: 484 fnArgs: (mergeDefinitions (loc ++ [ "[function body]" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue; 485 getSubOptions = elemType.getSubOptions; 486 getSubModules = elemType.getSubModules; 487 substSubModules = m: functionTo (elemType.substSubModules m); 488 }; 489 490 # A submodule (like typed attribute set). See NixOS manual. 491 submodule = modules: submoduleWith { 492 shorthandOnlyDefinesConfig = true; 493 modules = toList modules; 494 }; 495 496 submoduleWith = 497 { modules 498 , specialArgs ? {} 499 , shorthandOnlyDefinesConfig ? false 500 }@attrs: 501 let 502 inherit (lib.modules) evalModules; 503 504 coerce = unify: value: if isFunction value 505 then setFunctionArgs (args: unify (value args)) (functionArgs value) 506 else unify (if shorthandOnlyDefinesConfig then { config = value; } else value); 507 508 allModules = defs: imap1 (n: { value, file }: 509 if isAttrs value || isFunction value then 510 # Annotate the value with the location of its definition for better error messages 511 coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value 512 else value 513 ) defs; 514 515 base = evalModules { 516 inherit specialArgs; 517 modules = [{ 518 # This is a work-around for the fact that some sub-modules, 519 # such as the one included in an attribute set, expects an "args" 520 # attribute to be given to the sub-module. As the option 521 # evaluation does not have any specific attribute name yet, we 522 # provide a default for the documentation and the freeform type. 523 # 524 # This is necessary as some option declaration might use the 525 # "name" attribute given as argument of the submodule and use it 526 # as the default of option declarations. 527 # 528 # We use lookalike unicode single angle quotation marks because 529 # of the docbook transformation the options receive. In all uses 530 # &gt; and &lt; wouldn't be encoded correctly so the encoded values 531 # would be used, and use of `<` and `>` would break the XML document. 532 # It shouldn't cause an issue since this is cosmetic for the manual. 533 _module.args.name = lib.mkOptionDefault "name"; 534 }] ++ modules; 535 }; 536 537 freeformType = base._module.freeformType; 538 539 in 540 mkOptionType rec { 541 name = "submodule"; 542 description = freeformType.description or name; 543 check = x: isAttrs x || isFunction x || path.check x; 544 merge = loc: defs: 545 (base.extendModules { 546 modules = [ { _module.args.name = last loc; } ] ++ allModules defs; 547 prefix = loc; 548 }).config; 549 emptyValue = { value = {}; }; 550 getSubOptions = prefix: (base.extendModules 551 { inherit prefix; }).options // optionalAttrs (freeformType != null) { 552 # Expose the sub options of the freeform type. Note that the option 553 # discovery doesn't care about the attribute name used here, so this 554 # is just to avoid conflicts with potential options from the submodule 555 _freeformOptions = freeformType.getSubOptions prefix; 556 }; 557 getSubModules = modules; 558 substSubModules = m: submoduleWith (attrs // { 559 modules = m; 560 }); 561 nestedTypes = lib.optionalAttrs (freeformType != null) { 562 freeformType = freeformType; 563 }; 564 functor = defaultFunctor name // { 565 type = types.submoduleWith; 566 payload = { 567 modules = modules; 568 specialArgs = specialArgs; 569 shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig; 570 }; 571 binOp = lhs: rhs: { 572 modules = lhs.modules ++ rhs.modules; 573 specialArgs = 574 let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs; 575 in if intersecting == {} 576 then lhs.specialArgs // rhs.specialArgs 577 else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\""; 578 shorthandOnlyDefinesConfig = 579 if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig 580 then lhs.shorthandOnlyDefinesConfig 581 else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values"; 582 }; 583 }; 584 }; 585 586 # A value from a set of allowed ones. 587 enum = values: 588 let 589 show = v: 590 if builtins.isString v then ''"${v}"'' 591 else if builtins.isInt v then builtins.toString v 592 else if builtins.isBool v then boolToString v 593 else ''<${builtins.typeOf v}>''; 594 in 595 mkOptionType rec { 596 name = "enum"; 597 description = 598 # Length 0 or 1 enums may occur in a design pattern with type merging 599 # where an "interface" module declares an empty enum and other modules 600 # provide implementations, each extending the enum with their own 601 # identifier. 602 if values == [] then 603 "impossible (empty enum)" 604 else if builtins.length values == 1 then 605 "value ${show (builtins.head values)} (singular enum)" 606 else 607 "one of ${concatMapStringsSep ", " show values}"; 608 check = flip elem values; 609 merge = mergeEqualOption; 610 functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); }; 611 }; 612 613 # Either value of type `t1` or `t2`. 614 either = t1: t2: mkOptionType rec { 615 name = "either"; 616 description = "${t1.description} or ${t2.description}"; 617 check = x: t1.check x || t2.check x; 618 merge = loc: defs: 619 let 620 defList = map (d: d.value) defs; 621 in 622 if all (x: t1.check x) defList 623 then t1.merge loc defs 624 else if all (x: t2.check x) defList 625 then t2.merge loc defs 626 else mergeOneOption loc defs; 627 typeMerge = f': 628 let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor; 629 mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor; 630 in 631 if (name == f'.name) && (mt1 != null) && (mt2 != null) 632 then functor.type mt1 mt2 633 else null; 634 functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; }; 635 nestedTypes.left = t1; 636 nestedTypes.right = t2; 637 }; 638 639 # Any of the types in the given list 640 oneOf = ts: 641 let 642 head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts; 643 tail' = tail ts; 644 in foldl' either head' tail'; 645 646 # Either value of type `coercedType` or `finalType`, the former is 647 # converted to `finalType` using `coerceFunc`. 648 coercedTo = coercedType: coerceFunc: finalType: 649 assert lib.assertMsg (coercedType.getSubModules == null) 650 "coercedTo: coercedType must not have submodules (its a ${ 651 coercedType.description})"; 652 mkOptionType rec { 653 name = "coercedTo"; 654 description = "${finalType.description} or ${coercedType.description} convertible to it"; 655 check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x; 656 merge = loc: defs: 657 let 658 coerceVal = val: 659 if coercedType.check val then coerceFunc val 660 else val; 661 in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); 662 emptyValue = finalType.emptyValue; 663 getSubOptions = finalType.getSubOptions; 664 getSubModules = finalType.getSubModules; 665 substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); 666 typeMerge = t1: t2: null; 667 functor = (defaultFunctor name) // { wrapped = finalType; }; 668 nestedTypes.coercedType = coercedType; 669 nestedTypes.finalType = finalType; 670 }; 671 672 # Obsolete alternative to configOf. It takes its option 673 # declarations from the ‘options’ attribute of containing option 674 # declaration. 675 optionSet = mkOptionType { 676 name = "optionSet"; 677 description = "option set"; 678 deprecationMessage = "Use `types.submodule' instead"; 679 }; 680 # Augment the given type with an additional type check function. 681 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; }; 682 683 }; 684}; 685 686in outer_types // outer_types.types