electron-source.electron_29: remove as it's EOL
[NixPkgs.git] / lib / types.nix
blob8b1d19ff2c6134cc975845663e2e2ac6c217a765
1 # Definitions related to run-time type checking.  Used in particular
2 # to type-check NixOS configurations.
3 { lib }:
5 let
6   inherit (lib)
7     elem
8     flip
9     isAttrs
10     isBool
11     isDerivation
12     isFloat
13     isFunction
14     isInt
15     isList
16     isString
17     isStorePath
18     throwIf
19     toDerivation
20     toList
21     ;
22   inherit (lib.lists)
23     all
24     concatLists
25     count
26     elemAt
27     filter
28     foldl'
29     head
30     imap1
31     last
32     length
33     tail
34     ;
35   inherit (lib.attrsets)
36     attrNames
37     filterAttrs
38     hasAttr
39     mapAttrs
40     optionalAttrs
41     zipAttrsWith
42     ;
43   inherit (lib.options)
44     getFiles
45     getValues
46     mergeDefaultOption
47     mergeEqualOption
48     mergeOneOption
49     mergeUniqueOption
50     showFiles
51     showOption
52     ;
53   inherit (lib.strings)
54     concatMapStringsSep
55     concatStringsSep
56     escapeNixString
57     hasInfix
58     isStringLike
59     ;
60   inherit (lib.trivial)
61     boolToString
62     ;
64   inherit (lib.modules)
65     mergeDefinitions
66     fixupOptionType
67     mergeOptionDecls
68     ;
70   inAttrPosSuffix = v: name:
71     let pos = builtins.unsafeGetAttrPos name v; in
72     if pos == null then "" else " at ${pos.file}:${toString pos.line}:${toString pos.column}";
74   outer_types =
75 rec {
76   isType = type: x: (x._type or "") == type;
78   setType = typeName: value: value // {
79     _type = typeName;
80   };
83   # Default type merging function
84   # takes two type functors and return the merged type
85   defaultTypeMerge = f: f':
86     let wrapped = f.wrapped.typeMerge f'.wrapped.functor;
87         payload = f.binOp f.payload f'.payload;
88     in
89     # cannot merge different types
90     if f.name != f'.name
91        then null
92     # simple types
93     else if    (f.wrapped == null && f'.wrapped == null)
94             && (f.payload == null && f'.payload == null)
95        then f.type
96     # composed types
97     else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
98        then f.type wrapped
99     # value types
100     else if (f.payload != null && f'.payload != null) && (payload != null)
101        then f.type payload
102     else null;
104   # Default type functor
105   defaultFunctor = name: {
106     inherit name;
107     type    = types.${name} or null;
108     wrapped = null;
109     payload = null;
110     binOp   = a: b: null;
111   };
113   isOptionType = isType "option-type";
114   mkOptionType =
115     { # Human-readable representation of the type, should be equivalent to
116       # the type function name.
117       name
118     , # Description of the type, defined recursively by embedding the wrapped type if any.
119       description ? null
120       # A hint for whether or not this description needs parentheses. Possible values:
121       #  - "noun": a noun phrase
122       #    Example description: "positive integer",
123       #  - "conjunction": a phrase with a potentially ambiguous "or" connective
124       #    Example description: "int or string"
125       #  - "composite": a phrase with an "of" connective
126       #    Example description: "list of string"
127       #  - "nonRestrictiveClause": a noun followed by a comma and a clause
128       #    Example description: "positive integer, meaning >0"
129       # See the `optionDescriptionPhrase` function.
130     , descriptionClass ? null
131     , # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING!
132       # Function applied to each definition that must return false when a definition
133       # does not match the type. It should not check more than the root of the value,
134       # because checking nested values reduces laziness, leading to unnecessary
135       # infinite recursions in the module system.
136       # Further checks of nested values should be performed by throwing in
137       # the merge function.
138       # Strict and deep type checking can be performed by calling lib.deepSeq on
139       # the merged value.
140       #
141       # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change,
142       # https://github.com/NixOS/nixpkgs/pull/173568 and
143       # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this,
144       # https://github.com/NixOS/nixpkgs/issues/191124 and
145       # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore
146       # this disclaimer.
147       check ? (x: true)
148     , # Merge a list of definitions together into a single value.
149       # This function is called with two arguments: the location of
150       # the option in the configuration as a list of strings
151       # (e.g. ["boot" "loader "grub" "enable"]), and a list of
152       # definition values and locations (e.g. [ { file = "/foo.nix";
153       # value = 1; } { file = "/bar.nix"; value = 2 } ]).
154       merge ? mergeDefaultOption
155     , # Whether this type has a value representing nothingness. If it does,
156       # this should be a value of the form { value = <the nothing value>; }
157       # If it doesn't, this should be {}
158       # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
159       emptyValue ? {}
160     , # Return a flat attrset of sub-options.  Used to generate
161       # documentation.
162       getSubOptions ? prefix: {}
163     , # List of modules if any, or null if none.
164       getSubModules ? null
165     , # Function for building the same option type with a different list of
166       # modules.
167       substSubModules ? m: null
168     , # Function that merge type declarations.
169       # internal, takes a functor as argument and returns the merged type.
170       # returning null means the type is not mergeable
171       typeMerge ? defaultTypeMerge functor
172     , # The type functor.
173       # internal, representation of the type as an attribute set.
174       #   name: name of the type
175       #   type: type function.
176       #   wrapped: the type wrapped in case of compound types.
177       #   payload: values of the type, two payloads of the same type must be
178       #            combinable with the binOp binary operation.
179       #   binOp: binary operation that merge two payloads of the same type.
180       functor ? defaultFunctor name
181     , # The deprecation message to display when this type is used by an option
182       # If null, the type isn't deprecated
183       deprecationMessage ? null
184     , # The types that occur in the definition of this type. This is used to
185       # issue deprecation warnings recursively. Can also be used to reuse
186       # nested types
187       nestedTypes ? {}
188     }:
189     { _type = "option-type";
190       inherit
191         name check merge emptyValue getSubOptions getSubModules substSubModules
192         typeMerge functor deprecationMessage nestedTypes descriptionClass;
193       description = if description == null then name else description;
194     };
196   # optionDescriptionPhrase :: (str -> bool) -> optionType -> str
197   #
198   # Helper function for producing unambiguous but readable natural language
199   # descriptions of types.
200   #
201   # Parameters
202   #
203   #     optionDescriptionPhase unparenthesize optionType
204   #
205   # `unparenthesize`: A function from descriptionClass string to boolean.
206   #   It must return true when the class of phrase will fit unambiguously into
207   #   the description of the caller.
208   #
209   # `optionType`: The option type to parenthesize or not.
210   #   The option whose description we're returning.
211   #
212   # Return value
213   #
214   # The description of the `optionType`, with parentheses if there may be an
215   # ambiguity.
216   optionDescriptionPhrase = unparenthesize: t:
217     if unparenthesize (t.descriptionClass or null)
218     then t.description
219     else "(${t.description})";
221   # When adding new types don't forget to document them in
222   # nixos/doc/manual/development/option-types.section.md!
223   types = rec {
225     raw = mkOptionType {
226       name = "raw";
227       description = "raw value";
228       descriptionClass = "noun";
229       check = value: true;
230       merge = mergeOneOption;
231     };
233     anything = mkOptionType {
234       name = "anything";
235       description = "anything";
236       descriptionClass = "noun";
237       check = value: true;
238       merge = loc: defs:
239         let
240           getType = value:
241             if isAttrs value && isStringLike value
242             then "stringCoercibleSet"
243             else builtins.typeOf value;
245           # Returns the common type of all definitions, throws an error if they
246           # don't have the same type
247           commonType = foldl' (type: def:
248             if getType def.value == type
249             then type
250             else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
251           ) (getType (head defs).value) defs;
253           mergeFunction = {
254             # Recursively merge attribute sets
255             set = (attrsOf anything).merge;
256             # Safe and deterministic behavior for lists is to only accept one definition
257             # listOf only used to apply mkIf and co.
258             list =
259               if length defs > 1
260               then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
261               else (listOf anything).merge;
262             # This is the type of packages, only accept a single definition
263             stringCoercibleSet = mergeOneOption;
264             lambda = loc: defs: arg: anything.merge
265               (loc ++ [ "<function body>" ])
266               (map (def: {
267                 file = def.file;
268                 value = def.value arg;
269               }) defs);
270             # Otherwise fall back to only allowing all equal definitions
271           }.${commonType} or mergeEqualOption;
272         in mergeFunction loc defs;
273     };
275     unspecified = mkOptionType {
276       name = "unspecified";
277       description = "unspecified value";
278       descriptionClass = "noun";
279     };
281     bool = mkOptionType {
282       name = "bool";
283       description = "boolean";
284       descriptionClass = "noun";
285       check = isBool;
286       merge = mergeEqualOption;
287     };
289     boolByOr = mkOptionType {
290       name = "boolByOr";
291       description = "boolean (merged using or)";
292       descriptionClass = "noun";
293       check = isBool;
294       merge = loc: defs:
295         foldl'
296           (result: def:
297             # Under the assumption that .check always runs before merge, we can assume that all defs.*.value
298             # have been forced, and therefore we assume we don't introduce order-dependent strictness here
299             result || def.value
300           )
301           false
302           defs;
303     };
305     int = mkOptionType {
306       name = "int";
307       description = "signed integer";
308       descriptionClass = "noun";
309       check = isInt;
310       merge = mergeEqualOption;
311     };
313     # Specialized subdomains of int
314     ints =
315       let
316         betweenDesc = lowest: highest:
317           "${toString lowest} and ${toString highest} (both inclusive)";
318         between = lowest: highest:
319           assert lib.assertMsg (lowest <= highest)
320             "ints.between: lowest must be smaller than highest";
321           addCheck int (x: x >= lowest && x <= highest) // {
322             name = "intBetween";
323             description = "integer between ${betweenDesc lowest highest}";
324           };
325         ign = lowest: highest: name: docStart:
326           between lowest highest // {
327             inherit name;
328             description = docStart + "; between ${betweenDesc lowest highest}";
329           };
330         unsign = bit: range: ign 0 (range - 1)
331           "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
332         sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1)
333           "signedInt${toString bit}" "${toString bit} bit signed integer";
335       in {
336         # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md
337         /**
338           An int with a fixed range.
340           # Example
341           :::{.example}
342           ## `lib.types.ints.between` usage example
344           ```nix
345           (ints.between 0 100).check (-1)
346           => false
347           (ints.between 0 100).check (101)
348           => false
349           (ints.between 0 0).check 0
350           => true
351           ```
353           :::
354         */
355         inherit between;
357         unsigned = addCheck types.int (x: x >= 0) // {
358           name = "unsignedInt";
359           description = "unsigned integer, meaning >=0";
360           descriptionClass = "nonRestrictiveClause";
361         };
362         positive = addCheck types.int (x: x > 0) // {
363           name = "positiveInt";
364           description = "positive integer, meaning >0";
365           descriptionClass = "nonRestrictiveClause";
366         };
367         u8 = unsign 8 256;
368         u16 = unsign 16 65536;
369         # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
370         # the smallest int Nix accepts is -2^63 (-9223372036854775807)
371         u32 = unsign 32 4294967296;
372         # u64 = unsign 64 18446744073709551616;
374         s8 = sign 8 256;
375         s16 = sign 16 65536;
376         s32 = sign 32 4294967296;
377       };
379     # Alias of u16 for a port number
380     port = ints.u16;
382     float = mkOptionType {
383       name = "float";
384       description = "floating point number";
385       descriptionClass = "noun";
386       check = isFloat;
387       merge = mergeEqualOption;
388     };
390     number = either int float;
392     numbers = let
393       betweenDesc = lowest: highest:
394         "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)";
395     in {
396       between = lowest: highest:
397         assert lib.assertMsg (lowest <= highest)
398           "numbers.between: lowest must be smaller than highest";
399         addCheck number (x: x >= lowest && x <= highest) // {
400           name = "numberBetween";
401           description = "integer or floating point number between ${betweenDesc lowest highest}";
402         };
404       nonnegative = addCheck number (x: x >= 0) // {
405         name = "numberNonnegative";
406         description = "nonnegative integer or floating point number, meaning >=0";
407         descriptionClass = "nonRestrictiveClause";
408       };
409       positive = addCheck number (x: x > 0) // {
410         name = "numberPositive";
411         description = "positive integer or floating point number, meaning >0";
412         descriptionClass = "nonRestrictiveClause";
413       };
414     };
416     str = mkOptionType {
417       name = "str";
418       description = "string";
419       descriptionClass = "noun";
420       check = isString;
421       merge = mergeEqualOption;
422     };
424     nonEmptyStr = mkOptionType {
425       name = "nonEmptyStr";
426       description = "non-empty string";
427       descriptionClass = "noun";
428       check = x: str.check x && builtins.match "[ \t\n]*" x == null;
429       inherit (str) merge;
430     };
432     # Allow a newline character at the end and trim it in the merge function.
433     singleLineStr =
434       let
435         inherit (strMatching "[^\n\r]*\n?") check merge;
436       in
437       mkOptionType {
438         name = "singleLineStr";
439         description = "(optionally newline-terminated) single-line string";
440         descriptionClass = "noun";
441         inherit check;
442         merge = loc: defs:
443           lib.removeSuffix "\n" (merge loc defs);
444       };
446     strMatching = pattern: mkOptionType {
447       name = "strMatching ${escapeNixString pattern}";
448       description = "string matching the pattern ${pattern}";
449       descriptionClass = "noun";
450       check = x: str.check x && builtins.match pattern x != null;
451       inherit (str) merge;
452     };
454     # Merge multiple definitions by concatenating them (with the given
455     # separator between the values).
456     separatedString = sep: mkOptionType rec {
457       name = "separatedString";
458       description = if sep == ""
459         then "Concatenated string" # for types.string.
460         else "strings concatenated with ${builtins.toJSON sep}"
461       ;
462       descriptionClass = "noun";
463       check = isString;
464       merge = loc: defs: concatStringsSep sep (getValues defs);
465       functor = (defaultFunctor name) // {
466         payload = sep;
467         binOp = sepLhs: sepRhs:
468           if sepLhs == sepRhs then sepLhs
469           else null;
470       };
471     };
473     lines = separatedString "\n";
474     commas = separatedString ",";
475     envVar = separatedString ":";
477     # Deprecated; should not be used because it quietly concatenates
478     # strings, which is usually not what you want.
479     # We use a lib.warn because `deprecationMessage` doesn't trigger in nested types such as `attrsOf string`
480     string = lib.warn
481       "The type `types.string` is deprecated. See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types."
482       (separatedString "" // {
483         name = "string";
484       });
486     passwdEntry = entryType: addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) // {
487       name = "passwdEntry ${entryType.name}";
488       description = "${optionDescriptionPhrase (class: class == "noun") entryType}, not containing newlines or colons";
489       descriptionClass = "nonRestrictiveClause";
490     };
492     attrs = mkOptionType {
493       name = "attrs";
494       description = "attribute set";
495       check = isAttrs;
496       merge = loc: foldl' (res: def: res // def.value) {};
497       emptyValue = { value = {}; };
498     };
500     # A package is a top-level store path (/nix/store/hash-name). This includes:
501     # - derivations
502     # - more generally, attribute sets with an `outPath` or `__toString` attribute
503     #   pointing to a store path, e.g. flake inputs
504     # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
505     # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
506     #   ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
507     # If you don't need a *top-level* store path, consider using pathInStore instead.
508     package = mkOptionType {
509       name = "package";
510       descriptionClass = "noun";
511       check = x: isDerivation x || isStorePath x;
512       merge = loc: defs:
513         let res = mergeOneOption loc defs;
514         in if builtins.isPath res || (builtins.isString res && ! builtins.hasContext res)
515           then toDerivation res
516           else res;
517     };
519     shellPackage = package // {
520       check = x: isDerivation x && hasAttr "shellPath" x;
521     };
523     pkgs = addCheck
524       (unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs // {
525         name = "pkgs";
526         descriptionClass = "noun";
527         description = "Nixpkgs package set";
528       })
529       (x: (x._type or null) == "pkgs");
531     path = mkOptionType {
532       name = "path";
533       descriptionClass = "noun";
534       check = x: isStringLike x && builtins.substring 0 1 (toString x) == "/";
535       merge = mergeEqualOption;
536     };
538     pathInStore = mkOptionType {
539       name = "pathInStore";
540       description = "path in the Nix store";
541       descriptionClass = "noun";
542       check = x: isStringLike x && builtins.match "${builtins.storeDir}/[^.].*" (toString x) != null;
543       merge = mergeEqualOption;
544     };
546     listOf = elemType: mkOptionType rec {
547       name = "listOf";
548       description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
549       descriptionClass = "composite";
550       check = isList;
551       merge = loc: defs:
552         map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
553           imap1 (m: def':
554             (mergeDefinitions
555               (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
556               elemType
557               [{ inherit (def) file; value = def'; }]
558             ).optionalValue
559           ) def.value
560         ) defs)));
561       emptyValue = { value = []; };
562       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
563       getSubModules = elemType.getSubModules;
564       substSubModules = m: listOf (elemType.substSubModules m);
565       functor = (defaultFunctor name) // { wrapped = elemType; };
566       nestedTypes.elemType = elemType;
567     };
569     nonEmptyListOf = elemType:
570       let list = addCheck (types.listOf elemType) (l: l != []);
571       in list // {
572         description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
573         emptyValue = { }; # no .value attr, meaning unset
574         substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
575       };
577     attrsOf = elemType: mkOptionType rec {
578       name = "attrsOf";
579       description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
580       descriptionClass = "composite";
581       check = isAttrs;
582       merge = loc: defs:
583         mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
584             (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
585           )
586           # Push down position info.
587           (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
588       emptyValue = { value = {}; };
589       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
590       getSubModules = elemType.getSubModules;
591       substSubModules = m: attrsOf (elemType.substSubModules m);
592       functor = (defaultFunctor name) // { wrapped = elemType; };
593       nestedTypes.elemType = elemType;
594     };
596     # A version of attrsOf that's lazy in its values at the expense of
597     # conditional definitions not working properly. E.g. defining a value with
598     # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
599     # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
600     # error that it's not defined. Use only if conditional definitions don't make sense.
601     lazyAttrsOf = elemType: mkOptionType rec {
602       name = "lazyAttrsOf";
603       description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
604       descriptionClass = "composite";
605       check = isAttrs;
606       merge = loc: defs:
607         zipAttrsWith (name: defs:
608           let merged = mergeDefinitions (loc ++ [name]) elemType defs;
609           # mergedValue will trigger an appropriate error when accessed
610           in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
611         )
612         # Push down position info.
613         (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
614       emptyValue = { value = {}; };
615       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
616       getSubModules = elemType.getSubModules;
617       substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
618       functor = (defaultFunctor name) // { wrapped = elemType; };
619       nestedTypes.elemType = elemType;
620     };
622     # TODO: deprecate this in the future:
623     loaOf = elemType: types.attrsOf elemType // {
624       name = "loaOf";
625       deprecationMessage = "Mixing lists with attribute values is no longer"
626         + " possible; please use `types.attrsOf` instead. See"
627         + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
628       nestedTypes.elemType = elemType;
629     };
631     attrTag = tags:
632       let tags_ = tags; in
633       let
634         tags =
635           mapAttrs
636             (n: opt:
637               builtins.addErrorContext "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}" (
638                 throwIf (opt._type or null != "option")
639                   "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${
640                     if opt?_type then
641                       if opt._type == "option-type"
642                       then "was a bare type, not wrapped in mkOption."
643                       else "was of type ${lib.strings.escapeNixString opt._type}."
644                     else "was not."}"
645                 opt // {
646                   declarations = opt.declarations or (
647                     let pos = builtins.unsafeGetAttrPos n tags_;
648                     in if pos == null then [] else [ pos.file ]
649                   );
650                   declarationPositions = opt.declarationPositions or (
651                     let pos = builtins.unsafeGetAttrPos n tags_;
652                     in if pos == null then [] else [ pos ]
653                   );
654                 }
655               ))
656             tags_;
657         choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
658       in
659       mkOptionType {
660         name = "attrTag";
661         description = "attribute-tagged union";
662         descriptionClass = "noun";
663         getSubOptions = prefix:
664           mapAttrs
665             (tagName: tagOption: {
666               "${lib.showOption prefix}" =
667                 tagOption // {
668                   loc = prefix ++ [ tagName ];
669                 };
670             })
671             tags;
672         check = v: isAttrs v && length (attrNames v) == 1 && tags?${head (attrNames v)};
673         merge = loc: defs:
674           let
675             choice = head (attrNames (head defs).value);
676             checkedValueDefs = map
677               (def:
678                 assert (length (attrNames def.value)) == 1;
679                 if (head (attrNames def.value)) != choice
680                 then throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
681                 else { inherit (def) file; value = def.value.${choice}; })
682               defs;
683           in
684             if tags?${choice}
685             then
686               { ${choice} =
687                   (lib.modules.evalOptionValue
688                     (loc ++ [choice])
689                     tags.${choice}
690                     checkedValueDefs
691                   ).value;
692               }
693             else 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)}.";
694         nestedTypes = tags;
695         functor = defaultFunctor "attrTag" // {
696           type = { tags, ... }: types.attrTag tags;
697           payload = { inherit tags; };
698           binOp =
699             let
700               # Add metadata in the format that submodules work with
701               wrapOptionDecl =
702                 option: { options = option; _file = "<attrTag {...}>"; pos = null; };
703             in
704             a: b: {
705               tags = a.tags // b.tags //
706                 mapAttrs
707                   (tagName: bOpt:
708                     lib.mergeOptionDecls
709                       # FIXME: loc is not accurate; should include prefix
710                       #        Fortunately, it's only used for error messages, where a "relative" location is kinda ok.
711                       #        It is also returned though, but use of the attribute seems rare?
712                       [tagName]
713                       [ (wrapOptionDecl a.tags.${tagName}) (wrapOptionDecl bOpt) ]
714                     // {
715                       # mergeOptionDecls is not idempotent in these attrs:
716                       declarations = a.tags.${tagName}.declarations ++ bOpt.declarations;
717                       declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions;
718                     }
719                   )
720                   (builtins.intersectAttrs a.tags b.tags);
721           };
722         };
723       };
725     uniq = unique { message = ""; };
727     unique = { message }: type: mkOptionType rec {
728       name = "unique";
729       inherit (type) description descriptionClass check;
730       merge = mergeUniqueOption { inherit message; inherit (type) merge; };
731       emptyValue = type.emptyValue;
732       getSubOptions = type.getSubOptions;
733       getSubModules = type.getSubModules;
734       substSubModules = m: uniq (type.substSubModules m);
735       functor = (defaultFunctor name) // { wrapped = type; };
736       nestedTypes.elemType = type;
737     };
739     # Null or value of ...
740     nullOr = elemType: mkOptionType rec {
741       name = "nullOr";
742       description = "null or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType}";
743       descriptionClass = "conjunction";
744       check = x: x == null || elemType.check x;
745       merge = loc: defs:
746         let nrNulls = count (def: def.value == null) defs; in
747         if nrNulls == length defs then null
748         else if nrNulls != 0 then
749           throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
750         else elemType.merge loc defs;
751       emptyValue = { value = null; };
752       getSubOptions = elemType.getSubOptions;
753       getSubModules = elemType.getSubModules;
754       substSubModules = m: nullOr (elemType.substSubModules m);
755       functor = (defaultFunctor name) // { wrapped = elemType; };
756       nestedTypes.elemType = elemType;
757     };
759     functionTo = elemType: mkOptionType {
760       name = "functionTo";
761       description = "function that evaluates to a(n) ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
762       descriptionClass = "composite";
763       check = isFunction;
764       merge = loc: defs:
765         fnArgs: (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
766       getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
767       getSubModules = elemType.getSubModules;
768       substSubModules = m: functionTo (elemType.substSubModules m);
769       functor = (defaultFunctor "functionTo") // { wrapped = elemType; };
770       nestedTypes.elemType = elemType;
771     };
773     # A submodule (like typed attribute set). See NixOS manual.
774     submodule = modules: submoduleWith {
775       shorthandOnlyDefinesConfig = true;
776       modules = toList modules;
777     };
779     # A module to be imported in some other part of the configuration.
780     deferredModule = deferredModuleWith { };
782     # A module to be imported in some other part of the configuration.
783     # `staticModules`' options will be added to the documentation, unlike
784     # options declared via `config`.
785     deferredModuleWith = attrs@{ staticModules ? [] }: mkOptionType {
786       name = "deferredModule";
787       description = "module";
788       descriptionClass = "noun";
789       check = x: isAttrs x || isFunction x || path.check x;
790       merge = loc: defs: {
791         imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
792       };
793       inherit (submoduleWith { modules = staticModules; })
794         getSubOptions
795         getSubModules;
796       substSubModules = m: deferredModuleWith (attrs // {
797         staticModules = m;
798       });
799       functor = defaultFunctor "deferredModuleWith" // {
800         type = types.deferredModuleWith;
801         payload = {
802           inherit staticModules;
803         };
804         binOp = lhs: rhs: {
805           staticModules = lhs.staticModules ++ rhs.staticModules;
806         };
807       };
808     };
810     # The type of a type!
811     optionType = mkOptionType {
812       name = "optionType";
813       description = "optionType";
814       descriptionClass = "noun";
815       check = value: value._type or null == "option-type";
816       merge = loc: defs:
817         if length defs == 1
818         then (head defs).value
819         else let
820           # Prepares the type definitions for mergeOptionDecls, which
821           # annotates submodules types with file locations
822           optionModules = map ({ value, file }:
823             {
824               _file = file;
825               # There's no way to merge types directly from the module system,
826               # but we can cheat a bit by just declaring an option with the type
827               options = lib.mkOption {
828                 type = value;
829               };
830             }
831           ) defs;
832           # Merges all the types into a single one, including submodule merging.
833           # This also propagates file information to all submodules
834           mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
835         in mergedOption.type;
836     };
838     submoduleWith =
839       { modules
840       , specialArgs ? {}
841       , shorthandOnlyDefinesConfig ? false
842       , description ? null
843       , class ? null
844       }@attrs:
845       let
846         inherit (lib.modules) evalModules;
848         allModules = defs: map ({ value, file }:
849           if isAttrs value && shorthandOnlyDefinesConfig
850           then { _file = file; config = value; }
851           else { _file = file; imports = [ value ]; }
852         ) defs;
854         base = evalModules {
855           inherit class specialArgs;
856           modules = [{
857             # This is a work-around for the fact that some sub-modules,
858             # such as the one included in an attribute set, expects an "args"
859             # attribute to be given to the sub-module. As the option
860             # evaluation does not have any specific attribute name yet, we
861             # provide a default for the documentation and the freeform type.
862             #
863             # This is necessary as some option declaration might use the
864             # "name" attribute given as argument of the submodule and use it
865             # as the default of option declarations.
866             #
867             # We use lookalike unicode single angle quotation marks because
868             # of the docbook transformation the options receive. In all uses
869             # &gt; and &lt; wouldn't be encoded correctly so the encoded values
870             # would be used, and use of `<` and `>` would break the XML document.
871             # It shouldn't cause an issue since this is cosmetic for the manual.
872             _module.args.name = lib.mkOptionDefault "‹name›";
873           }] ++ modules;
874         };
876         freeformType = base._module.freeformType;
878         name = "submodule";
880       in
881       mkOptionType {
882         inherit name;
883         description =
884           if description != null then description
885           else freeformType.description or name;
886         check = x: isAttrs x || isFunction x || path.check x;
887         merge = loc: defs:
888           (base.extendModules {
889             modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
890             prefix = loc;
891           }).config;
892         emptyValue = { value = {}; };
893         getSubOptions = prefix: (base.extendModules
894           { inherit prefix; }).options // optionalAttrs (freeformType != null) {
895             # Expose the sub options of the freeform type. Note that the option
896             # discovery doesn't care about the attribute name used here, so this
897             # is just to avoid conflicts with potential options from the submodule
898             _freeformOptions = freeformType.getSubOptions prefix;
899           };
900         getSubModules = modules;
901         substSubModules = m: submoduleWith (attrs // {
902           modules = m;
903         });
904         nestedTypes = lib.optionalAttrs (freeformType != null) {
905           freeformType = freeformType;
906         };
907         functor = defaultFunctor name // {
908           type = types.submoduleWith;
909           payload = {
910             inherit modules class specialArgs shorthandOnlyDefinesConfig description;
911           };
912           binOp = lhs: rhs: {
913             class =
914               # `or null` was added for backwards compatibility only. `class` is
915               # always set in the current version of the module system.
916               if lhs.class or null == null then rhs.class or null
917               else if rhs.class or null == null then lhs.class or null
918               else if lhs.class or null == rhs.class then lhs.class or null
919               else throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\".";
920             modules = lhs.modules ++ rhs.modules;
921             specialArgs =
922               let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
923               in if intersecting == {}
924               then lhs.specialArgs // rhs.specialArgs
925               else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
926             shorthandOnlyDefinesConfig =
927               if lhs.shorthandOnlyDefinesConfig == null
928               then rhs.shorthandOnlyDefinesConfig
929               else if rhs.shorthandOnlyDefinesConfig == null
930               then lhs.shorthandOnlyDefinesConfig
931               else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
932               then lhs.shorthandOnlyDefinesConfig
933               else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
934             description =
935               if lhs.description == null
936               then rhs.description
937               else if rhs.description == null
938               then lhs.description
939               else if lhs.description == rhs.description
940               then lhs.description
941               else throw "A submoduleWith option is declared multiple times with conflicting descriptions";
942           };
943         };
944       };
946     # A value from a set of allowed ones.
947     enum = values:
948       let
949         inherit (lib.lists) unique;
950         show = v:
951                if builtins.isString v then ''"${v}"''
952           else if builtins.isInt v then builtins.toString v
953           else if builtins.isBool v then boolToString v
954           else ''<${builtins.typeOf v}>'';
955       in
956       mkOptionType rec {
957         name = "enum";
958         description =
959           # Length 0 or 1 enums may occur in a design pattern with type merging
960           # where an "interface" module declares an empty enum and other modules
961           # provide implementations, each extending the enum with their own
962           # identifier.
963           if values == [] then
964             "impossible (empty enum)"
965           else if builtins.length values == 1 then
966             "value ${show (builtins.head values)} (singular enum)"
967           else
968             "one of ${concatMapStringsSep ", " show values}";
969         descriptionClass =
970           if builtins.length values < 2
971           then "noun"
972           else "conjunction";
973         check = flip elem values;
974         merge = mergeEqualOption;
975         functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
976       };
978     # Either value of type `t1` or `t2`.
979     either = t1: t2: mkOptionType rec {
980       name = "either";
981       description =
982         if t1.descriptionClass or null == "nonRestrictiveClause"
983         then
984           # Plain, but add comma
985           "${t1.description}, or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2}"
986         else
987           "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction" || class == "composite") t2}";
988       descriptionClass = "conjunction";
989       check = x: t1.check x || t2.check x;
990       merge = loc: defs:
991         let
992           defList = map (d: d.value) defs;
993         in
994           if   all (x: t1.check x) defList
995                then t1.merge loc defs
996           else if all (x: t2.check x) defList
997                then t2.merge loc defs
998           else mergeOneOption loc defs;
999       typeMerge = f':
1000         let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
1001             mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
1002         in
1003            if (name == f'.name) && (mt1 != null) && (mt2 != null)
1004            then functor.type mt1 mt2
1005            else null;
1006       functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
1007       nestedTypes.left = t1;
1008       nestedTypes.right = t2;
1009     };
1011     # Any of the types in the given list
1012     oneOf = ts:
1013       let
1014         head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
1015         tail' = tail ts;
1016       in foldl' either head' tail';
1018     # Either value of type `coercedType` or `finalType`, the former is
1019     # converted to `finalType` using `coerceFunc`.
1020     coercedTo = coercedType: coerceFunc: finalType:
1021       assert lib.assertMsg (coercedType.getSubModules == null)
1022         "coercedTo: coercedType must not have submodules (it’s a ${
1023           coercedType.description})";
1024       mkOptionType rec {
1025         name = "coercedTo";
1026         description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${optionDescriptionPhrase (class: class == "noun") coercedType} convertible to it";
1027         check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
1028         merge = loc: defs:
1029           let
1030             coerceVal = val:
1031               if coercedType.check val then coerceFunc val
1032               else val;
1033           in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
1034         emptyValue = finalType.emptyValue;
1035         getSubOptions = finalType.getSubOptions;
1036         getSubModules = finalType.getSubModules;
1037         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
1038         typeMerge = t1: t2: null;
1039         functor = (defaultFunctor name) // { wrapped = finalType; };
1040         nestedTypes.coercedType = coercedType;
1041         nestedTypes.finalType = finalType;
1042       };
1044     # Augment the given type with an additional type check function.
1045     addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
1047   };
1050 in outer_types // outer_types.types