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