Merge pull request #136474 from waldheinz/etc-file-source-to-store
[NixPkgs.git] / lib / types.nix
bloba0be2ff3a452a6e4600dd5c10c8cadd8ae503ab5
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     , # The types that occur in the definition of this type. This is used to
151       # issue deprecation warnings recursively. Can also be used to reuse
152       # nested types
153       nestedTypes ? {}
154     }:
155     { _type = "option-type";
156       inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage nestedTypes;
157       description = if description == null then name else description;
158     };
161   # When adding new types don't forget to document them in
162   # nixos/doc/manual/development/option-types.xml!
163   types = rec {
165     anything = mkOptionType {
166       name = "anything";
167       description = "anything";
168       check = value: true;
169       merge = loc: defs:
170         let
171           getType = value:
172             if isAttrs value && isCoercibleToString value
173             then "stringCoercibleSet"
174             else builtins.typeOf value;
176           # Returns the common type of all definitions, throws an error if they
177           # don't have the same type
178           commonType = foldl' (type: def:
179             if getType def.value == type
180             then type
181             else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
182           ) (getType (head defs).value) defs;
184           mergeFunction = {
185             # Recursively merge attribute sets
186             set = (attrsOf anything).merge;
187             # Safe and deterministic behavior for lists is to only accept one definition
188             # listOf only used to apply mkIf and co.
189             list =
190               if length defs > 1
191               then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
192               else (listOf anything).merge;
193             # This is the type of packages, only accept a single definition
194             stringCoercibleSet = mergeOneOption;
195             # Otherwise fall back to only allowing all equal definitions
196           }.${commonType} or mergeEqualOption;
197         in mergeFunction loc defs;
198     };
200     unspecified = mkOptionType {
201       name = "unspecified";
202     };
204     bool = mkOptionType {
205       name = "bool";
206       description = "boolean";
207       check = isBool;
208       merge = mergeEqualOption;
209     };
211     int = mkOptionType {
212         name = "int";
213         description = "signed integer";
214         check = isInt;
215         merge = mergeEqualOption;
216       };
218     # Specialized subdomains of int
219     ints =
220       let
221         betweenDesc = lowest: highest:
222           "${toString lowest} and ${toString highest} (both inclusive)";
223         between = lowest: highest:
224           assert lib.assertMsg (lowest <= highest)
225             "ints.between: lowest must be smaller than highest";
226           addCheck int (x: x >= lowest && x <= highest) // {
227             name = "intBetween";
228             description = "integer between ${betweenDesc lowest highest}";
229           };
230         ign = lowest: highest: name: docStart:
231           between lowest highest // {
232             inherit name;
233             description = docStart + "; between ${betweenDesc lowest highest}";
234           };
235         unsign = bit: range: ign 0 (range - 1)
236           "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
237         sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1)
238           "signedInt${toString bit}" "${toString bit} bit signed integer";
240       in {
241         /* An int with a fixed range.
242         *
243         * Example:
244         *   (ints.between 0 100).check (-1)
245         *   => false
246         *   (ints.between 0 100).check (101)
247         *   => false
248         *   (ints.between 0 0).check 0
249         *   => true
250         */
251         inherit between;
253         unsigned = addCheck types.int (x: x >= 0) // {
254           name = "unsignedInt";
255           description = "unsigned integer, meaning >=0";
256         };
257         positive = addCheck types.int (x: x > 0) // {
258           name = "positiveInt";
259           description = "positive integer, meaning >0";
260         };
261         u8 = unsign 8 256;
262         u16 = unsign 16 65536;
263         # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
264         # the smallest int Nix accepts is -2^63 (-9223372036854775807)
265         u32 = unsign 32 4294967296;
266         # u64 = unsign 64 18446744073709551616;
268         s8 = sign 8 256;
269         s16 = sign 16 65536;
270         s32 = sign 32 4294967296;
271       };
273     # Alias of u16 for a port number
274     port = ints.u16;
276     float = mkOptionType {
277         name = "float";
278         description = "floating point number";
279         check = isFloat;
280         merge = mergeEqualOption;
281     };
283     str = mkOptionType {
284       name = "str";
285       description = "string";
286       check = isString;
287       merge = mergeEqualOption;
288     };
290     nonEmptyStr = mkOptionType {
291       name = "nonEmptyStr";
292       description = "non-empty string";
293       check = x: str.check x && builtins.match "[ \t\n]*" x == null;
294       inherit (str) merge;
295     };
297     strMatching = pattern: mkOptionType {
298       name = "strMatching ${escapeNixString pattern}";
299       description = "string matching the pattern ${pattern}";
300       check = x: str.check x && builtins.match pattern x != null;
301       inherit (str) merge;
302     };
304     # Merge multiple definitions by concatenating them (with the given
305     # separator between the values).
306     separatedString = sep: mkOptionType rec {
307       name = "separatedString";
308       description = if sep == ""
309         then "Concatenated string" # for types.string.
310         else "strings concatenated with ${builtins.toJSON sep}"
311       ;
312       check = isString;
313       merge = loc: defs: concatStringsSep sep (getValues defs);
314       functor = (defaultFunctor name) // {
315         payload = sep;
316         binOp = sepLhs: sepRhs:
317           if sepLhs == sepRhs then sepLhs
318           else null;
319       };
320     };
322     lines = separatedString "\n";
323     commas = separatedString ",";
324     envVar = separatedString ":";
326     # Deprecated; should not be used because it quietly concatenates
327     # strings, which is usually not what you want.
328     string = separatedString "" // {
329       name = "string";
330       deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types.";
331     };
333     attrs = mkOptionType {
334       name = "attrs";
335       description = "attribute set";
336       check = isAttrs;
337       merge = loc: foldl' (res: def: res // def.value) {};
338       emptyValue = { value = {}; };
339     };
341     # derivation is a reserved keyword.
342     package = mkOptionType {
343       name = "package";
344       check = x: isDerivation x || isStorePath x;
345       merge = loc: defs:
346         let res = mergeOneOption loc defs;
347         in if isDerivation res then res else toDerivation res;
348     };
350     shellPackage = package // {
351       check = x: isDerivation x && hasAttr "shellPath" x;
352     };
354     path = mkOptionType {
355       name = "path";
356       check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
357       merge = mergeEqualOption;
358     };
360     listOf = elemType: mkOptionType rec {
361       name = "listOf";
362       description = "list of ${elemType.description}s";
363       check = isList;
364       merge = loc: defs:
365         map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
366           imap1 (m: def':
367             (mergeDefinitions
368               (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
369               elemType
370               [{ inherit (def) file; value = def'; }]
371             ).optionalValue
372           ) def.value
373         ) defs)));
374       emptyValue = { value = {}; };
375       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
376       getSubModules = elemType.getSubModules;
377       substSubModules = m: listOf (elemType.substSubModules m);
378       functor = (defaultFunctor name) // { wrapped = elemType; };
379       nestedTypes.elemType = elemType;
380     };
382     nonEmptyListOf = elemType:
383       let list = addCheck (types.listOf elemType) (l: l != []);
384       in list // {
385         description = "non-empty " + list.description;
386         # Note: emptyValue is left as is, because another module may define an element.
387       };
389     attrsOf = elemType: mkOptionType rec {
390       name = "attrsOf";
391       description = "attribute set of ${elemType.description}s";
392       check = isAttrs;
393       merge = loc: defs:
394         mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
395             (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
396           )
397           # Push down position info.
398           (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
399       emptyValue = { value = {}; };
400       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
401       getSubModules = elemType.getSubModules;
402       substSubModules = m: attrsOf (elemType.substSubModules m);
403       functor = (defaultFunctor name) // { wrapped = elemType; };
404       nestedTypes.elemType = elemType;
405     };
407     # A version of attrsOf that's lazy in its values at the expense of
408     # conditional definitions not working properly. E.g. defining a value with
409     # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
410     # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
411     # error that it's not defined. Use only if conditional definitions don't make sense.
412     lazyAttrsOf = elemType: mkOptionType rec {
413       name = "lazyAttrsOf";
414       description = "lazy attribute set of ${elemType.description}s";
415       check = isAttrs;
416       merge = loc: defs:
417         zipAttrsWith (name: defs:
418           let merged = mergeDefinitions (loc ++ [name]) elemType defs;
419           # mergedValue will trigger an appropriate error when accessed
420           in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
421         )
422         # Push down position info.
423         (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
424       emptyValue = { value = {}; };
425       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
426       getSubModules = elemType.getSubModules;
427       substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
428       functor = (defaultFunctor name) // { wrapped = elemType; };
429       nestedTypes.elemType = elemType;
430     };
432     # TODO: drop this in the future:
433     loaOf = elemType: types.attrsOf elemType // {
434       name = "loaOf";
435       deprecationMessage = "Mixing lists with attribute values is no longer"
436         + " possible; please use `types.attrsOf` instead. See"
437         + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
438       nestedTypes.elemType = elemType;
439     };
441     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
442     uniq = elemType: mkOptionType rec {
443       name = "uniq";
444       inherit (elemType) description check;
445       merge = mergeOneOption;
446       emptyValue = elemType.emptyValue;
447       getSubOptions = elemType.getSubOptions;
448       getSubModules = elemType.getSubModules;
449       substSubModules = m: uniq (elemType.substSubModules m);
450       functor = (defaultFunctor name) // { wrapped = elemType; };
451       nestedTypes.elemType = elemType;
452     };
454     # Null or value of ...
455     nullOr = elemType: mkOptionType rec {
456       name = "nullOr";
457       description = "null or ${elemType.description}";
458       check = x: x == null || elemType.check x;
459       merge = loc: defs:
460         let nrNulls = count (def: def.value == null) defs; in
461         if nrNulls == length defs then null
462         else if nrNulls != 0 then
463           throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
464         else elemType.merge loc defs;
465       emptyValue = { value = null; };
466       getSubOptions = elemType.getSubOptions;
467       getSubModules = elemType.getSubModules;
468       substSubModules = m: nullOr (elemType.substSubModules m);
469       functor = (defaultFunctor name) // { wrapped = elemType; };
470       nestedTypes.elemType = elemType;
471     };
473     functionTo = elemType: mkOptionType {
474       name = "functionTo";
475       description = "function that evaluates to a(n) ${elemType.name}";
476       check = isFunction;
477       merge = loc: defs:
478         fnArgs: (mergeDefinitions (loc ++ [ "[function body]" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
479       getSubOptions = elemType.getSubOptions;
480       getSubModules = elemType.getSubModules;
481       substSubModules = m: functionTo (elemType.substSubModules m);
482     };
484     # A submodule (like typed attribute set). See NixOS manual.
485     submodule = modules: submoduleWith {
486       shorthandOnlyDefinesConfig = true;
487       modules = toList modules;
488     };
490     submoduleWith =
491       { modules
492       , specialArgs ? {}
493       , shorthandOnlyDefinesConfig ? false
494       }@attrs:
495       let
496         inherit (lib.modules) evalModules;
498         coerce = unify: value: if isFunction value
499           then setFunctionArgs (args: unify (value args)) (functionArgs value)
500           else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
502         allModules = defs: modules ++ imap1 (n: { value, file }:
503           if isAttrs value || isFunction value then
504             # Annotate the value with the location of its definition for better error messages
505             coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
506           else value
507         ) defs;
509         freeformType = (evalModules {
510           inherit modules specialArgs;
511           args.name = "‹name›";
512         })._module.freeformType;
514       in
515       mkOptionType rec {
516         name = "submodule";
517         description = freeformType.description or name;
518         check = x: isAttrs x || isFunction x || path.check x;
519         merge = loc: defs:
520           (evalModules {
521             modules = allModules defs;
522             inherit specialArgs;
523             args.name = last loc;
524             prefix = loc;
525           }).config;
526         emptyValue = { value = {}; };
527         getSubOptions = prefix: (evalModules
528           { inherit modules prefix specialArgs;
529             # This is a work-around due to the fact that some sub-modules,
530             # such as the one included in an attribute set, expects a "args"
531             # attribute to be given to the sub-module. As the option
532             # evaluation does not have any specific attribute name, we
533             # provide a default one for the documentation.
534             #
535             # This is mandatory as some option declaration might use the
536             # "name" attribute given as argument of the submodule and use it
537             # as the default of option declarations.
538             #
539             # Using lookalike unicode single angle quotation marks because
540             # of the docbook transformation the options receive. In all uses
541             # &gt; and &lt; wouldn't be encoded correctly so the encoded values
542             # would be used, and use of `<` and `>` would break the XML document.
543             # It shouldn't cause an issue since this is cosmetic for the manual.
544             args.name = "‹name›";
545           }).options // optionalAttrs (freeformType != null) {
546             # Expose the sub options of the freeform type. Note that the option
547             # discovery doesn't care about the attribute name used here, so this
548             # is just to avoid conflicts with potential options from the submodule
549             _freeformOptions = freeformType.getSubOptions prefix;
550           };
551         getSubModules = modules;
552         substSubModules = m: submoduleWith (attrs // {
553           modules = m;
554         });
555         nestedTypes = lib.optionalAttrs (freeformType != null) {
556           freeformType = freeformType;
557         };
558         functor = defaultFunctor name // {
559           type = types.submoduleWith;
560           payload = {
561             modules = modules;
562             specialArgs = specialArgs;
563             shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig;
564           };
565           binOp = lhs: rhs: {
566             modules = lhs.modules ++ rhs.modules;
567             specialArgs =
568               let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
569               in if intersecting == {}
570               then lhs.specialArgs // rhs.specialArgs
571               else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
572             shorthandOnlyDefinesConfig =
573               if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
574               then lhs.shorthandOnlyDefinesConfig
575               else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
576           };
577         };
578       };
580     # A value from a set of allowed ones.
581     enum = values:
582       let
583         show = v:
584                if builtins.isString v then ''"${v}"''
585           else if builtins.isInt v then builtins.toString v
586           else if builtins.isBool v then boolToString v
587           else ''<${builtins.typeOf v}>'';
588       in
589       mkOptionType rec {
590         name = "enum";
591         description =
592           # Length 0 or 1 enums may occur in a design pattern with type merging
593           # where an "interface" module declares an empty enum and other modules
594           # provide implementations, each extending the enum with their own
595           # identifier.
596           if values == [] then
597             "impossible (empty enum)"
598           else if builtins.length values == 1 then
599             "value ${show (builtins.head values)} (singular enum)"
600           else
601             "one of ${concatMapStringsSep ", " show values}";
602         check = flip elem values;
603         merge = mergeEqualOption;
604         functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
605       };
607     # Either value of type `t1` or `t2`.
608     either = t1: t2: mkOptionType rec {
609       name = "either";
610       description = "${t1.description} or ${t2.description}";
611       check = x: t1.check x || t2.check x;
612       merge = loc: defs:
613         let
614           defList = map (d: d.value) defs;
615         in
616           if   all (x: t1.check x) defList
617                then t1.merge loc defs
618           else if all (x: t2.check x) defList
619                then t2.merge loc defs
620           else mergeOneOption loc defs;
621       typeMerge = f':
622         let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
623             mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
624         in
625            if (name == f'.name) && (mt1 != null) && (mt2 != null)
626            then functor.type mt1 mt2
627            else null;
628       functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
629       nestedTypes.left = t1;
630       nestedTypes.right = t2;
631     };
633     # Any of the types in the given list
634     oneOf = ts:
635       let
636         head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
637         tail' = tail ts;
638       in foldl' either head' tail';
640     # Either value of type `coercedType` or `finalType`, the former is
641     # converted to `finalType` using `coerceFunc`.
642     coercedTo = coercedType: coerceFunc: finalType:
643       assert lib.assertMsg (coercedType.getSubModules == null)
644         "coercedTo: coercedType must not have submodules (it’s a ${
645           coercedType.description})";
646       mkOptionType rec {
647         name = "coercedTo";
648         description = "${finalType.description} or ${coercedType.description} convertible to it";
649         check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
650         merge = loc: defs:
651           let
652             coerceVal = val:
653               if coercedType.check val then coerceFunc val
654               else val;
655           in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
656         emptyValue = finalType.emptyValue;
657         getSubOptions = finalType.getSubOptions;
658         getSubModules = finalType.getSubModules;
659         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
660         typeMerge = t1: t2: null;
661         functor = (defaultFunctor name) // { wrapped = finalType; };
662         nestedTypes.coercedType = coercedType;
663         nestedTypes.finalType = finalType;
664       };
666     # Obsolete alternative to configOf.  It takes its option
667     # declarations from the ‘options’ attribute of containing option
668     # declaration.
669     optionSet = mkOptionType {
670       name = "optionSet";
671       description = "option set";
672       deprecationMessage = "Use `types.submodule' instead";
673     };
674     # Augment the given type with an additional type check function.
675     addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
677   };
680 in outer_types // outer_types.types