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