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