Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at release-16.03 268 lines 9.1 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 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}