python310Packages.pydeconz: 104 -> 105
[NixPkgs.git] / lib / types.nix
blobb83898744df827b6833fcafc774b70e2453be619
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     isCoercibleToString
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 rec {
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 && isCoercibleToString 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     string = separatedString "" // {
440       name = "string";
441       deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types.";
442     };
444     passwdEntry = entryType: addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) // {
445       name = "passwdEntry ${entryType.name}";
446       description = "${optionDescriptionPhrase (class: class == "noun") entryType}, not containing newlines or colons";
447     };
449     attrs = mkOptionType {
450       name = "attrs";
451       description = "attribute set";
452       check = isAttrs;
453       merge = loc: foldl' (res: def: res // def.value) {};
454       emptyValue = { value = {}; };
455     };
457     # A package is a top-level store path (/nix/store/hash-name). This includes:
458     # - derivations
459     # - more generally, attribute sets with an `outPath` or `__toString` attribute
460     #   pointing to a store path, e.g. flake inputs
461     # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
462     # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
463     #   ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
464     package = mkOptionType {
465       name = "package";
466       descriptionClass = "noun";
467       check = x: isDerivation x || isStorePath x;
468       merge = loc: defs:
469         let res = mergeOneOption loc defs;
470         in if builtins.isPath res || (builtins.isString res && ! builtins.hasContext res)
471           then toDerivation res
472           else res;
473     };
475     shellPackage = package // {
476       check = x: isDerivation x && hasAttr "shellPath" x;
477     };
479     path = mkOptionType {
480       name = "path";
481       check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
482       merge = mergeEqualOption;
483     };
485     listOf = elemType: mkOptionType rec {
486       name = "listOf";
487       description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
488       descriptionClass = "composite";
489       check = isList;
490       merge = loc: defs:
491         map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
492           imap1 (m: def':
493             (mergeDefinitions
494               (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
495               elemType
496               [{ inherit (def) file; value = def'; }]
497             ).optionalValue
498           ) def.value
499         ) defs)));
500       emptyValue = { value = []; };
501       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
502       getSubModules = elemType.getSubModules;
503       substSubModules = m: listOf (elemType.substSubModules m);
504       functor = (defaultFunctor name) // { wrapped = elemType; };
505       nestedTypes.elemType = elemType;
506     };
508     nonEmptyListOf = elemType:
509       let list = addCheck (types.listOf elemType) (l: l != []);
510       in list // {
511         description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
512         emptyValue = { }; # no .value attr, meaning unset
513       };
515     attrsOf = elemType: mkOptionType rec {
516       name = "attrsOf";
517       description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
518       descriptionClass = "composite";
519       check = isAttrs;
520       merge = loc: defs:
521         mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
522             (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
523           )
524           # Push down position info.
525           (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
526       emptyValue = { value = {}; };
527       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
528       getSubModules = elemType.getSubModules;
529       substSubModules = m: attrsOf (elemType.substSubModules m);
530       functor = (defaultFunctor name) // { wrapped = elemType; };
531       nestedTypes.elemType = elemType;
532     };
534     # A version of attrsOf that's lazy in its values at the expense of
535     # conditional definitions not working properly. E.g. defining a value with
536     # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
537     # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
538     # error that it's not defined. Use only if conditional definitions don't make sense.
539     lazyAttrsOf = elemType: mkOptionType rec {
540       name = "lazyAttrsOf";
541       description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
542       descriptionClass = "composite";
543       check = isAttrs;
544       merge = loc: defs:
545         zipAttrsWith (name: defs:
546           let merged = mergeDefinitions (loc ++ [name]) elemType defs;
547           # mergedValue will trigger an appropriate error when accessed
548           in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
549         )
550         # Push down position info.
551         (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
552       emptyValue = { value = {}; };
553       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
554       getSubModules = elemType.getSubModules;
555       substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
556       functor = (defaultFunctor name) // { wrapped = elemType; };
557       nestedTypes.elemType = elemType;
558     };
560     # TODO: drop this in the future:
561     loaOf = elemType: types.attrsOf elemType // {
562       name = "loaOf";
563       deprecationMessage = "Mixing lists with attribute values is no longer"
564         + " possible; please use `types.attrsOf` instead. See"
565         + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
566       nestedTypes.elemType = elemType;
567     };
569     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
570     uniq = elemType: mkOptionType rec {
571       name = "uniq";
572       inherit (elemType) description descriptionClass check;
573       merge = mergeOneOption;
574       emptyValue = elemType.emptyValue;
575       getSubOptions = elemType.getSubOptions;
576       getSubModules = elemType.getSubModules;
577       substSubModules = m: uniq (elemType.substSubModules m);
578       functor = (defaultFunctor name) // { wrapped = elemType; };
579       nestedTypes.elemType = elemType;
580     };
582     unique = { message }: type: mkOptionType rec {
583       name = "unique";
584       inherit (type) description descriptionClass check;
585       merge = mergeUniqueOption { inherit message; };
586       emptyValue = type.emptyValue;
587       getSubOptions = type.getSubOptions;
588       getSubModules = type.getSubModules;
589       substSubModules = m: uniq (type.substSubModules m);
590       functor = (defaultFunctor name) // { wrapped = type; };
591       nestedTypes.elemType = type;
592     };
594     # Null or value of ...
595     nullOr = elemType: mkOptionType rec {
596       name = "nullOr";
597       description = "null or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType}";
598       descriptionClass = "conjunction";
599       check = x: x == null || elemType.check x;
600       merge = loc: defs:
601         let nrNulls = count (def: def.value == null) defs; in
602         if nrNulls == length defs then null
603         else if nrNulls != 0 then
604           throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
605         else elemType.merge loc defs;
606       emptyValue = { value = null; };
607       getSubOptions = elemType.getSubOptions;
608       getSubModules = elemType.getSubModules;
609       substSubModules = m: nullOr (elemType.substSubModules m);
610       functor = (defaultFunctor name) // { wrapped = elemType; };
611       nestedTypes.elemType = elemType;
612     };
614     functionTo = elemType: mkOptionType {
615       name = "functionTo";
616       description = "function that evaluates to a(n) ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
617       descriptionClass = "composite";
618       check = isFunction;
619       merge = loc: defs:
620         fnArgs: (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
621       getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
622       getSubModules = elemType.getSubModules;
623       substSubModules = m: functionTo (elemType.substSubModules m);
624       functor = (defaultFunctor "functionTo") // { wrapped = elemType; };
625       nestedTypes.elemType = elemType;
626     };
628     # A submodule (like typed attribute set). See NixOS manual.
629     submodule = modules: submoduleWith {
630       shorthandOnlyDefinesConfig = true;
631       modules = toList modules;
632     };
634     # A module to be imported in some other part of the configuration.
635     deferredModule = deferredModuleWith { };
637     # A module to be imported in some other part of the configuration.
638     # `staticModules`' options will be added to the documentation, unlike
639     # options declared via `config`.
640     deferredModuleWith = attrs@{ staticModules ? [] }: mkOptionType {
641       name = "deferredModule";
642       description = "module";
643       descriptionClass = "noun";
644       check = x: isAttrs x || isFunction x || path.check x;
645       merge = loc: defs: {
646         imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
647       };
648       inherit (submoduleWith { modules = staticModules; })
649         getSubOptions
650         getSubModules;
651       substSubModules = m: deferredModuleWith (attrs // {
652         staticModules = m;
653       });
654       functor = defaultFunctor "deferredModuleWith" // {
655         type = types.deferredModuleWith;
656         payload = {
657           inherit staticModules;
658         };
659         binOp = lhs: rhs: {
660           staticModules = lhs.staticModules ++ rhs.staticModules;
661         };
662       };
663     };
665     # The type of a type!
666     optionType = mkOptionType {
667       name = "optionType";
668       description = "optionType";
669       descriptionClass = "noun";
670       check = value: value._type or null == "option-type";
671       merge = loc: defs:
672         if length defs == 1
673         then (head defs).value
674         else let
675           # Prepares the type definitions for mergeOptionDecls, which
676           # annotates submodules types with file locations
677           optionModules = map ({ value, file }:
678             {
679               _file = file;
680               # There's no way to merge types directly from the module system,
681               # but we can cheat a bit by just declaring an option with the type
682               options = lib.mkOption {
683                 type = value;
684               };
685             }
686           ) defs;
687           # Merges all the types into a single one, including submodule merging.
688           # This also propagates file information to all submodules
689           mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
690         in mergedOption.type;
691     };
693     submoduleWith =
694       { modules
695       , specialArgs ? {}
696       , shorthandOnlyDefinesConfig ? false
697       , description ? null
698       }@attrs:
699       let
700         inherit (lib.modules) evalModules;
702         allModules = defs: map ({ value, file }:
703           if isAttrs value && shorthandOnlyDefinesConfig
704           then { _file = file; config = value; }
705           else { _file = file; imports = [ value ]; }
706         ) defs;
708         base = evalModules {
709           inherit specialArgs;
710           modules = [{
711             # This is a work-around for the fact that some sub-modules,
712             # such as the one included in an attribute set, expects an "args"
713             # attribute to be given to the sub-module. As the option
714             # evaluation does not have any specific attribute name yet, we
715             # provide a default for the documentation and the freeform type.
716             #
717             # This is necessary as some option declaration might use the
718             # "name" attribute given as argument of the submodule and use it
719             # as the default of option declarations.
720             #
721             # We use lookalike unicode single angle quotation marks because
722             # of the docbook transformation the options receive. In all uses
723             # &gt; and &lt; wouldn't be encoded correctly so the encoded values
724             # would be used, and use of `<` and `>` would break the XML document.
725             # It shouldn't cause an issue since this is cosmetic for the manual.
726             _module.args.name = lib.mkOptionDefault "‹name›";
727           }] ++ modules;
728         };
730         freeformType = base._module.freeformType;
732         name = "submodule";
734       in
735       mkOptionType {
736         inherit name;
737         description =
738           if description != null then description
739           else freeformType.description or name;
740         check = x: isAttrs x || isFunction x || path.check x;
741         merge = loc: defs:
742           (base.extendModules {
743             modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
744             prefix = loc;
745           }).config;
746         emptyValue = { value = {}; };
747         getSubOptions = prefix: (base.extendModules
748           { inherit prefix; }).options // optionalAttrs (freeformType != null) {
749             # Expose the sub options of the freeform type. Note that the option
750             # discovery doesn't care about the attribute name used here, so this
751             # is just to avoid conflicts with potential options from the submodule
752             _freeformOptions = freeformType.getSubOptions prefix;
753           };
754         getSubModules = modules;
755         substSubModules = m: submoduleWith (attrs // {
756           modules = m;
757         });
758         nestedTypes = lib.optionalAttrs (freeformType != null) {
759           freeformType = freeformType;
760         };
761         functor = defaultFunctor name // {
762           type = types.submoduleWith;
763           payload = {
764             inherit modules specialArgs shorthandOnlyDefinesConfig description;
765           };
766           binOp = lhs: rhs: {
767             modules = lhs.modules ++ rhs.modules;
768             specialArgs =
769               let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
770               in if intersecting == {}
771               then lhs.specialArgs // rhs.specialArgs
772               else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
773             shorthandOnlyDefinesConfig =
774               if lhs.shorthandOnlyDefinesConfig == null
775               then rhs.shorthandOnlyDefinesConfig
776               else if rhs.shorthandOnlyDefinesConfig == null
777               then lhs.shorthandOnlyDefinesConfig
778               else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
779               then lhs.shorthandOnlyDefinesConfig
780               else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
781             description =
782               if lhs.description == null
783               then rhs.description
784               else if rhs.description == null
785               then lhs.description
786               else if lhs.description == rhs.description
787               then lhs.description
788               else throw "A submoduleWith option is declared multiple times with conflicting descriptions";
789           };
790         };
791       };
793     # A value from a set of allowed ones.
794     enum = values:
795       let
796         inherit (lib.lists) unique;
797         show = v:
798                if builtins.isString v then ''"${v}"''
799           else if builtins.isInt v then builtins.toString v
800           else if builtins.isBool v then boolToString v
801           else ''<${builtins.typeOf v}>'';
802       in
803       mkOptionType rec {
804         name = "enum";
805         description =
806           # Length 0 or 1 enums may occur in a design pattern with type merging
807           # where an "interface" module declares an empty enum and other modules
808           # provide implementations, each extending the enum with their own
809           # identifier.
810           if values == [] then
811             "impossible (empty enum)"
812           else if builtins.length values == 1 then
813             "value ${show (builtins.head values)} (singular enum)"
814           else
815             "one of ${concatMapStringsSep ", " show values}";
816         descriptionClass =
817           if builtins.length values < 2
818           then "noun"
819           else "conjunction";
820         check = flip elem values;
821         merge = mergeEqualOption;
822         functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
823       };
825     # Either value of type `t1` or `t2`.
826     either = t1: t2: mkOptionType rec {
827       name = "either";
828       description = "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction" || class == "composite") t2}";
829       descriptionClass = "conjunction";
830       check = x: t1.check x || t2.check x;
831       merge = loc: defs:
832         let
833           defList = map (d: d.value) defs;
834         in
835           if   all (x: t1.check x) defList
836                then t1.merge loc defs
837           else if all (x: t2.check x) defList
838                then t2.merge loc defs
839           else mergeOneOption loc defs;
840       typeMerge = f':
841         let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
842             mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
843         in
844            if (name == f'.name) && (mt1 != null) && (mt2 != null)
845            then functor.type mt1 mt2
846            else null;
847       functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
848       nestedTypes.left = t1;
849       nestedTypes.right = t2;
850     };
852     # Any of the types in the given list
853     oneOf = ts:
854       let
855         head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
856         tail' = tail ts;
857       in foldl' either head' tail';
859     # Either value of type `coercedType` or `finalType`, the former is
860     # converted to `finalType` using `coerceFunc`.
861     coercedTo = coercedType: coerceFunc: finalType:
862       assert lib.assertMsg (coercedType.getSubModules == null)
863         "coercedTo: coercedType must not have submodules (it’s a ${
864           coercedType.description})";
865       mkOptionType rec {
866         name = "coercedTo";
867         description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${optionDescriptionPhrase (class: class == "noun") coercedType} convertible to it";
868         check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
869         merge = loc: defs:
870           let
871             coerceVal = val:
872               if coercedType.check val then coerceFunc val
873               else val;
874           in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
875         emptyValue = finalType.emptyValue;
876         getSubOptions = finalType.getSubOptions;
877         getSubModules = finalType.getSubModules;
878         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
879         typeMerge = t1: t2: null;
880         functor = (defaultFunctor name) // { wrapped = finalType; };
881         nestedTypes.coercedType = coercedType;
882         nestedTypes.finalType = finalType;
883       };
885     # Augment the given type with an additional type check function.
886     addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
888   };
891 in outer_types // outer_types.types