at 15.09-beta 8.0 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; }; 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}