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