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