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; };
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 uniq = elemType: mkOptionType {
170 inherit (elemType) name check;
171 merge = mergeOneOption;
172 getSubOptions = elemType.getSubOptions;
173 getSubModules = elemType.getSubModules;
174 substSubModules = m: uniq (elemType.substSubModules m);
175 };
176
177 nullOr = elemType: mkOptionType {
178 name = "null or ${elemType.name}";
179 check = x: builtins.isNull x || elemType.check x;
180 merge = loc: defs:
181 let nrNulls = count (def: isNull def.value) defs; in
182 if nrNulls == length defs then null
183 else if nrNulls != 0 then
184 throw "The option `${showOption loc}' is defined both null and not null, in ${showFiles (getFiles defs)}."
185 else elemType.merge loc defs;
186 getSubOptions = elemType.getSubOptions;
187 getSubModules = elemType.getSubModules;
188 substSubModules = m: nullOr (elemType.substSubModules m);
189 };
190
191 submodule = opts:
192 let
193 opts' = toList opts;
194 inherit (import ./modules.nix) evalModules;
195 in
196 mkOptionType rec {
197 name = "submodule";
198 check = x: isAttrs x || isFunction x;
199 merge = loc: defs:
200 let
201 coerce = def: if isFunction def then def else { config = def; };
202 modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
203 in (evalModules {
204 inherit modules;
205 args.name = last loc;
206 prefix = loc;
207 }).config;
208 getSubOptions = prefix: (evalModules
209 { modules = opts'; inherit prefix;
210 # FIXME: hack to get shit to evaluate.
211 args = { name = ""; }; }).options;
212 getSubModules = opts';
213 substSubModules = m: submodule m;
214 };
215
216 enum = values: mkOptionType {
217 name = "one of ${concatStringsSep ", " values}";
218 check = flip elem values;
219 merge = mergeOneOption;
220 };
221
222 either = t1: t2: mkOptionType {
223 name = "${t1.name} or ${t2.name}";
224 check = x: t1.check x || t2.check x;
225 merge = mergeOneOption;
226 };
227
228 # Obsolete alternative to configOf. It takes its option
229 # declarations from the ‘options’ attribute of containing option
230 # declaration.
231 optionSet = mkOptionType {
232 name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
233 };
234
235 # Augment the given type with an additional type check function.
236 addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
237
238 };
239
240}