at v206 8.7 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 = "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}