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 = "derivation";
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: imap (m: def':
118 (mergeDefinitions
119 (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
120 elemType
121 [{ inherit (def) file; value = def'; }]
122 ).optionalValue
123 ) def.value) defs)));
124 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
125 getSubModules = elemType.getSubModules;
126 substSubModules = m: listOf (elemType.substSubModules m);
127 };
128
129 attrsOf = elemType: mkOptionType {
130 name = "attribute set of ${elemType.name}s";
131 check = isAttrs;
132 merge = loc: defs:
133 mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
134 (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
135 )
136 # Push down position info.
137 (map (def: listToAttrs (mapAttrsToList (n: def':
138 { name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs)));
139 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
140 getSubModules = elemType.getSubModules;
141 substSubModules = m: attrsOf (elemType.substSubModules m);
142 };
143
144 # List or attribute set of ...
145 loaOf = elemType:
146 let
147 convertIfList = defIdx: def:
148 if isList def.value then
149 { inherit (def) file;
150 value = listToAttrs (
151 imap (elemIdx: elem:
152 { name = elem.name or "unnamed-${toString defIdx}.${toString elemIdx}";
153 value = elem;
154 }) def.value);
155 }
156 else
157 def;
158 listOnly = listOf elemType;
159 attrOnly = attrsOf elemType;
160 in mkOptionType {
161 name = "list or attribute set of ${elemType.name}s";
162 check = x: isList x || isAttrs x;
163 merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
164 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
165 getSubModules = elemType.getSubModules;
166 substSubModules = m: loaOf (elemType.substSubModules m);
167 };
168
169 # List or element of ...
170 loeOf = elemType: mkOptionType {
171 name = "element or list of ${elemType.name}s";
172 check = x: isList x || elemType.check x;
173 merge = loc: defs:
174 let
175 defs' = filterOverrides defs;
176 res = (head defs').value;
177 in
178 if isList res then concatLists (getValues defs')
179 else if lessThan 1 (length defs') then
180 throw "The option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}."
181 else if !isString res then
182 throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
183 else res;
184 };
185
186 uniq = elemType: mkOptionType {
187 inherit (elemType) name check;
188 merge = mergeOneOption;
189 getSubOptions = elemType.getSubOptions;
190 getSubModules = elemType.getSubModules;
191 substSubModules = m: uniq (elemType.substSubModules m);
192 };
193
194 nullOr = elemType: mkOptionType {
195 name = "null or ${elemType.name}";
196 check = x: builtins.isNull x || elemType.check x;
197 merge = loc: defs:
198 let nrNulls = count (def: isNull def.value) defs; in
199 if nrNulls == length defs then null
200 else if nrNulls != 0 then
201 throw "The option `${showOption loc}' is defined both null and not null, in ${showFiles (getFiles defs)}."
202 else elemType.merge loc defs;
203 getSubOptions = elemType.getSubOptions;
204 getSubModules = elemType.getSubModules;
205 substSubModules = m: nullOr (elemType.substSubModules m);
206 };
207
208 submodule = opts:
209 let
210 opts' = toList opts;
211 inherit (import ./modules.nix) evalModules;
212 in
213 mkOptionType rec {
214 name = "submodule";
215 check = x: isAttrs x || isFunction x;
216 merge = loc: defs:
217 let
218 coerce = def: if isFunction def then def else { config = def; };
219 modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
220 in (evalModules {
221 inherit modules;
222 args.name = last loc;
223 prefix = loc;
224 }).config;
225 getSubOptions = prefix: (evalModules
226 { modules = opts'; inherit prefix;
227 # FIXME: hack to get shit to evaluate.
228 args = { name = ""; }; }).options;
229 getSubModules = opts';
230 substSubModules = m: submodule m;
231 };
232
233 enum = values: mkOptionType {
234 name = "one of ${concatStringsSep ", " values}";
235 check = flip elem values;
236 merge = mergeOneOption;
237 };
238
239 either = t1: t2: mkOptionType {
240 name = "${t1.name} or ${t2.name}";
241 check = x: t1.check x || t2.check x;
242 merge = mergeOneOption;
243 };
244
245 # Obsolete alternative to configOf. It takes its option
246 # declarations from the ‘options’ attribute of containing option
247 # declaration.
248 optionSet = mkOptionType {
249 name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
250 };
251
252 # Augment the given type with an additional type check function.
253 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
254
255 };
256
257}