summary refs log tree commit diff
path: root/lib/types.nix
diff options
context:
space:
mode:
Diffstat (limited to 'lib/types.nix')
-rw-r--r--lib/types.nix210
1 files changed, 210 insertions, 0 deletions
diff --git a/lib/types.nix b/lib/types.nix
new file mode 100644
index 000000000000..09b29a762e1d
--- /dev/null
+++ b/lib/types.nix
@@ -0,0 +1,210 @@
+# Definitions related to run-time type checking.  Used in particular
+# to type-check NixOS configurations.
+
+with import ./lists.nix;
+with import ./attrsets.nix;
+with import ./options.nix;
+with import ./trivial.nix;
+with import ./strings.nix;
+
+rec {
+
+  isType = type: x: (x._type or "") == type;
+  typeOf = x: x._type or "";
+
+  setType = typeName: value: value // {
+    _type = typeName;
+  };
+
+
+  isOptionType = isType "option-type";
+  mkOptionType =
+    { # Human-readable representation of the type.
+      name
+    , # Function applied to each definition that should return true if
+      # its type-correct, false otherwise.
+      check ? (x: true)
+    , # Merge a list of definitions together into a single value.
+      # This function is called with two arguments: the location of
+      # the option in the configuration as a list of strings
+      # (e.g. ["boot" "loader "grub" "enable"]), and a list of
+      # definition values and locations (e.g. [ { file = "/foo.nix";
+      # value = 1; } { file = "/bar.nix"; value = 2 } ]).
+      merge ? mergeDefaultOption
+    , # Return a flat list of sub-options.  Used to generate
+      # documentation.
+      getSubOptions ? prefix: {}
+    }:
+    { _type = "option-type";
+      inherit name check merge getSubOptions;
+    };
+
+
+  types = rec {
+
+    unspecified = mkOptionType {
+      name = "unspecified";
+    };
+
+    bool = mkOptionType {
+      name = "boolean";
+      check = builtins.isBool;
+      merge = loc: fold (x: y: x.value || y) false;
+    };
+
+    int = mkOptionType {
+      name = "integer";
+      check = builtins.isInt;
+      merge = mergeOneOption;
+    };
+
+    str = mkOptionType {
+      name = "string";
+      check = builtins.isString;
+      merge = mergeOneOption;
+    };
+
+    # Merge multiple definitions by concatenating them (with the given
+    # separator between the values).
+    separatedString = sep: mkOptionType {
+      name = "string";
+      check = builtins.isString;
+      merge = loc: defs: concatStringsSep sep (getValues defs);
+    };
+
+    lines = separatedString "\n";
+    commas = separatedString ",";
+    envVar = separatedString ":";
+
+    # Deprecated; should not be used because it quietly concatenates
+    # strings, which is usually not what you want.
+    string = separatedString "";
+
+    attrs = mkOptionType {
+      name = "attribute set";
+      check = isAttrs;
+      merge = loc: fold (def: mergeAttrs def.value) {};
+    };
+
+    # derivation is a reserved keyword.
+    package = mkOptionType {
+      name = "derivation";
+      check = isDerivation;
+      merge = mergeOneOption;
+    };
+
+    path = mkOptionType {
+      name = "path";
+      # Hacky: there is no ‘isPath’ primop.
+      check = x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/";
+      merge = mergeOneOption;
+    };
+
+    # drop this in the future:
+    list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
+
+    listOf = elemType: mkOptionType {
+      name = "list of ${elemType.name}s";
+      check = value: isList value && all elemType.check value;
+      merge = loc: defs:
+        concatLists (imap (n: def: imap (m: def':
+          elemType.merge (loc ++ ["[${toString n}-${toString m}]"])
+            [{ inherit (def) file; value = def'; }]) def.value) defs);
+      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
+    };
+
+    attrsOf = elemType: mkOptionType {
+      name = "attribute set of ${elemType.name}s";
+      check = x: isAttrs x && all elemType.check (attrValues x);
+      merge = loc: defs:
+        zipAttrsWith (name: elemType.merge (loc ++ [name]))
+          # Push down position info.
+          (map (def: listToAttrs (mapAttrsToList (n: def':
+            { name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs);
+      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
+    };
+
+    # List or attribute set of ...
+    loaOf = elemType:
+      let
+        convertIfList = defIdx: def:
+          if isList def.value then
+            { inherit (def) file;
+              value = listToAttrs (
+                imap (elemIdx: elem:
+                  { name = "unnamed-${toString defIdx}.${toString elemIdx}";
+                    value = elem;
+                  }) def.value);
+            }
+          else
+            def;
+        listOnly = listOf elemType;
+        attrOnly = attrsOf elemType;
+      in mkOptionType {
+        name = "list or attribute set of ${elemType.name}s";
+        check = x:
+          if isList x       then listOnly.check x
+          else if isAttrs x then attrOnly.check x
+          else false;
+        merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
+        getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
+      };
+
+    uniq = elemType: mkOptionType {
+      inherit (elemType) name check;
+      merge = mergeOneOption;
+      getSubOptions = elemType.getSubOptions;
+    };
+
+    nullOr = elemType: mkOptionType {
+      name = "null or ${elemType.name}";
+      check = x: builtins.isNull x || elemType.check x;
+      merge = loc: defs:
+        let nrNulls = count (def: isNull def.value) defs; in
+        if nrNulls == length defs then null
+        else if nrNulls != 0 then
+          throw "The option `${showOption loc}' is defined both null and not null, in ${showFiles (getFiles defs)}."
+        else elemType.merge loc defs;
+      getSubOptions = elemType.getSubOptions;
+    };
+
+    functionTo = elemType: mkOptionType {
+      name = "function that evaluates to a(n) ${elemType.name}";
+      check = builtins.isFunction;
+      merge = loc: defs:
+        fnArgs: elemType.merge loc (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs);
+      getSubOptions = elemType.getSubOptions;
+    };
+
+    submodule = opts:
+      let
+        opts' = toList opts;
+        inherit (import ./modules.nix) evalModules;
+      in
+      mkOptionType rec {
+        name = "submodule";
+        check = x: isAttrs x || builtins.isFunction x;
+        merge = loc: defs:
+          let
+            coerce = def: if builtins.isFunction def then def else { config = def; };
+            modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
+          in (evalModules { inherit modules; args.name = last loc; prefix = loc; }).config;
+        getSubOptions = prefix: (evalModules
+          { modules = opts'; inherit prefix;
+            # FIXME: hack to get shit to evaluate.
+            args = { name = ""; }; }).options;
+      };
+
+    # Obsolete alternative to configOf.  It takes its option
+    # declarations from the ‘options’ attribute of containing option
+    # declaration.
+    optionSet = mkOptionType {
+      name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
+    };
+
+    # Augment the given type with an additional type check function.
+    addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
+
+  };
+
+}