1 # Definitions related to run-time type checking. Used in particular
2 # to type-check NixOS configurations.
37 inherit (lib.attrsets)
64 inherit (lib.modules) mergeDefinitions;
67 isType = type: x: (x._type or "") == type;
69 setType = typeName: value: value // {
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;
80 # cannot merge different types
84 else if (f.wrapped == null && f'.wrapped == null)
85 && (f.payload == null && f'.payload == null)
88 else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
91 else if (f.payload != null && f'.payload != null) && (payload != null)
95 # Default type functor
96 defaultFunctor = name: {
98 type = types.${name} or null;
104 isOptionType = isType "option-type";
106 { # Human-readable representation of the type, should be equivalent to
107 # the type function name.
109 , # Description of the type, defined recursively by embedding the wrapped type if any.
111 , # Function applied to each definition that should return true if
112 # its type-correct, false otherwise.
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`.
126 , # Return a flat list of sub-options. Used to generate
128 getSubOptions ? prefix: {}
129 , # List of modules if any, or null if none.
131 , # Function for building the same option type with a different list of
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
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;
157 # When adding new types don't forget to document them in
158 # nixos/doc/manual/development/option-types.xml!
161 anything = mkOptionType {
163 description = "anything";
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
177 else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
178 ) (getType (head defs).value) defs;
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.
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;
196 unspecified = mkOptionType {
197 name = "unspecified";
200 bool = mkOptionType {
202 description = "boolean";
204 merge = mergeEqualOption;
209 description = "signed integer";
211 merge = mergeEqualOption;
214 # Specialized subdomains of int
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) // {
224 description = "integer between ${betweenDesc lowest highest}";
226 ign = lowest: highest: name: docStart:
227 between lowest highest // {
229 description = docStart + "; between ${betweenDesc lowest highest}";
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";
237 /* An int with a fixed range.
240 * (ints.between 0 100).check (-1)
242 * (ints.between 0 100).check (101)
244 * (ints.between 0 0).check 0
249 unsigned = addCheck types.int (x: x >= 0) // {
250 name = "unsignedInt";
251 description = "unsigned integer, meaning >=0";
253 positive = addCheck types.int (x: x > 0) // {
254 name = "positiveInt";
255 description = "positive integer, meaning >0";
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;
266 # s32 = sign 32 4294967296;
269 # Alias of u16 for a port number
272 float = mkOptionType {
274 description = "floating point number";
276 merge = mergeEqualOption;
281 description = "string";
283 merge = mergeEqualOption;
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;
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}"
302 merge = loc: defs: concatStringsSep sep (getValues defs);
303 functor = (defaultFunctor name) // {
305 binOp = sepLhs: sepRhs:
306 if sepLhs == sepRhs then sepLhs
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 "" // {
319 deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types.";
322 attrs = mkOptionType {
324 description = "attribute set";
326 merge = loc: foldl' (res: def: res // def.value) {};
327 emptyValue = { value = {}; };
330 # derivation is a reserved keyword.
331 package = mkOptionType {
333 check = x: isDerivation x || isStorePath x;
335 let res = mergeOneOption loc defs;
336 in if isDerivation res then res else toDerivation res;
339 shellPackage = package // {
340 check = x: (package.check x) && (hasAttr "shellPath" x);
343 path = mkOptionType {
345 check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
346 merge = mergeEqualOption;
349 listOf = elemType: mkOptionType rec {
351 description = "list of ${elemType.description}s";
354 map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
357 (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
359 [{ inherit (def) file; value = def'; }]
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; };
370 nonEmptyListOf = elemType:
371 let list = addCheck (types.listOf elemType) (l: l != []);
373 description = "non-empty " + list.description;
374 # Note: emptyValue is left as is, because another module may define an element.
377 attrsOf = elemType: mkOptionType rec {
379 description = "attribute set of ${elemType.description}s";
382 mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
383 (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
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; };
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";
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
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; };
418 # TODO: drop this in the future:
419 loaOf = elemType: types.attrsOf elemType // {
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.";
426 # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
427 uniq = elemType: mkOptionType rec {
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; };
438 # Null or value of ...
439 nullOr = elemType: mkOptionType rec {
441 description = "null or ${elemType.description}";
442 check = x: x == null || elemType.check x;
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; };
456 functionTo = elemType: mkOptionType {
458 description = "function that evaluates to a(n) ${elemType.name}";
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);
467 # A submodule (like typed attribute set). See NixOS manual.
468 submodule = modules: submoduleWith {
469 shorthandOnlyDefinesConfig = true;
470 modules = toList modules;
476 , shorthandOnlyDefinesConfig ? false
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
492 freeformType = (evalModules {
493 inherit modules specialArgs;
494 args.name = "‹name›";
495 })._module.freeformType;
500 description = freeformType.description or name;
501 check = x: isAttrs x || isFunction x || path.check x;
504 modules = allModules defs;
506 args.name = last loc;
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.
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.
522 # Using lookalike unicode single angle quotation marks because
523 # of the docbook transformation the options receive. In all uses
524 # > and < 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;
534 getSubModules = modules;
535 substSubModules = m: submoduleWith (attrs // {
538 functor = defaultFunctor name // {
539 type = types.submoduleWith;
542 specialArgs = specialArgs;
543 shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig;
546 modules = lhs.modules ++ rhs.modules;
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";
560 # A value from a set of allowed ones.
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}>'';
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); };
577 # Either value of type `t1` or `t2`.
578 either = t1: t2: mkOptionType rec {
580 description = "${t1.description} or ${t2.description}";
581 check = x: t1.check x || t2.check x;
584 defList = map (d: d.value) defs;
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;
592 let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
593 mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
595 if (name == f'.name) && (mt1 != null) && (mt2 != null)
596 then functor.type mt1 mt2
598 functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
601 # Any of the types in the given list
604 head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head 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})";
616 description = "${finalType.description} or ${coercedType.description} convertible to it";
617 check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
621 if coercedType.check val then coerceFunc 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; };
632 # Obsolete alternative to configOf. It takes its option
633 # declarations from the ‘options’ attribute of containing option
635 optionSet = mkOptionType {
637 description = "option set";
638 deprecationMessage = "Use `types.submodule' instead";
640 # Augment the given type with an additional type check function.
641 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
646 in outer_types // outer_types.types