linux_xanmod: 5.11.14 -> 5.11.15
[NixPkgs.git] / lib / types.nix
blobd0a8e96149d70455ee1bf3f6cb992a448f9c7f34
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     functionArgs
10     isAttrs
11     isBool
12     isDerivation
13     isFloat
14     isFunction
15     isInt
16     isList
17     isString
18     isStorePath
19     setFunctionArgs
20     toDerivation
21     toList
22     ;
23   inherit (lib.lists)
24     all
25     concatLists
26     count
27     elemAt
28     filter
29     foldl'
30     head
31     imap1
32     last
33     length
34     tail
35     unique
36     ;
37   inherit (lib.attrsets)
38     attrNames
39     filterAttrs
40     hasAttr
41     mapAttrs
42     optionalAttrs
43     zipAttrsWith
44     ;
45   inherit (lib.options)
46     getFiles
47     getValues
48     mergeDefaultOption
49     mergeEqualOption
50     mergeOneOption
51     showFiles
52     showOption
53     ;
54   inherit (lib.strings)
55     concatMapStringsSep
56     concatStringsSep
57     escapeNixString
58     isCoercibleToString
59     ;
60   inherit (lib.trivial)
61     boolToString
62     ;
64   inherit (lib.modules) mergeDefinitions;
65   outer_types =
66 rec {
67   isType = type: x: (x._type or "") == type;
69   setType = typeName: value: value // {
70     _type = typeName;
71   };
74   # Default type merging function
75   # takes two type functors and return the merged type
76   defaultTypeMerge = f: f':
77     let wrapped = f.wrapped.typeMerge f'.wrapped.functor;
78         payload = f.binOp f.payload f'.payload;
79     in
80     # cannot merge different types
81     if f.name != f'.name
82        then null
83     # simple types
84     else if    (f.wrapped == null && f'.wrapped == null)
85             && (f.payload == null && f'.payload == null)
86        then f.type
87     # composed types
88     else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
89        then f.type wrapped
90     # value types
91     else if (f.payload != null && f'.payload != null) && (payload != null)
92        then f.type payload
93     else null;
95   # Default type functor
96   defaultFunctor = name: {
97     inherit name;
98     type    = types.${name} or null;
99     wrapped = null;
100     payload = null;
101     binOp   = a: b: null;
102   };
104   isOptionType = isType "option-type";
105   mkOptionType =
106     { # Human-readable representation of the type, should be equivalent to
107       # the type function name.
108       name
109     , # Description of the type, defined recursively by embedding the wrapped type if any.
110       description ? null
111     , # Function applied to each definition that should return true if
112       # its type-correct, false otherwise.
113       check ? (x: true)
114     , # Merge a list of definitions together into a single value.
115       # This function is called with two arguments: the location of
116       # the option in the configuration as a list of strings
117       # (e.g. ["boot" "loader "grub" "enable"]), and a list of
118       # definition values and locations (e.g. [ { file = "/foo.nix";
119       # value = 1; } { file = "/bar.nix"; value = 2 } ]).
120       merge ? mergeDefaultOption
121     , # Whether this type has a value representing nothingness. If it does,
122       # this should be a value of the form { value = <the nothing value>; }
123       # If it doesn't, this should be {}
124       # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
125       emptyValue ? {}
126     , # Return a flat list of sub-options.  Used to generate
127       # documentation.
128       getSubOptions ? prefix: {}
129     , # List of modules if any, or null if none.
130       getSubModules ? null
131     , # Function for building the same option type with a different list of
132       # modules.
133       substSubModules ? m: null
134     , # Function that merge type declarations.
135       # internal, takes a functor as argument and returns the merged type.
136       # returning null means the type is not mergeable
137       typeMerge ? defaultTypeMerge functor
138     , # The type functor.
139       # internal, representation of the type as an attribute set.
140       #   name: name of the type
141       #   type: type function.
142       #   wrapped: the type wrapped in case of compound types.
143       #   payload: values of the type, two payloads of the same type must be
144       #            combinable with the binOp binary operation.
145       #   binOp: binary operation that merge two payloads of the same type.
146       functor ? defaultFunctor name
147     , # The deprecation message to display when this type is used by an option
148       # If null, the type isn't deprecated
149       deprecationMessage ? null
150     }:
151     { _type = "option-type";
152       inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage;
153       description = if description == null then name else description;
154     };
157   # When adding new types don't forget to document them in
158   # nixos/doc/manual/development/option-types.xml!
159   types = rec {
161     anything = mkOptionType {
162       name = "anything";
163       description = "anything";
164       check = value: true;
165       merge = loc: defs:
166         let
167           getType = value:
168             if isAttrs value && isCoercibleToString value
169             then "stringCoercibleSet"
170             else builtins.typeOf value;
172           # Returns the common type of all definitions, throws an error if they
173           # don't have the same type
174           commonType = foldl' (type: def:
175             if getType def.value == type
176             then type
177             else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
178           ) (getType (head defs).value) defs;
180           mergeFunction = {
181             # Recursively merge attribute sets
182             set = (attrsOf anything).merge;
183             # Safe and deterministic behavior for lists is to only accept one definition
184             # listOf only used to apply mkIf and co.
185             list =
186               if length defs > 1
187               then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
188               else (listOf anything).merge;
189             # This is the type of packages, only accept a single definition
190             stringCoercibleSet = mergeOneOption;
191             # Otherwise fall back to only allowing all equal definitions
192           }.${commonType} or mergeEqualOption;
193         in mergeFunction loc defs;
194     };
196     unspecified = mkOptionType {
197       name = "unspecified";
198     };
200     bool = mkOptionType {
201       name = "bool";
202       description = "boolean";
203       check = isBool;
204       merge = mergeEqualOption;
205     };
207     int = mkOptionType {
208         name = "int";
209         description = "signed integer";
210         check = isInt;
211         merge = mergeEqualOption;
212       };
214     # Specialized subdomains of int
215     ints =
216       let
217         betweenDesc = lowest: highest:
218           "${toString lowest} and ${toString highest} (both inclusive)";
219         between = lowest: highest:
220           assert lib.assertMsg (lowest <= highest)
221             "ints.between: lowest must be smaller than highest";
222           addCheck int (x: x >= lowest && x <= highest) // {
223             name = "intBetween";
224             description = "integer between ${betweenDesc lowest highest}";
225           };
226         ign = lowest: highest: name: docStart:
227           between lowest highest // {
228             inherit name;
229             description = docStart + "; between ${betweenDesc lowest highest}";
230           };
231         unsign = bit: range: ign 0 (range - 1)
232           "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
233         sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1)
234           "signedInt${toString bit}" "${toString bit} bit signed integer";
236       in {
237         /* An int with a fixed range.
238         *
239         * Example:
240         *   (ints.between 0 100).check (-1)
241         *   => false
242         *   (ints.between 0 100).check (101)
243         *   => false
244         *   (ints.between 0 0).check 0
245         *   => true
246         */
247         inherit between;
249         unsigned = addCheck types.int (x: x >= 0) // {
250           name = "unsignedInt";
251           description = "unsigned integer, meaning >=0";
252         };
253         positive = addCheck types.int (x: x > 0) // {
254           name = "positiveInt";
255           description = "positive integer, meaning >0";
256         };
257         u8 = unsign 8 256;
258         u16 = unsign 16 65536;
259         # the biggest int a 64-bit Nix accepts is 2^63 - 1 (9223372036854775808), for a 32-bit Nix it is 2^31 - 1 (2147483647)
260         # the smallest int a 64-bit Nix accepts is -2^63 (-9223372036854775807), for a 32-bit Nix it is -2^31 (-2147483648)
261         # u32 = unsign 32 4294967296;
262         # u64 = unsign 64 18446744073709551616;
264         s8 = sign 8 256;
265         s16 = sign 16 65536;
266         # s32 = sign 32 4294967296;
267       };
269     # Alias of u16 for a port number
270     port = ints.u16;
272     float = mkOptionType {
273         name = "float";
274         description = "floating point number";
275         check = isFloat;
276         merge = mergeEqualOption;
277     };
279     str = mkOptionType {
280       name = "str";
281       description = "string";
282       check = isString;
283       merge = mergeEqualOption;
284     };
286     strMatching = pattern: mkOptionType {
287       name = "strMatching ${escapeNixString pattern}";
288       description = "string matching the pattern ${pattern}";
289       check = x: str.check x && builtins.match pattern x != null;
290       inherit (str) merge;
291     };
293     # Merge multiple definitions by concatenating them (with the given
294     # separator between the values).
295     separatedString = sep: mkOptionType rec {
296       name = "separatedString";
297       description = if sep == ""
298         then "Concatenated string" # for types.string.
299         else "strings concatenated with ${builtins.toJSON sep}"
300       ;
301       check = isString;
302       merge = loc: defs: concatStringsSep sep (getValues defs);
303       functor = (defaultFunctor name) // {
304         payload = sep;
305         binOp = sepLhs: sepRhs:
306           if sepLhs == sepRhs then sepLhs
307           else null;
308       };
309     };
311     lines = separatedString "\n";
312     commas = separatedString ",";
313     envVar = separatedString ":";
315     # Deprecated; should not be used because it quietly concatenates
316     # strings, which is usually not what you want.
317     string = separatedString "" // {
318       name = "string";
319       deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types.";
320     };
322     attrs = mkOptionType {
323       name = "attrs";
324       description = "attribute set";
325       check = isAttrs;
326       merge = loc: foldl' (res: def: res // def.value) {};
327       emptyValue = { value = {}; };
328     };
330     # derivation is a reserved keyword.
331     package = mkOptionType {
332       name = "package";
333       check = x: isDerivation x || isStorePath x;
334       merge = loc: defs:
335         let res = mergeOneOption loc defs;
336         in if isDerivation res then res else toDerivation res;
337     };
339     shellPackage = package // {
340       check = x: (package.check x) && (hasAttr "shellPath" x);
341     };
343     path = mkOptionType {
344       name = "path";
345       check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
346       merge = mergeEqualOption;
347     };
349     listOf = elemType: mkOptionType rec {
350       name = "listOf";
351       description = "list of ${elemType.description}s";
352       check = isList;
353       merge = loc: defs:
354         map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
355           imap1 (m: def':
356             (mergeDefinitions
357               (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
358               elemType
359               [{ inherit (def) file; value = def'; }]
360             ).optionalValue
361           ) def.value
362         ) defs)));
363       emptyValue = { value = {}; };
364       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
365       getSubModules = elemType.getSubModules;
366       substSubModules = m: listOf (elemType.substSubModules m);
367       functor = (defaultFunctor name) // { wrapped = elemType; };
368     };
370     nonEmptyListOf = elemType:
371       let list = addCheck (types.listOf elemType) (l: l != []);
372       in list // {
373         description = "non-empty " + list.description;
374         # Note: emptyValue is left as is, because another module may define an element.
375       };
377     attrsOf = elemType: mkOptionType rec {
378       name = "attrsOf";
379       description = "attribute set of ${elemType.description}s";
380       check = isAttrs;
381       merge = loc: defs:
382         mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
383             (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
384           )
385           # Push down position info.
386           (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
387       emptyValue = { value = {}; };
388       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
389       getSubModules = elemType.getSubModules;
390       substSubModules = m: attrsOf (elemType.substSubModules m);
391       functor = (defaultFunctor name) // { wrapped = elemType; };
392     };
394     # A version of attrsOf that's lazy in its values at the expense of
395     # conditional definitions not working properly. E.g. defining a value with
396     # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
397     # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
398     # error that it's not defined. Use only if conditional definitions don't make sense.
399     lazyAttrsOf = elemType: mkOptionType rec {
400       name = "lazyAttrsOf";
401       description = "lazy attribute set of ${elemType.description}s";
402       check = isAttrs;
403       merge = loc: defs:
404         zipAttrsWith (name: defs:
405           let merged = mergeDefinitions (loc ++ [name]) elemType defs;
406           # mergedValue will trigger an appropriate error when accessed
407           in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
408         )
409         # Push down position info.
410         (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
411       emptyValue = { value = {}; };
412       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
413       getSubModules = elemType.getSubModules;
414       substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
415       functor = (defaultFunctor name) // { wrapped = elemType; };
416     };
418     # TODO: drop this in the future:
419     loaOf = elemType: types.attrsOf elemType // {
420       name = "loaOf";
421       deprecationMessage = "Mixing lists with attribute values is no longer"
422         + " possible; please use `types.attrsOf` instead. See"
423         + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
424     };
426     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
427     uniq = elemType: mkOptionType rec {
428       name = "uniq";
429       inherit (elemType) description check;
430       merge = mergeOneOption;
431       emptyValue = elemType.emptyValue;
432       getSubOptions = elemType.getSubOptions;
433       getSubModules = elemType.getSubModules;
434       substSubModules = m: uniq (elemType.substSubModules m);
435       functor = (defaultFunctor name) // { wrapped = elemType; };
436     };
438     # Null or value of ...
439     nullOr = elemType: mkOptionType rec {
440       name = "nullOr";
441       description = "null or ${elemType.description}";
442       check = x: x == null || elemType.check x;
443       merge = loc: defs:
444         let nrNulls = count (def: def.value == null) defs; in
445         if nrNulls == length defs then null
446         else if nrNulls != 0 then
447           throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
448         else elemType.merge loc defs;
449       emptyValue = { value = null; };
450       getSubOptions = elemType.getSubOptions;
451       getSubModules = elemType.getSubModules;
452       substSubModules = m: nullOr (elemType.substSubModules m);
453       functor = (defaultFunctor name) // { wrapped = elemType; };
454     };
456     functionTo = elemType: mkOptionType {
457       name = "functionTo";
458       description = "function that evaluates to a(n) ${elemType.name}";
459       check = isFunction;
460       merge = loc: defs:
461         fnArgs: (mergeDefinitions (loc ++ [ "[function body]" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
462       getSubOptions = elemType.getSubOptions;
463       getSubModules = elemType.getSubModules;
464       substSubModules = m: functionTo (elemType.substSubModules m);
465     };
467     # A submodule (like typed attribute set). See NixOS manual.
468     submodule = modules: submoduleWith {
469       shorthandOnlyDefinesConfig = true;
470       modules = toList modules;
471     };
473     submoduleWith =
474       { modules
475       , specialArgs ? {}
476       , shorthandOnlyDefinesConfig ? false
477       }@attrs:
478       let
479         inherit (lib.modules) evalModules;
481         coerce = unify: value: if isFunction value
482           then setFunctionArgs (args: unify (value args)) (functionArgs value)
483           else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
485         allModules = defs: modules ++ imap1 (n: { value, file }:
486           if isAttrs value || isFunction value then
487             # Annotate the value with the location of its definition for better error messages
488             coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
489           else value
490         ) defs;
492         freeformType = (evalModules {
493           inherit modules specialArgs;
494           args.name = "‹name›";
495         })._module.freeformType;
497       in
498       mkOptionType rec {
499         name = "submodule";
500         description = freeformType.description or name;
501         check = x: isAttrs x || isFunction x || path.check x;
502         merge = loc: defs:
503           (evalModules {
504             modules = allModules defs;
505             inherit specialArgs;
506             args.name = last loc;
507             prefix = loc;
508           }).config;
509         emptyValue = { value = {}; };
510         getSubOptions = prefix: (evalModules
511           { inherit modules prefix specialArgs;
512             # This is a work-around due to the fact that some sub-modules,
513             # such as the one included in an attribute set, expects a "args"
514             # attribute to be given to the sub-module. As the option
515             # evaluation does not have any specific attribute name, we
516             # provide a default one for the documentation.
517             #
518             # This is mandatory as some option declaration might use the
519             # "name" attribute given as argument of the submodule and use it
520             # as the default of option declarations.
521             #
522             # Using lookalike unicode single angle quotation marks because
523             # of the docbook transformation the options receive. In all uses
524             # &gt; and &lt; wouldn't be encoded correctly so the encoded values
525             # would be used, and use of `<` and `>` would break the XML document.
526             # It shouldn't cause an issue since this is cosmetic for the manual.
527             args.name = "‹name›";
528           }).options // optionalAttrs (freeformType != null) {
529             # Expose the sub options of the freeform type. Note that the option
530             # discovery doesn't care about the attribute name used here, so this
531             # is just to avoid conflicts with potential options from the submodule
532             _freeformOptions = freeformType.getSubOptions prefix;
533           };
534         getSubModules = modules;
535         substSubModules = m: submoduleWith (attrs // {
536           modules = m;
537         });
538         functor = defaultFunctor name // {
539           type = types.submoduleWith;
540           payload = {
541             modules = modules;
542             specialArgs = specialArgs;
543             shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig;
544           };
545           binOp = lhs: rhs: {
546             modules = lhs.modules ++ rhs.modules;
547             specialArgs =
548               let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
549               in if intersecting == {}
550               then lhs.specialArgs // rhs.specialArgs
551               else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
552             shorthandOnlyDefinesConfig =
553               if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
554               then lhs.shorthandOnlyDefinesConfig
555               else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
556           };
557         };
558       };
560     # A value from a set of allowed ones.
561     enum = values:
562       let
563         show = v:
564                if builtins.isString v then ''"${v}"''
565           else if builtins.isInt v then builtins.toString v
566           else if builtins.isBool v then boolToString v
567           else ''<${builtins.typeOf v}>'';
568       in
569       mkOptionType rec {
570         name = "enum";
571         description = "one of ${concatMapStringsSep ", " show values}";
572         check = flip elem values;
573         merge = mergeEqualOption;
574         functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
575       };
577     # Either value of type `t1` or `t2`.
578     either = t1: t2: mkOptionType rec {
579       name = "either";
580       description = "${t1.description} or ${t2.description}";
581       check = x: t1.check x || t2.check x;
582       merge = loc: defs:
583         let
584           defList = map (d: d.value) defs;
585         in
586           if   all (x: t1.check x) defList
587                then t1.merge loc defs
588           else if all (x: t2.check x) defList
589                then t2.merge loc defs
590           else mergeOneOption loc defs;
591       typeMerge = f':
592         let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
593             mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
594         in
595            if (name == f'.name) && (mt1 != null) && (mt2 != null)
596            then functor.type mt1 mt2
597            else null;
598       functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
599     };
601     # Any of the types in the given list
602     oneOf = ts:
603       let
604         head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
605         tail' = tail ts;
606       in foldl' either head' tail';
608     # Either value of type `coercedType` or `finalType`, the former is
609     # converted to `finalType` using `coerceFunc`.
610     coercedTo = coercedType: coerceFunc: finalType:
611       assert lib.assertMsg (coercedType.getSubModules == null)
612         "coercedTo: coercedType must not have submodules (it’s a ${
613           coercedType.description})";
614       mkOptionType rec {
615         name = "coercedTo";
616         description = "${finalType.description} or ${coercedType.description} convertible to it";
617         check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
618         merge = loc: defs:
619           let
620             coerceVal = val:
621               if coercedType.check val then coerceFunc val
622               else val;
623           in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
624         emptyValue = finalType.emptyValue;
625         getSubOptions = finalType.getSubOptions;
626         getSubModules = finalType.getSubModules;
627         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
628         typeMerge = t1: t2: null;
629         functor = (defaultFunctor name) // { wrapped = finalType; };
630       };
632     # Obsolete alternative to configOf.  It takes its option
633     # declarations from the ‘options’ attribute of containing option
634     # declaration.
635     optionSet = mkOptionType {
636       name = "optionSet";
637       description = "option set";
638       deprecationMessage = "Use `types.submodule' instead";
639     };
640     # Augment the given type with an additional type check function.
641     addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
643   };
646 in outer_types // outer_types.types