fork
Configure Feed
Select the types of activity you want to include in your feed.
lol
fork
Configure Feed
Select the types of activity you want to include in your feed.
1# Definitions related to run-time type checking. Used in particular
2# to type-check NixOS configurations.
3{ lib }:
4
5let
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 ;
62
63 inherit (lib.modules)
64 mergeDefinitions
65 fixupOptionType
66 mergeOptionDecls
67 ;
68 outer_types =
69rec {
70 isType = type: x: (x._type or "") == type;
71
72 setType = typeName: value: value // {
73 _type = typeName;
74 };
75
76
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;
97
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 };
106
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 };
184
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})";
209
210 # When adding new types don't forget to document them in
211 # nixos/doc/manual/development/option-types.xml!
212 types = rec {
213
214 raw = mkOptionType rec {
215 name = "raw";
216 description = "raw value";
217 descriptionClass = "noun";
218 check = value: true;
219 merge = mergeOneOption;
220 };
221
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;
233
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;
241
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 };
263
264 unspecified = mkOptionType {
265 name = "unspecified";
266 description = "unspecified value";
267 descriptionClass = "noun";
268 };
269
270 bool = mkOptionType {
271 name = "bool";
272 description = "boolean";
273 descriptionClass = "noun";
274 check = isBool;
275 merge = mergeEqualOption;
276 };
277
278 int = mkOptionType {
279 name = "int";
280 description = "signed integer";
281 descriptionClass = "noun";
282 check = isInt;
283 merge = mergeEqualOption;
284 };
285
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";
307
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;
320
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;
335
336 s8 = sign 8 256;
337 s16 = sign 16 65536;
338 s32 = sign 32 4294967296;
339 };
340
341 # Alias of u16 for a port number
342 port = ints.u16;
343
344 float = mkOptionType {
345 name = "float";
346 description = "floating point number";
347 descriptionClass = "noun";
348 check = isFloat;
349 merge = mergeEqualOption;
350 };
351
352 number = either int float;
353
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 };
365
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 };
375
376 str = mkOptionType {
377 name = "str";
378 description = "string";
379 descriptionClass = "noun";
380 check = isString;
381 merge = mergeEqualOption;
382 };
383
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 };
391
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 };
405
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 };
413
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 };
432
433 lines = separatedString "\n";
434 commas = separatedString ",";
435 envVar = separatedString ":";
436
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 };
443
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 };
448
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 };
456
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 };
474
475 shellPackage = package // {
476 check = x: isDerivation x && hasAttr "shellPath" x;
477 };
478
479 path = mkOptionType {
480 name = "path";
481 descriptionClass = "noun";
482 check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
483 merge = mergeEqualOption;
484 };
485
486 listOf = elemType: mkOptionType rec {
487 name = "listOf";
488 description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
489 descriptionClass = "composite";
490 check = isList;
491 merge = loc: defs:
492 map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
493 imap1 (m: def':
494 (mergeDefinitions
495 (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
496 elemType
497 [{ inherit (def) file; value = def'; }]
498 ).optionalValue
499 ) def.value
500 ) defs)));
501 emptyValue = { value = []; };
502 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
503 getSubModules = elemType.getSubModules;
504 substSubModules = m: listOf (elemType.substSubModules m);
505 functor = (defaultFunctor name) // { wrapped = elemType; };
506 nestedTypes.elemType = elemType;
507 };
508
509 nonEmptyListOf = elemType:
510 let list = addCheck (types.listOf elemType) (l: l != []);
511 in list // {
512 description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
513 emptyValue = { }; # no .value attr, meaning unset
514 };
515
516 attrsOf = elemType: mkOptionType rec {
517 name = "attrsOf";
518 description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
519 descriptionClass = "composite";
520 check = isAttrs;
521 merge = loc: defs:
522 mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
523 (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
524 )
525 # Push down position info.
526 (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
527 emptyValue = { value = {}; };
528 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
529 getSubModules = elemType.getSubModules;
530 substSubModules = m: attrsOf (elemType.substSubModules m);
531 functor = (defaultFunctor name) // { wrapped = elemType; };
532 nestedTypes.elemType = elemType;
533 };
534
535 # A version of attrsOf that's lazy in its values at the expense of
536 # conditional definitions not working properly. E.g. defining a value with
537 # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
538 # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
539 # error that it's not defined. Use only if conditional definitions don't make sense.
540 lazyAttrsOf = elemType: mkOptionType rec {
541 name = "lazyAttrsOf";
542 description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
543 descriptionClass = "composite";
544 check = isAttrs;
545 merge = loc: defs:
546 zipAttrsWith (name: defs:
547 let merged = mergeDefinitions (loc ++ [name]) elemType defs;
548 # mergedValue will trigger an appropriate error when accessed
549 in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
550 )
551 # Push down position info.
552 (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
553 emptyValue = { value = {}; };
554 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
555 getSubModules = elemType.getSubModules;
556 substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
557 functor = (defaultFunctor name) // { wrapped = elemType; };
558 nestedTypes.elemType = elemType;
559 };
560
561 # TODO: drop this in the future:
562 loaOf = elemType: types.attrsOf elemType // {
563 name = "loaOf";
564 deprecationMessage = "Mixing lists with attribute values is no longer"
565 + " possible; please use `types.attrsOf` instead. See"
566 + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
567 nestedTypes.elemType = elemType;
568 };
569
570 # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
571 uniq = elemType: mkOptionType rec {
572 name = "uniq";
573 inherit (elemType) description descriptionClass check;
574 merge = mergeOneOption;
575 emptyValue = elemType.emptyValue;
576 getSubOptions = elemType.getSubOptions;
577 getSubModules = elemType.getSubModules;
578 substSubModules = m: uniq (elemType.substSubModules m);
579 functor = (defaultFunctor name) // { wrapped = elemType; };
580 nestedTypes.elemType = elemType;
581 };
582
583 unique = { message }: type: mkOptionType rec {
584 name = "unique";
585 inherit (type) description descriptionClass check;
586 merge = mergeUniqueOption { inherit message; };
587 emptyValue = type.emptyValue;
588 getSubOptions = type.getSubOptions;
589 getSubModules = type.getSubModules;
590 substSubModules = m: uniq (type.substSubModules m);
591 functor = (defaultFunctor name) // { wrapped = type; };
592 nestedTypes.elemType = type;
593 };
594
595 # Null or value of ...
596 nullOr = elemType: mkOptionType rec {
597 name = "nullOr";
598 description = "null or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType}";
599 descriptionClass = "conjunction";
600 check = x: x == null || elemType.check x;
601 merge = loc: defs:
602 let nrNulls = count (def: def.value == null) defs; in
603 if nrNulls == length defs then null
604 else if nrNulls != 0 then
605 throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
606 else elemType.merge loc defs;
607 emptyValue = { value = null; };
608 getSubOptions = elemType.getSubOptions;
609 getSubModules = elemType.getSubModules;
610 substSubModules = m: nullOr (elemType.substSubModules m);
611 functor = (defaultFunctor name) // { wrapped = elemType; };
612 nestedTypes.elemType = elemType;
613 };
614
615 functionTo = elemType: mkOptionType {
616 name = "functionTo";
617 description = "function that evaluates to a(n) ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
618 descriptionClass = "composite";
619 check = isFunction;
620 merge = loc: defs:
621 fnArgs: (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
622 getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
623 getSubModules = elemType.getSubModules;
624 substSubModules = m: functionTo (elemType.substSubModules m);
625 functor = (defaultFunctor "functionTo") // { wrapped = elemType; };
626 nestedTypes.elemType = elemType;
627 };
628
629 # A submodule (like typed attribute set). See NixOS manual.
630 submodule = modules: submoduleWith {
631 shorthandOnlyDefinesConfig = true;
632 modules = toList modules;
633 };
634
635 # A module to be imported in some other part of the configuration.
636 deferredModule = deferredModuleWith { };
637
638 # A module to be imported in some other part of the configuration.
639 # `staticModules`' options will be added to the documentation, unlike
640 # options declared via `config`.
641 deferredModuleWith = attrs@{ staticModules ? [] }: mkOptionType {
642 name = "deferredModule";
643 description = "module";
644 descriptionClass = "noun";
645 check = x: isAttrs x || isFunction x || path.check x;
646 merge = loc: defs: {
647 imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
648 };
649 inherit (submoduleWith { modules = staticModules; })
650 getSubOptions
651 getSubModules;
652 substSubModules = m: deferredModuleWith (attrs // {
653 staticModules = m;
654 });
655 functor = defaultFunctor "deferredModuleWith" // {
656 type = types.deferredModuleWith;
657 payload = {
658 inherit staticModules;
659 };
660 binOp = lhs: rhs: {
661 staticModules = lhs.staticModules ++ rhs.staticModules;
662 };
663 };
664 };
665
666 # The type of a type!
667 optionType = mkOptionType {
668 name = "optionType";
669 description = "optionType";
670 descriptionClass = "noun";
671 check = value: value._type or null == "option-type";
672 merge = loc: defs:
673 if length defs == 1
674 then (head defs).value
675 else let
676 # Prepares the type definitions for mergeOptionDecls, which
677 # annotates submodules types with file locations
678 optionModules = map ({ value, file }:
679 {
680 _file = file;
681 # There's no way to merge types directly from the module system,
682 # but we can cheat a bit by just declaring an option with the type
683 options = lib.mkOption {
684 type = value;
685 };
686 }
687 ) defs;
688 # Merges all the types into a single one, including submodule merging.
689 # This also propagates file information to all submodules
690 mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
691 in mergedOption.type;
692 };
693
694 submoduleWith =
695 { modules
696 , specialArgs ? {}
697 , shorthandOnlyDefinesConfig ? false
698 , description ? null
699 }@attrs:
700 let
701 inherit (lib.modules) evalModules;
702
703 allModules = defs: map ({ value, file }:
704 if isAttrs value && shorthandOnlyDefinesConfig
705 then { _file = file; config = value; }
706 else { _file = file; imports = [ value ]; }
707 ) defs;
708
709 base = evalModules {
710 inherit specialArgs;
711 modules = [{
712 # This is a work-around for the fact that some sub-modules,
713 # such as the one included in an attribute set, expects an "args"
714 # attribute to be given to the sub-module. As the option
715 # evaluation does not have any specific attribute name yet, we
716 # provide a default for the documentation and the freeform type.
717 #
718 # This is necessary as some option declaration might use the
719 # "name" attribute given as argument of the submodule and use it
720 # as the default of option declarations.
721 #
722 # We use lookalike unicode single angle quotation marks because
723 # of the docbook transformation the options receive. In all uses
724 # > and < wouldn't be encoded correctly so the encoded values
725 # would be used, and use of `<` and `>` would break the XML document.
726 # It shouldn't cause an issue since this is cosmetic for the manual.
727 _module.args.name = lib.mkOptionDefault "‹name›";
728 }] ++ modules;
729 };
730
731 freeformType = base._module.freeformType;
732
733 name = "submodule";
734
735 in
736 mkOptionType {
737 inherit name;
738 description =
739 if description != null then description
740 else freeformType.description or name;
741 check = x: isAttrs x || isFunction x || path.check x;
742 merge = loc: defs:
743 (base.extendModules {
744 modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
745 prefix = loc;
746 }).config;
747 emptyValue = { value = {}; };
748 getSubOptions = prefix: (base.extendModules
749 { inherit prefix; }).options // optionalAttrs (freeformType != null) {
750 # Expose the sub options of the freeform type. Note that the option
751 # discovery doesn't care about the attribute name used here, so this
752 # is just to avoid conflicts with potential options from the submodule
753 _freeformOptions = freeformType.getSubOptions prefix;
754 };
755 getSubModules = modules;
756 substSubModules = m: submoduleWith (attrs // {
757 modules = m;
758 });
759 nestedTypes = lib.optionalAttrs (freeformType != null) {
760 freeformType = freeformType;
761 };
762 functor = defaultFunctor name // {
763 type = types.submoduleWith;
764 payload = {
765 inherit modules specialArgs shorthandOnlyDefinesConfig description;
766 };
767 binOp = lhs: rhs: {
768 modules = lhs.modules ++ rhs.modules;
769 specialArgs =
770 let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
771 in if intersecting == {}
772 then lhs.specialArgs // rhs.specialArgs
773 else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
774 shorthandOnlyDefinesConfig =
775 if lhs.shorthandOnlyDefinesConfig == null
776 then rhs.shorthandOnlyDefinesConfig
777 else if rhs.shorthandOnlyDefinesConfig == null
778 then lhs.shorthandOnlyDefinesConfig
779 else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
780 then lhs.shorthandOnlyDefinesConfig
781 else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
782 description =
783 if lhs.description == null
784 then rhs.description
785 else if rhs.description == null
786 then lhs.description
787 else if lhs.description == rhs.description
788 then lhs.description
789 else throw "A submoduleWith option is declared multiple times with conflicting descriptions";
790 };
791 };
792 };
793
794 # A value from a set of allowed ones.
795 enum = values:
796 let
797 inherit (lib.lists) unique;
798 show = v:
799 if builtins.isString v then ''"${v}"''
800 else if builtins.isInt v then builtins.toString v
801 else if builtins.isBool v then boolToString v
802 else ''<${builtins.typeOf v}>'';
803 in
804 mkOptionType rec {
805 name = "enum";
806 description =
807 # Length 0 or 1 enums may occur in a design pattern with type merging
808 # where an "interface" module declares an empty enum and other modules
809 # provide implementations, each extending the enum with their own
810 # identifier.
811 if values == [] then
812 "impossible (empty enum)"
813 else if builtins.length values == 1 then
814 "value ${show (builtins.head values)} (singular enum)"
815 else
816 "one of ${concatMapStringsSep ", " show values}";
817 descriptionClass =
818 if builtins.length values < 2
819 then "noun"
820 else "conjunction";
821 check = flip elem values;
822 merge = mergeEqualOption;
823 functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
824 };
825
826 # Either value of type `t1` or `t2`.
827 either = t1: t2: mkOptionType rec {
828 name = "either";
829 description = "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction" || class == "composite") t2}";
830 descriptionClass = "conjunction";
831 check = x: t1.check x || t2.check x;
832 merge = loc: defs:
833 let
834 defList = map (d: d.value) defs;
835 in
836 if all (x: t1.check x) defList
837 then t1.merge loc defs
838 else if all (x: t2.check x) defList
839 then t2.merge loc defs
840 else mergeOneOption loc defs;
841 typeMerge = f':
842 let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
843 mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
844 in
845 if (name == f'.name) && (mt1 != null) && (mt2 != null)
846 then functor.type mt1 mt2
847 else null;
848 functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
849 nestedTypes.left = t1;
850 nestedTypes.right = t2;
851 };
852
853 # Any of the types in the given list
854 oneOf = ts:
855 let
856 head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
857 tail' = tail ts;
858 in foldl' either head' tail';
859
860 # Either value of type `coercedType` or `finalType`, the former is
861 # converted to `finalType` using `coerceFunc`.
862 coercedTo = coercedType: coerceFunc: finalType:
863 assert lib.assertMsg (coercedType.getSubModules == null)
864 "coercedTo: coercedType must not have submodules (it’s a ${
865 coercedType.description})";
866 mkOptionType rec {
867 name = "coercedTo";
868 description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${optionDescriptionPhrase (class: class == "noun") coercedType} convertible to it";
869 check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
870 merge = loc: defs:
871 let
872 coerceVal = val:
873 if coercedType.check val then coerceFunc val
874 else val;
875 in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
876 emptyValue = finalType.emptyValue;
877 getSubOptions = finalType.getSubOptions;
878 getSubModules = finalType.getSubModules;
879 substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
880 typeMerge = t1: t2: null;
881 functor = (defaultFunctor name) // { wrapped = finalType; };
882 nestedTypes.coercedType = coercedType;
883 nestedTypes.finalType = finalType;
884 };
885
886 # Augment the given type with an additional type check function.
887 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
888
889 };
890};
891
892in outer_types // outer_types.types