1# Definitions related to run-time type checking. Used in particular
2# to type-check NixOS configurations.
3
4with import ./lists.nix;
5with import ./attrsets.nix;
6with import ./options.nix;
7with import ./trivial.nix;
8with import ./strings.nix;
9with {inherit (import ./modules.nix) mergeDefinitions filterOverrides; };
10
11rec {
12
13 isType = type: x: (x._type or "") == type;
14
15 setType = typeName: value: value // {
16 _type = typeName;
17 };
18
19
20 isOptionType = isType "option-type";
21 mkOptionType =
22 { # Human-readable representation of the type.
23 name
24 , # Function applied to each definition that should return true if
25 # its type-correct, false otherwise.
26 check ? (x: true)
27 , # Merge a list of definitions together into a single value.
28 # This function is called with two arguments: the location of
29 # the option in the configuration as a list of strings
30 # (e.g. ["boot" "loader "grub" "enable"]), and a list of
31 # definition values and locations (e.g. [ { file = "/foo.nix";
32 # value = 1; } { file = "/bar.nix"; value = 2 } ]).
33 merge ? mergeDefaultOption
34 , # Return a flat list of sub-options. Used to generate
35 # documentation.
36 getSubOptions ? prefix: {}
37 , # List of modules if any, or null if none.
38 getSubModules ? null
39 , # Function for building the same option type with a different list of
40 # modules.
41 substSubModules ? m: null
42 }:
43 { _type = "option-type";
44 inherit name check merge getSubOptions getSubModules substSubModules;
45 };
46
47
48 types = rec {
49
50 unspecified = mkOptionType {
51 name = "unspecified";
52 };
53
54 bool = mkOptionType {
55 name = "boolean";
56 check = isBool;
57 merge = mergeEqualOption;
58 };
59
60 int = mkOptionType {
61 name = "integer";
62 check = isInt;
63 merge = mergeOneOption;
64 };
65
66 str = mkOptionType {
67 name = "string";
68 check = isString;
69 merge = mergeOneOption;
70 };
71
72 # Merge multiple definitions by concatenating them (with the given
73 # separator between the values).
74 separatedString = sep: mkOptionType {
75 name = "string";
76 check = isString;
77 merge = loc: defs: concatStringsSep sep (getValues defs);
78 };
79
80 lines = separatedString "\n";
81 commas = separatedString ",";
82 envVar = separatedString ":";
83
84 # Deprecated; should not be used because it quietly concatenates
85 # strings, which is usually not what you want.
86 string = separatedString "";
87
88 attrs = mkOptionType {
89 name = "attribute set";
90 check = isAttrs;
91 merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
92 };
93
94 # derivation is a reserved keyword.
95 package = mkOptionType {
96 name = "package";
97 check = x: isDerivation x || isStorePath x;
98 merge = loc: defs:
99 let res = mergeOneOption loc defs;
100 in if isDerivation res then res else toDerivation res;
101 };
102
103 path = mkOptionType {
104 name = "path";
105 # Hacky: there is no ‘isPath’ primop.
106 check = x: builtins.substring 0 1 (toString x) == "/";
107 merge = mergeOneOption;
108 };
109
110 # drop this in the future:
111 list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
112
113 listOf = elemType: mkOptionType {
114 name = "list of ${elemType.name}s";
115 check = isList;
116 merge = loc: defs:
117 map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def:
118 if isList def.value then
119 imap (m: def':
120 (mergeDefinitions
121 (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
122 elemType
123 [{ inherit (def) file; value = def'; }]
124 ).optionalValue
125 ) def.value
126 else
127 throw "The option value `${showOption loc}' in `${def.file}' is not a list.") defs)));
128 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
129 getSubModules = elemType.getSubModules;
130 substSubModules = m: listOf (elemType.substSubModules m);
131 };
132
133 attrsOf = elemType: mkOptionType {
134 name = "attribute set of ${elemType.name}s";
135 check = isAttrs;
136 merge = loc: defs:
137 mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
138 (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
139 )
140 # Push down position info.
141 (map (def: listToAttrs (mapAttrsToList (n: def':
142 { name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs)));
143 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
144 getSubModules = elemType.getSubModules;
145 substSubModules = m: attrsOf (elemType.substSubModules m);
146 };
147
148 # List or attribute set of ...
149 loaOf = elemType:
150 let
151 convertIfList = defIdx: def:
152 if isList def.value then
153 { inherit (def) file;
154 value = listToAttrs (
155 imap (elemIdx: elem:
156 { name = elem.name or "unnamed-${toString defIdx}.${toString elemIdx}";
157 value = elem;
158 }) def.value);
159 }
160 else
161 def;
162 listOnly = listOf elemType;
163 attrOnly = attrsOf elemType;
164 in mkOptionType {
165 name = "list or attribute set of ${elemType.name}s";
166 check = x: isList x || isAttrs x;
167 merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
168 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
169 getSubModules = elemType.getSubModules;
170 substSubModules = m: loaOf (elemType.substSubModules m);
171 };
172
173 # List or element of ...
174 loeOf = elemType: mkOptionType {
175 name = "element or list of ${elemType.name}s";
176 check = x: isList x || elemType.check x;
177 merge = loc: defs:
178 let
179 defs' = filterOverrides defs;
180 res = (head defs').value;
181 in
182 if isList res then concatLists (getValues defs')
183 else if lessThan 1 (length defs') then
184 throw "The option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}."
185 else if !isString res then
186 throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
187 else res;
188 };
189
190 uniq = elemType: mkOptionType {
191 inherit (elemType) name check;
192 merge = mergeOneOption;
193 getSubOptions = elemType.getSubOptions;
194 getSubModules = elemType.getSubModules;
195 substSubModules = m: uniq (elemType.substSubModules m);
196 };
197
198 nullOr = elemType: mkOptionType {
199 name = "null or ${elemType.name}";
200 check = x: x == null || elemType.check x;
201 merge = loc: defs:
202 let nrNulls = count (def: def.value == null) defs; in
203 if nrNulls == length defs then null
204 else if nrNulls != 0 then
205 throw "The option `${showOption loc}' is defined both null and not null, in ${showFiles (getFiles defs)}."
206 else elemType.merge loc defs;
207 getSubOptions = elemType.getSubOptions;
208 getSubModules = elemType.getSubModules;
209 substSubModules = m: nullOr (elemType.substSubModules m);
210 };
211
212 submodule = opts:
213 let
214 opts' = toList opts;
215 inherit (import ./modules.nix) evalModules;
216 in
217 mkOptionType rec {
218 name = "submodule";
219 check = x: isAttrs x || isFunction x;
220 merge = loc: defs:
221 let
222 coerce = def: if isFunction def then def else { config = def; };
223 modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
224 in (evalModules {
225 inherit modules;
226 args.name = last loc;
227 prefix = loc;
228 }).config;
229 getSubOptions = prefix: (evalModules
230 { modules = opts'; inherit prefix;
231 # FIXME: hack to get shit to evaluate.
232 args = { name = ""; }; }).options;
233 getSubModules = opts';
234 substSubModules = m: submodule m;
235 };
236
237 enum = values:
238 let
239 show = v:
240 if builtins.isString v then ''"${v}"''
241 else if builtins.isInt v then builtins.toString v
242 else ''<${builtins.typeOf v}>'';
243 in
244 mkOptionType {
245 name = "one of ${concatMapStringsSep ", " show values}";
246 check = flip elem values;
247 merge = mergeOneOption;
248 };
249
250 either = t1: t2: mkOptionType {
251 name = "${t1.name} or ${t2.name}";
252 check = x: t1.check x || t2.check x;
253 merge = mergeOneOption;
254 };
255
256 # Obsolete alternative to configOf. It takes its option
257 # declarations from the ‘options’ attribute of containing option
258 # declaration.
259 optionSet = mkOptionType {
260 name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
261 };
262
263 # Augment the given type with an additional type check function.
264 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
265
266 };
267
268}