about summary refs log tree commit diff
path: root/nixpkgs/lib/types.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/lib/types.nix')
-rw-r--r--nixpkgs/lib/types.nix195
1 files changed, 73 insertions, 122 deletions
diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix
index 6fd6de7e1fd9..77105740bc23 100644
--- a/nixpkgs/lib/types.nix
+++ b/nixpkgs/lib/types.nix
@@ -91,9 +91,12 @@ rec {
       #            combinable with the binOp binary operation.
       #   binOp: binary operation that merge two payloads of the same type.
       functor ? defaultFunctor name
+    , # The deprecation message to display when this type is used by an option
+      # If null, the type isn't deprecated
+      deprecationMessage ? null
     }:
     { _type = "option-type";
-      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor;
+      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage;
       description = if description == null then name else description;
     };
 
@@ -101,6 +104,42 @@ rec {
   # When adding new types don't forget to document them in
   # nixos/doc/manual/development/option-types.xml!
   types = rec {
+
+    anything = mkOptionType {
+      name = "anything";
+      description = "anything";
+      check = value: true;
+      merge = loc: defs:
+        let
+          getType = value:
+            if isAttrs value && isCoercibleToString value
+            then "stringCoercibleSet"
+            else builtins.typeOf value;
+
+          # Returns the common type of all definitions, throws an error if they
+          # don't have the same type
+          commonType = foldl' (type: def:
+            if getType def.value == type
+            then type
+            else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
+          ) (getType (head defs).value) defs;
+
+          mergeFunction = {
+            # Recursively merge attribute sets
+            set = (attrsOf anything).merge;
+            # Safe and deterministic behavior for lists is to only accept one definition
+            # listOf only used to apply mkIf and co.
+            list =
+              if length defs > 1
+              then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
+              else (listOf anything).merge;
+            # This is the type of packages, only accept a single definition
+            stringCoercibleSet = mergeOneOption;
+            # Otherwise fall back to only allowing all equal definitions
+          }.${commonType} or mergeEqualOption;
+        in mergeFunction loc defs;
+    };
+
     unspecified = mkOptionType {
       name = "unspecified";
     };
@@ -222,8 +261,10 @@ rec {
 
     # Deprecated; should not be used because it quietly concatenates
     # strings, which is usually not what you want.
-    string = warn "types.string is deprecated because it quietly concatenates strings"
-      (separatedString "");
+    string = separatedString "" // {
+      name = "string";
+      deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types.";
+    };
 
     attrs = mkOptionType {
       name = "attrs";
@@ -252,25 +293,20 @@ rec {
       merge = mergeEqualOption;
     };
 
-    # drop this in the future:
-    list = builtins.trace "`types.list` is deprecated; use `types.listOf` instead" types.listOf;
-
     listOf = elemType: mkOptionType rec {
       name = "listOf";
       description = "list of ${elemType.description}s";
       check = isList;
       merge = loc: defs:
         map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
-          if isList def.value then
-            imap1 (m: def':
-              (mergeDefinitions
-                (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
-                elemType
-                [{ inherit (def) file; value = def'; }]
-              ).optionalValue
-            ) def.value
-          else
-            throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
+          imap1 (m: def':
+            (mergeDefinitions
+              (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
+              elemType
+              [{ inherit (def) file; value = def'; }]
+            ).optionalValue
+          ) def.value
+        ) defs)));
       emptyValue = { value = {}; };
       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
       getSubModules = elemType.getSubModules;
@@ -326,110 +362,13 @@ rec {
       functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
-    # List or attribute set of ...
-    loaOf = elemType:
-      let
-        convertAllLists = loc: defs:
-          let
-            padWidth = stringLength (toString (length defs));
-            unnamedPrefix = i: "unnamed-" + fixedWidthNumber padWidth i + ".";
-          in
-            imap1 (i: convertIfList loc (unnamedPrefix i)) defs;
-        convertIfList = loc: unnamedPrefix: def:
-          if isList def.value then
-            let
-              padWidth = stringLength (toString (length def.value));
-              unnamed = i: unnamedPrefix + fixedWidthNumber padWidth i;
-              anyString = placeholder "name";
-              nameAttrs = [
-                { path = [ "environment" "etc" ];
-                  name = "target";
-                }
-                { path = [ "containers" anyString "bindMounts" ];
-                  name = "mountPoint";
-                }
-                { path = [ "programs" "ssh" "knownHosts" ];
-                  # hostNames is actually a list so we would need to handle it only when singleton
-                  name = "hostNames";
-                }
-                { path = [ "fileSystems" ];
-                  name = "mountPoint";
-                }
-                { path = [ "boot" "specialFileSystems" ];
-                  name = "mountPoint";
-                }
-                { path = [ "services" "znapzend" "zetup" ];
-                  name = "dataset";
-                }
-                { path = [ "services" "znapzend" "zetup" anyString "destinations" ];
-                  name = "label";
-                }
-                { path = [ "services" "geoclue2" "appConfig" ];
-                  name = "desktopID";
-                }
-              ];
-              matched = let
-                equals = a: b: b == anyString || a == b;
-                fallback = { name = "name"; };
-              in findFirst ({ path, ... }: all (v: v == true) (zipListsWith equals loc path)) fallback nameAttrs;
-              nameAttr = matched.name;
-              nameValueOld = value:
-                if isList value then
-                  if length value > 0 then
-                    "[ " + concatMapStringsSep " " escapeNixString value + " ]"
-                  else
-                    "[ ]"
-                else
-                  escapeNixString value;
-              nameValueNew = value: unnamed:
-                if isList value then
-                  if length value > 0 then
-                    head value
-                  else
-                    unnamed
-                else
-                  value;
-              res =
-                { inherit (def) file;
-                  value = listToAttrs (
-                    imap1 (elemIdx: elem:
-                      { name  = nameValueNew (elem.${nameAttr} or (unnamed elemIdx)) (unnamed elemIdx);
-                        value = elem;
-                      }) def.value);
-                };
-              option = concatStringsSep "." loc;
-              sample = take 3 def.value;
-              more = lib.optionalString (length def.value > 3) "... ";
-              list = concatMapStrings (x: ''{ ${nameAttr} = ${nameValueOld (x.${nameAttr} or "unnamed")}; ...} '') sample;
-              set = concatMapStrings (x: ''${nameValueNew (x.${nameAttr} or "unnamed") "unnamed"} = {...}; '') sample;
-              msg = ''
-                In file ${def.file}
-                a list is being assigned to the option config.${option}.
-                This will soon be an error as type loaOf is deprecated.
-                See https://github.com/NixOS/nixpkgs/pull/63103 for more information.
-                Do
-                  ${option} =
-                    { ${set}${more}}
-                instead of
-                  ${option} =
-                    [ ${list}${more}]
-              '';
-            in
-              lib.warn msg res
-          else
-            def;
-        attrOnly = attrsOf elemType;
-      in mkOptionType rec {
-        name = "loaOf";
-        description = "list or attribute set of ${elemType.description}s";
-        check = x: isList x || isAttrs x;
-        merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs);
-        emptyValue = { value = {}; };
-        getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
-        getSubModules = elemType.getSubModules;
-        substSubModules = m: loaOf (elemType.substSubModules m);
-        functor = (defaultFunctor name) // { wrapped = elemType; };
-      };
+    # TODO: drop this in the future:
+    loaOf = elemType: types.attrsOf elemType // {
+      name = "loaOf";
+      deprecationMessage = "Mixing lists with attribute values is no longer"
+        + " possible; please use `types.attrsOf` instead. See"
+        + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
+    };
 
     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
     uniq = elemType: mkOptionType rec {
@@ -486,9 +425,15 @@ rec {
           else value
         ) defs;
 
+        freeformType = (evalModules {
+          inherit modules specialArgs;
+          args.name = "‹name›";
+        })._module.freeformType;
+
       in
       mkOptionType rec {
         name = "submodule";
+        description = freeformType.description or name;
         check = x: isAttrs x || isFunction x || path.check x;
         merge = loc: defs:
           (evalModules {
@@ -516,7 +461,12 @@ rec {
             # would be used, and use of `<` and `>` would break the XML document.
             # It shouldn't cause an issue since this is cosmetic for the manual.
             args.name = "‹name›";
-          }).options;
+          }).options // optionalAttrs (freeformType != null) {
+            # Expose the sub options of the freeform type. Note that the option
+            # discovery doesn't care about the attribute name used here, so this
+            # is just to avoid conflicts with potential options from the submodule
+            _freeformOptions = freeformType.getSubOptions prefix;
+          };
         getSubModules = modules;
         substSubModules = m: submoduleWith (attrs // {
           modules = m;
@@ -618,8 +568,9 @@ rec {
     # declarations from the ‘options’ attribute of containing option
     # declaration.
     optionSet = mkOptionType {
-      name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "optionSet";
+      name = "optionSet";
       description = "option set";
+      deprecationMessage = "Use `types.submodule' instead";
     };
     # Augment the given type with an additional type check function.
     addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };