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