about summary refs log tree commit diff
path: root/nixpkgs/lib
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2021-01-10 07:13:44 +0000
committerAlyssa Ross <hi@alyssa.is>2021-01-12 14:07:16 +0000
commite2698550456abba83c6dcd5d5e5a9990a0b96f8a (patch)
tree79a56f0df3fa55e470d84b4dff6059fbf487ec18 /nixpkgs/lib
parent1cdc42df888dc98c347e03bd942ed9825a55bcb3 (diff)
parent84d74ae9c9cbed73274b8e4e00be14688ffc93fe (diff)
downloadnixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar
nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.gz
nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.bz2
nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.lz
nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.xz
nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.zst
nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.zip
Merge commit '84d74ae9c9cbed73274b8e4e00be14688ffc93fe'
Diffstat (limited to 'nixpkgs/lib')
-rw-r--r--nixpkgs/lib/attrsets.nix1
-rw-r--r--nixpkgs/lib/default.nix4
-rw-r--r--nixpkgs/lib/generators.nix65
-rw-r--r--nixpkgs/lib/licenses.nix80
-rw-r--r--nixpkgs/lib/modules.nix183
-rw-r--r--nixpkgs/lib/options.nix34
-rw-r--r--nixpkgs/lib/sources.nix10
-rw-r--r--nixpkgs/lib/strings.nix33
-rw-r--r--nixpkgs/lib/systems/architectures.nix52
-rw-r--r--nixpkgs/lib/systems/default.nix1
-rw-r--r--nixpkgs/lib/systems/doubles.nix55
-rw-r--r--nixpkgs/lib/systems/examples.nix8
-rw-r--r--nixpkgs/lib/tests/misc.nix108
-rwxr-xr-xnixpkgs/lib/tests/modules.sh74
-rw-r--r--nixpkgs/lib/tests/modules/define-option-dependently.nix2
-rw-r--r--nixpkgs/lib/tests/modules/define-value-string-properties.nix12
-rw-r--r--nixpkgs/lib/tests/modules/freeform-attrsOf.nix3
-rw-r--r--nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix3
-rw-r--r--nixpkgs/lib/tests/modules/freeform-nested.nix7
-rw-r--r--nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix8
-rw-r--r--nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix8
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix12
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix26
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/functions.nix17
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/lists.nix16
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/mk-mods.nix44
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix22
-rw-r--r--nixpkgs/lib/tests/release.nix1
-rw-r--r--nixpkgs/lib/tests/systems.nix4
-rw-r--r--nixpkgs/lib/trivial.nix53
-rw-r--r--nixpkgs/lib/types.nix195
31 files changed, 837 insertions, 304 deletions
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix
index 82bea7af31fc..d91d7a0cd47e 100644
--- a/nixpkgs/lib/attrsets.nix
+++ b/nixpkgs/lib/attrsets.nix
@@ -469,6 +469,7 @@ rec {
   getBin = getOutput "bin";
   getLib = getOutput "lib";
   getDev = getOutput "dev";
+  getMan = getOutput "man";
 
   /* Pick the outputs of packages to place in buildInputs */
   chooseDevOutputs = drvs: builtins.map getDev drvs;
diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix
index e7f59a67abbd..43b9ab5930c4 100644
--- a/nixpkgs/lib/default.nix
+++ b/nixpkgs/lib/default.nix
@@ -67,7 +67,7 @@ let
     inherit (trivial) id const pipe concat or and bitAnd bitOr bitXor
       bitNot boolToString mergeAttrs flip mapNullable inNixShell min max
       importJSON warn info showWarnings nixpkgsVersion version mod compare
-      splitByAndCompare functionArgs setFunctionArgs isFunction;
+      splitByAndCompare functionArgs setFunctionArgs isFunction toHexString toBaseDigits;
     inherit (fixedPoints) fix fix' converge extends composeExtensions
       makeExtensible makeExtensibleWithCustomName;
     inherit (attrsets) attrByPath hasAttrByPath setAttrByPath
@@ -77,7 +77,7 @@ let
       genAttrs isDerivation toDerivation optionalAttrs
       zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
       recursiveUpdate matchAttrs overrideExisting getOutput getBin
-      getLib getDev chooseDevOutputs zipWithNames zip
+      getLib getDev getMan chooseDevOutputs zipWithNames zip
       recurseIntoAttrs dontRecurseIntoAttrs;
     inherit (lists) singleton forEach foldr fold foldl foldl' imap0 imap1
       concatMap flatten remove findSingle findFirst any all count
diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix
index efe6ea6031d3..501a23599f45 100644
--- a/nixpkgs/lib/generators.nix
+++ b/nixpkgs/lib/generators.nix
@@ -48,8 +48,10 @@ rec {
     else if isAttrs    v then err "attrsets" v
     # functions can’t be printed of course
     else if isFunction v then err "functions" v
-    # let’s not talk about floats. There is no sensible `toString` for them.
-    else if isFloat    v then err "floats" v
+    # Floats currently can't be converted to precise strings,
+    # condition warning on nix version once this isn't a problem anymore
+    # See https://github.com/NixOS/nix/pull/3480
+    else if isFloat    v then libStr.floatToString v
     else err "this value is" (toString v);
 
 
@@ -201,40 +203,59 @@ rec {
     /* If this option is true, attrsets like { __pretty = fn; val = …; }
        will use fn to convert val to a pretty printed representation.
        (This means fn is type Val -> String.) */
-    allowPrettyValues ? false
-  }@args: v: with builtins;
+    allowPrettyValues ? false,
+    /* If this option is true, the output is indented with newlines for attribute sets and lists */
+    multiline ? true
+  }@args: let
+    go = indent: v: with builtins;
     let     isPath   = v: typeOf v == "path";
+            introSpace = if multiline then "\n${indent}  " else " ";
+            outroSpace = if multiline then "\n${indent}" else " ";
     in if   isInt      v then toString v
     else if isFloat    v then "~${toString v}"
-    else if isString   v then ''"${libStr.escape [''"''] v}"''
+    else if isString   v then
+      let
+        # Separate a string into its lines
+        newlineSplits = filter (v: ! isList v) (builtins.split "\n" v);
+        # For a '' string terminated by a \n, which happens when the closing '' is on a new line
+        multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''";
+        # For a '' string not terminated by a \n, which happens when the closing '' is not on a new line
+        multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''";
+        # For single lines, replace all newlines with their escaped representation
+        singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\"";
+      in if multiline && length newlineSplits > 1 then
+        if lib.last newlineSplits == "" then multilineResult else multilineResult'
+      else singlelineResult
     else if true  ==   v then "true"
     else if false ==   v then "false"
     else if null  ==   v then "null"
     else if isPath     v then toString v
-    else if isList     v then "[ "
-        + libStr.concatMapStringsSep " " (toPretty args) v
-      + " ]"
+    else if isList     v then
+      if v == [] then "[ ]"
+      else "[" + introSpace
+        + libStr.concatMapStringsSep introSpace (go (indent + "  ")) v
+        + outroSpace + "]"
+    else if isFunction v then
+      let fna = lib.functionArgs v;
+          showFnas = concatStringsSep ", " (libAttr.mapAttrsToList
+                       (name: hasDefVal: if hasDefVal then name + "?" else name)
+                       fna);
+      in if fna == {}    then "<function>"
+                         else "<function, args: {${showFnas}}>"
     else if isAttrs    v then
       # apply pretty values if allowed
       if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
          then v.__pretty v.val
-      # TODO: there is probably a better representation?
+      else if v == {} then "{ }"
       else if v ? type && v.type == "derivation" then
-        "<δ:${v.name}>"
-        # "<δ:${concatStringsSep "," (builtins.attrNames v)}>"
-      else "{ "
-          + libStr.concatStringsSep " " (libAttr.mapAttrsToList
+        "<derivation ${v.drvPath}>"
+      else "{" + introSpace
+          + libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
               (name: value:
-                "${toPretty args name} = ${toPretty args value};") v)
-        + " }"
-    else if isFunction v then
-      let fna = lib.functionArgs v;
-          showFnas = concatStringsSep "," (libAttr.mapAttrsToList
-                       (name: hasDefVal: if hasDefVal then "(${name})" else name)
-                       fna);
-      in if fna == {}    then "<λ>"
-                         else "<λ:{${showFnas}}>"
+                "${libStr.escapeNixIdentifier name} = ${go (indent + "  ") value};") v)
+        + outroSpace + "}"
     else abort "generators.toPretty: should never happen (v = ${v})";
+  in go "";
 
   # PLIST handling
   toPlist = {}: v: let
diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix
index b799a6ae8a43..a704a6884c7d 100644
--- a/nixpkgs/lib/licenses.nix
+++ b/nixpkgs/lib/licenses.nix
@@ -28,7 +28,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "Academic Free License v3.0";
   };
 
-  agpl3 = spdx {
+  agpl3Only = spdx {
     spdxId = "AGPL-3.0-only";
     fullName = "GNU Affero General Public License v3.0 only";
   };
@@ -85,6 +85,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = ''Beerware License'';
   };
 
+  blueOak100 = spdx {
+    spdxId = "BlueOak-1.0.0";
+    fullName = "Blue Oak Model License 1.0.0";
+  };
+
   bsd0 = spdx {
     spdxId = "0BSD";
     fullName = "BSD Zero Clause License";
@@ -95,6 +100,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = ''BSD 2-clause "Simplified" License'';
   };
 
+  bsd2Patent = spdx {
+    spdxId = "BSD-2-Clause-Patent";
+    fullName = ''BSD-2-Clause Plus Patent License'';
+  };
+
   bsd3 = spdx {
     spdxId = "BSD-3-Clause";
     fullName = ''BSD 3-clause "New" or "Revised" License'';
@@ -105,6 +115,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = ''BSD 4-clause "Original" or "Old" License'';
   };
 
+  bsdProtection = spdx {
+    spdxId = "BSD-Protection";
+    fullName = "BSD Protection License";
+  };
+
   bsl11 = {
     fullName = "Business Source License 1.1";
     url = "https://mariadb.com/bsl11";
@@ -276,12 +291,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "European Union Public License 1.2";
   };
 
-  fdl11 = spdx {
+  fdl11Only = spdx {
     spdxId = "GFDL-1.1-only";
     fullName = "GNU Free Documentation License v1.1 only";
   };
 
-  fdl12 = spdx {
+  fdl12Only = spdx {
     spdxId = "GFDL-1.2-only";
     fullName = "GNU Free Documentation License v1.2 only";
   };
@@ -291,7 +306,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU Free Documentation License v1.2 or later";
   };
 
-  fdl13 = spdx {
+  fdl13Only = spdx {
     spdxId = "GFDL-1.3-only";
     fullName = "GNU Free Documentation License v1.3 only";
   };
@@ -322,7 +337,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     free = false;
   };
 
-  gpl1 = spdx {
+  gpl1Only = spdx {
     spdxId = "GPL-1.0-only";
     fullName = "GNU General Public License v1.0 only";
   };
@@ -332,7 +347,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU General Public License v1.0 or later";
   };
 
-  gpl2 = spdx {
+  gpl2Only = spdx {
     spdxId = "GPL-2.0-only";
     fullName = "GNU General Public License v2.0 only";
   };
@@ -357,7 +372,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU General Public License v2.0 or later";
   };
 
-  gpl3 = spdx {
+  gpl3Only = spdx {
     spdxId = "GPL-3.0-only";
     fullName = "GNU General Public License v3.0 only";
   };
@@ -421,18 +436,19 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
   };
 
   # Proprietary binaries; free to redistribute without modification.
+  databricks = {
+    fullName = "Databricks Proprietary License";
+    url = "https://pypi.org/project/databricks-connect";
+    free = false;
+  };
+
   issl = {
     fullName = "Intel Simplified Software License";
     url = "https://software.intel.com/en-us/license/intel-simplified-software-license";
     free = false;
   };
 
-  jasper = spdx {
-    spdxId = "JasPer-2.0";
-    fullName = "JasPer License";
-  };
-
-  lgpl2 = spdx {
+  lgpl2Only = spdx {
     spdxId = "LGPL-2.0-only";
     fullName = "GNU Library General Public License v2 only";
   };
@@ -442,7 +458,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU Library General Public License v2 or later";
   };
 
-  lgpl21 = spdx {
+  lgpl21Only = spdx {
     spdxId = "LGPL-2.1-only";
     fullName = "GNU Lesser General Public License v2.1 only";
   };
@@ -452,7 +468,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU Lesser General Public License v2.1 or later";
   };
 
-  lgpl3 = spdx {
+  lgpl3Only = spdx {
     spdxId = "LGPL-3.0-only";
     fullName = "GNU Lesser General Public License v3.0 only";
   };
@@ -462,6 +478,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU Lesser General Public License v3.0 or later";
   };
 
+  lgpllr = spdx {
+    spdxId = "LGPLLR";
+    fullName = "Lesser General Public License For Linguistic Resources";
+  };
+
   libpng = spdx {
     spdxId = "Libpng";
     fullName = "libpng License";
@@ -482,6 +503,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     url = "https://opensource.franz.com/preamble.html";
   };
 
+  llvm-exception = spdx {
+    spdxId = "LLVM-exception";
+    fullName = "LLVM Exception"; # LLVM exceptions to the Apache 2.0 License
+  };
+
   lppl12 = spdx {
     spdxId = "LPPL-1.2";
     fullName = "LaTeX Project Public License v1.2";
@@ -545,6 +571,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "Non-Profit Open Software License 3.0";
   };
 
+  obsidian = {
+    fullName = "Obsidian End User Agreement";
+    url = "https://obsidian.md/eula";
+    free = false;
+  };
+
   ocamlpro_nc = {
     fullName = "OCamlPro Non Commercial license version 1";
     url = "https://alt-ergo.ocamlpro.com/http/alt-ergo-2.2.0/OCamlPro-Non-Commercial-License.pdf";
@@ -612,6 +644,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     url = "https://enterprise.dejacode.com/licenses/public/purdue-bsd";
   };
 
+  prosperity30 = {
+    fullName = "Prosperity-3.0.0";
+    free = false;
+    url = "https://prosperitylicense.com/versions/3.0.0.html";
+  };
+
   qhull = spdx {
     spdxId = "Qhull";
     fullName = "Qhull License";
@@ -761,4 +799,16 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     spdxId = "ZPL-2.1";
     fullName = "Zope Public License 2.1";
   };
+} // {
+  # TODO: remove legacy aliases
+  agpl3 = lib.licenses.agpl3Only;
+  fdl11 = lib.licenses.fdl11Only;
+  fdl12 = lib.licenses.fdl12Only;
+  fdl13 = lib.licenses.fdl13Only;
+  gpl1 = lib.licenses.gpl1Only;
+  gpl2 = lib.licenses.gpl2Only;
+  gpl3 = lib.licenses.gpl3Only;
+  lgpl2 = lib.licenses.lgpl2Only;
+  lgpl21 = lib.licenses.lgpl21Only;
+  lgpl3 = lib.licenses.lgpl3Only;
 }
diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix
index c18fec66c705..02a669df6593 100644
--- a/nixpkgs/lib/modules.nix
+++ b/nixpkgs/lib/modules.nix
@@ -58,6 +58,23 @@ rec {
             default = check;
             description = "Whether to check whether all option definitions have matching declarations.";
           };
+
+          _module.freeformType = mkOption {
+            # Disallow merging for now, but could be implemented nicely with a `types.optionType`
+            type = types.nullOr (types.uniq types.attrs);
+            internal = true;
+            default = null;
+            description = ''
+              If set, merge all definitions that don't have an associated option
+              together using this type. The result then gets combined with the
+              values of all declared options to produce the final <literal>
+              config</literal> value.
+
+              If this is <literal>null</literal>, definitions without an option
+              will throw an error unless <option>_module.check</option> is
+              turned off.
+            '';
+          };
         };
 
         config = {
@@ -65,35 +82,55 @@ rec {
         };
       };
 
-      collected = collectModules
-        (specialArgs.modulesPath or "")
-        (modules ++ [ internalModule ])
-        ({ inherit config options lib; } // specialArgs);
-
-      options = mergeModules prefix (reverseList collected);
-
-      # Traverse options and extract the option values into the final
-      # config set.  At the same time, check whether all option
-      # definitions have matching declarations.
-      # !!! _module.check's value can't depend on any other config values
-      # without an infinite recursion. One way around this is to make the
-      # 'config' passed around to the modules be unconditionally unchecked,
-      # and only do the check in 'result'.
-      config = yieldConfig prefix options;
-      yieldConfig = prefix: set:
-        let res = removeAttrs (mapAttrs (n: v:
-          if isOption v then v.value
-          else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"];
-        in
-        if options._module.check.value && set ? _definedNames then
-          foldl' (res: m:
-            foldl' (res: name:
-              if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.")
-              res m.names)
-            res set._definedNames
-        else
-          res;
-      result = {
+      merged =
+        let collected = collectModules
+          (specialArgs.modulesPath or "")
+          (modules ++ [ internalModule ])
+          ({ inherit lib options config; } // specialArgs);
+        in mergeModules prefix (reverseList collected);
+
+      options = merged.matchedOptions;
+
+      config =
+        let
+
+          # For definitions that have an associated option
+          declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options;
+
+          # If freeformType is set, this is for definitions that don't have an associated option
+          freeformConfig =
+            let
+              defs = map (def: {
+                file = def.file;
+                value = setAttrByPath def.prefix def.value;
+              }) merged.unmatchedDefns;
+            in if defs == [] then {}
+            else declaredConfig._module.freeformType.merge prefix defs;
+
+        in if declaredConfig._module.freeformType == null then declaredConfig
+          # Because all definitions that had an associated option ended in
+          # declaredConfig, freeformConfig can only contain the non-option
+          # paths, meaning recursiveUpdate will never override any value
+          else recursiveUpdate freeformConfig declaredConfig;
+
+      checkUnmatched =
+        if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
+          let
+            firstDef = head merged.unmatchedDefns;
+            baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}";
+          in
+            if attrNames options == [ "_module" ]
+              then throw ''
+                ${baseMsg}
+
+                However there are no options defined in `${showOption prefix}'. Are you sure you've
+                declared your options properly? This can happen if you e.g. declared your options in `types.submodule'
+                under `config' rather than `options'.
+              ''
+            else throw baseMsg
+        else null;
+
+      result = builtins.seq checkUnmatched {
         inherit options;
         config = removeAttrs config [ "_module" ];
         inherit (config) _module;
@@ -174,12 +211,16 @@ rec {
   /* Massage a module into canonical form, that is, a set consisting
      of ‘options’, ‘config’ and ‘imports’ attributes. */
   unifyModuleSyntax = file: key: m:
-    let addMeta = config: if m ? meta
-      then mkMerge [ config { meta = m.meta; } ]
-      else config;
+    let
+      addMeta = config: if m ? meta
+        then mkMerge [ config { meta = m.meta; } ]
+        else config;
+      addFreeformType = config: if m ? freeformType
+        then mkMerge [ config { _module.freeformType = m.freeformType; } ]
+        else config;
     in
     if m ? config || m ? options then
-      let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in
+      let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in
       if badAttrs != {} then
         throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
       else
@@ -188,7 +229,7 @@ rec {
           disabledModules = m.disabledModules or [];
           imports = m.imports or [];
           options = m.options or {};
-          config = addMeta (m.config or {});
+          config = addFreeformType (addMeta (m.config or {}));
         }
     else
       { _file = m._file or file;
@@ -196,7 +237,7 @@ rec {
         disabledModules = m.disabledModules or [];
         imports = m.require or [] ++ m.imports or [];
         options = {};
-        config = addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]);
+        config = addFreeformType (addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"]));
       };
 
   applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
@@ -233,7 +274,23 @@ rec {
      declarations in all modules, combining them into a single set.
      At the same time, for each option declaration, it will merge the
      corresponding option definitions in all machines, returning them
-     in the ‘value’ attribute of each option. */
+     in the ‘value’ attribute of each option.
+
+     This returns a set like
+       {
+         # A recursive set of options along with their final values
+         matchedOptions = {
+           foo = { _type = "option"; value = "option value of foo"; ... };
+           bar.baz = { _type = "option"; value = "option value of bar.baz"; ... };
+           ...
+         };
+         # A list of definitions that weren't matched by any option
+         unmatchedDefns = [
+           { file = "file.nix"; prefix = [ "qux" ]; value = "qux"; }
+           ...
+         ];
+       }
+  */
   mergeModules = prefix: modules:
     mergeModules' prefix modules
       (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);
@@ -280,9 +337,9 @@ rec {
       defnsByName' = byName "config" (module: value:
           [{ inherit (module) file; inherit value; }]
         ) configs;
-    in
-    (flip mapAttrs declsByName (name: decls:
-      # We're descending into attribute ‘name’.
+
+      resultsByName = flip mapAttrs declsByName (name: decls:
+        # We're descending into attribute ‘name’.
         let
           loc = prefix ++ [name];
           defns = defnsByName.${name} or [];
@@ -291,7 +348,10 @@ rec {
         in
           if nrOptions == length decls then
             let opt = fixupOptionType loc (mergeOptionDecls loc decls);
-            in evalOptionValue loc opt defns'
+            in {
+              matchedOptions = evalOptionValue loc opt defns';
+              unmatchedDefns = [];
+            }
           else if nrOptions != 0 then
             let
               firstOption = findFirst (m: isOption m.options) "" decls;
@@ -299,9 +359,27 @@ rec {
             in
               throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'."
           else
-            mergeModules' loc decls defns
-      ))
-    // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
+            mergeModules' loc decls defns);
+
+      matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
+
+      # an attrset 'name' => list of unmatched definitions for 'name'
+      unmatchedDefnsByName =
+        # Propagate all unmatched definitions from nested option sets
+        mapAttrs (n: v: v.unmatchedDefns) resultsByName
+        # Plus the definitions for the current prefix that don't have a matching option
+        // removeAttrs defnsByName' (attrNames matchedOptions);
+    in {
+      inherit matchedOptions;
+
+      # Transforms unmatchedDefnsByName into a list of definitions
+      unmatchedDefns = concatLists (mapAttrsToList (name: defs:
+        map (def: def // {
+          # Set this so we know when the definition first left unmatched territory
+          prefix = [name] ++ (def.prefix or []);
+        }) defs
+      ) unmatchedDefnsByName);
+    };
 
   /* Merge multiple option declarations into a single declaration.  In
      general, there should be only one declaration of each option.
@@ -371,7 +449,13 @@ rec {
       # Handle properties, check types, and merge everything together.
       res =
         if opt.readOnly or false && length defs' > 1 then
-          throw "The option `${showOption loc}' is read-only, but it's set multiple times."
+          let
+            # For a better error message, evaluate all readOnly definitions as
+            # if they were the only definition.
+            separateDefs = map (def: def // {
+              value = (mergeDefinitions loc opt.type [ def ]).mergedValue;
+            }) defs';
+          in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
         else
           mergeDefinitions loc opt.type defs';
 
@@ -379,7 +463,11 @@ rec {
       # yield a value computed from the definitions
       value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
 
-    in opt //
+      warnDeprecation =
+        if opt.type.deprecationMessage == null then id
+        else warn "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}";
+
+    in warnDeprecation opt //
       { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
         inherit (res.defsFinal') highestPrio;
         definitions = map (def: def.value) res.defsFinal;
@@ -415,8 +503,8 @@ rec {
     mergedValue =
       if isDefined then
         if all (def: type.check def.value) defsFinal then type.merge loc defsFinal
-        else let firstInvalid = findFirst (def: ! type.check def.value) null defsFinal;
-        in throw "The option value `${showOption loc}' in `${firstInvalid.file}' is not of type `${type.description}'."
+        else let allInvalid = filter (def: ! type.check def.value) defsFinal;
+        in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
       else
         # (nixos-option detects this specific error message and gives it special
         # handling.  If changed here, please change it there too.)
@@ -535,7 +623,6 @@ rec {
         if tp.name == "option set" || tp.name == "submodule" then
           throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
         else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
-        else if optionSetIn "loaOf"   then types.loaOf   (types.submodule options)
         else if optionSetIn "listOf"  then types.listOf  (types.submodule options)
         else if optionSetIn "nullOr"  then types.nullOr  (types.submodule options)
         else tp;
diff --git a/nixpkgs/lib/options.nix b/nixpkgs/lib/options.nix
index 38f4f1329f21..5b7482c80937 100644
--- a/nixpkgs/lib/options.nix
+++ b/nixpkgs/lib/options.nix
@@ -96,22 +96,26 @@ rec {
     else if all isBool list then foldl' lib.or false list
     else if all isString list then lib.concatStrings list
     else if all isInt list && all (x: x == head list) list then head list
-    else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
+    else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
 
   mergeOneOption = loc: defs:
     if defs == [] then abort "This case should never happen."
     else if length defs != 1 then
-      throw "The unique option `${showOption loc}' is defined multiple times, in:\n - ${concatStringsSep "\n - " (getFiles defs)}."
+      throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}"
     else (head defs).value;
 
   /* "Merge" option definitions by checking that they all have the same value. */
   mergeEqualOption = loc: defs:
     if defs == [] then abort "This case should never happen."
-    else foldl' (val: def:
-      if def.value != val then
-        throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
+    # Return early if we only have one element
+    # This also makes it work for functions, because the foldl' below would try
+    # to compare the first element with itself, which is false for functions
+    else if length defs == 1 then (elemAt defs 0).value
+    else (foldl' (first: def:
+      if def.value != first.value then
+        throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}"
       else
-        val) (head defs).value defs;
+        first) (head defs) defs).value;
 
   /* Extracts values of all "value" keys of the given list.
 
@@ -209,6 +213,24 @@ rec {
          else escaped;
     in (concatStringsSep ".") (map escapeOptionPart parts);
   showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
+
+  showDefs = defs: concatMapStrings (def:
+    let
+      # Pretty print the value for display, if successful
+      prettyEval = builtins.tryEval (lib.generators.toPretty {} def.value);
+      # Split it into its lines
+      lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value);
+      # Only display the first 5 lines, and indent them for better visibility
+      value = concatStringsSep "\n    " (take 5 lines ++ optional (length lines > 5) "...");
+      result =
+        # Don't print any value if evaluating the value strictly fails
+        if ! prettyEval.success then ""
+        # Put it on a new line if it consists of multiple
+        else if length lines > 1 then ":\n    " + value
+        else ": " + value;
+    in "\n- In `${def.file}'${result}"
+  ) defs;
+
   unknownModule = "<unknown-file>";
 
 }
diff --git a/nixpkgs/lib/sources.nix b/nixpkgs/lib/sources.nix
index ed9bce485300..776fcc32052b 100644
--- a/nixpkgs/lib/sources.nix
+++ b/nixpkgs/lib/sources.nix
@@ -145,10 +145,14 @@ rec {
            # packed-refs file, so we have to grep through it:
            then
              let fileContent = readFile packedRefsName;
-                 matchRef    = match (".*\n([^\n ]*) " + file + "\n.*") fileContent;
-             in if  matchRef == null
+                 matchRef = builtins.match "([a-z0-9]+) ${file}";
+                 isRef = s: builtins.isString s && (matchRef s) != null;
+                 # there is a bug in libstdc++ leading to stackoverflow for long strings:
+                 # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795
+                 refs = builtins.filter isRef (builtins.split "\n" fileContent);
+             in if refs == []
                 then throw ("Could not find " + file + " in " + packedRefsName)
-                else lib.head matchRef
+                else lib.head (matchRef (lib.head refs))
 
            else throw ("Not a .git directory: " + path);
     in readCommitFromFile "HEAD";
diff --git a/nixpkgs/lib/strings.nix b/nixpkgs/lib/strings.nix
index 74e3eaa0722d..9fa9f023561e 100644
--- a/nixpkgs/lib/strings.nix
+++ b/nixpkgs/lib/strings.nix
@@ -612,6 +612,22 @@ rec {
   */
   fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
 
+  /* Convert a float to a string, but emit a warning when precision is lost
+     during the conversion
+
+     Example:
+       floatToString 0.000001
+       => "0.000001"
+       floatToString 0.0000001
+       => trace: warning: Imprecise conversion from float to string 0.000000
+          "0.000000"
+  */
+  floatToString = float: let
+    result = toString float;
+    precise = float == builtins.fromJSON result;
+  in if precise then result
+    else lib.warn "Imprecise conversion from float to string ${result}" result;
+
   /* Check whether a value can be coerced to a string */
   isCoercibleToString = x:
     builtins.elem (builtins.typeOf x) [ "path" "string" "null" "int" "float" "bool" ] ||
@@ -673,14 +689,15 @@ rec {
             "/prefix/nix-profiles-library-paths.patch"
             "/prefix/compose-search-path.patch" ]
   */
-  readPathsFromFile = rootPath: file:
-    let
-      lines = lib.splitString "\n" (builtins.readFile file);
-      removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
-      relativePaths = removeComments lines;
-      absolutePaths = builtins.map (path: rootPath + "/${path}") relativePaths;
-    in
-      absolutePaths;
+  readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
+    (rootPath: file:
+      let
+        lines = lib.splitString "\n" (builtins.readFile file);
+        removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
+        relativePaths = removeComments lines;
+        absolutePaths = builtins.map (path: rootPath + "/${path}") relativePaths;
+      in
+        absolutePaths);
 
   /* Read the contents of a file removing the trailing \n
 
diff --git a/nixpkgs/lib/systems/architectures.nix b/nixpkgs/lib/systems/architectures.nix
index 287f5be03c45..bfecaec1ae88 100644
--- a/nixpkgs/lib/systems/architectures.nix
+++ b/nixpkgs/lib/systems/architectures.nix
@@ -35,21 +35,21 @@ rec {
     # x86_64 Intel
     default        = [ ];
     westmere       = [ ];
-    sandybridge    = [ "westmere" ];
-    ivybridge      = [ "westmere" "sandybridge" ];
-    haswell        = [ "westmere" "sandybridge" "ivybridge" ];
-    broadwell      = [ "westmere" "sandybridge" "ivybridge" "haswell" ];
-    skylake        = [ "westmere" "sandybridge" "ivybridge" "haswell" "broadwell" ];
-    skylake-avx512 = [ "westmere" "sandybridge" "ivybridge" "haswell" "broadwell" "skylake" ];
+    sandybridge    = [ "westmere"    ] ++ inferiors.westmere;
+    ivybridge      = [ "sandybridge" ] ++ inferiors.sandybridge;
+    haswell        = [ "ivybridge"   ] ++ inferiors.ivybridge;
+    broadwell      = [ "haswell"     ] ++ inferiors.haswell;
+    skylake        = [ "broadwell"   ] ++ inferiors.broadwell;
+    skylake-avx512 = [ "skylake"     ] ++ inferiors.skylake;
     # x86_64 AMD
     btver1         = [ ];
-    btver2         = [ ];
-    bdver1         = [ ];
-    bdver2         = [ ];
-    bdver3         = [ ];
-    bdver4         = [ ];
-    znver1         = [ ];
-    znver2         = [ ];
+    btver2         = [ ]; # TODO: fill this (need testing)
+    bdver1         = [ ]; # TODO: fill this (need testing)
+    bdver2         = [ ]; # TODO: fill this (need testing)
+    bdver3         = [ ]; # TODO: fill this (need testing)
+    bdver4         = [ ]; # TODO: fill this (need testing)
+    znver1         = [ ]; # TODO: fill this (need testing)
+    znver2         = [ ]; # TODO: fill this (need testing)
     # other
     armv5te        = [ ];
     armv6          = [ ];
@@ -59,17 +59,19 @@ rec {
     loongson2f     = [ ];
   };
 
-  predicates = {
-    sse3Support    = x: builtins.elem "sse3"   features.${x};
-    ssse3Support   = x: builtins.elem "ssse3"  features.${x};
-    sse4_1Support  = x: builtins.elem "sse4_1" features.${x};
-    sse4_2Support  = x: builtins.elem "sse4_2" features.${x};
-    sse4_aSupport  = x: builtins.elem "sse4a"  features.${x};
-    avxSupport     = x: builtins.elem "avx"    features.${x};
-    avx2Support    = x: builtins.elem "avx2"   features.${x};
-    avx512Support  = x: builtins.elem "avx512" features.${x};
-    aesSupport     = x: builtins.elem "aes"    features.${x};
-    fmaSupport     = x: builtins.elem "fma"    features.${x};
-    fma4Support    = x: builtins.elem "fma4"   features.${x};
+  predicates = let
+    featureSupport = feature: x: builtins.elem feature features.${x} or [];
+  in {
+    sse3Support    = featureSupport "sse3";
+    ssse3Support   = featureSupport "ssse3";
+    sse4_1Support  = featureSupport "sse4_1";
+    sse4_2Support  = featureSupport "sse4_2";
+    sse4_aSupport  = featureSupport "sse4a";
+    avxSupport     = featureSupport "avx";
+    avx2Support    = featureSupport "avx2";
+    avx512Support  = featureSupport "avx512";
+    aesSupport     = featureSupport "aes";
+    fmaSupport     = featureSupport "fma";
+    fma4Support    = featureSupport "fma4";
   };
 }
diff --git a/nixpkgs/lib/systems/default.nix b/nixpkgs/lib/systems/default.nix
index 02d58592b325..9939743157e7 100644
--- a/nixpkgs/lib/systems/default.nix
+++ b/nixpkgs/lib/systems/default.nix
@@ -77,6 +77,7 @@ rec {
          # uname -r
          release = null;
       };
+      isStatic = final.isWasm || final.isRedox;
 
       kernelArch =
         if final.isAarch32 then "arm"
diff --git a/nixpkgs/lib/systems/doubles.nix b/nixpkgs/lib/systems/doubles.nix
index c0e78595d85d..517a7296afd2 100644
--- a/nixpkgs/lib/systems/doubles.nix
+++ b/nixpkgs/lib/systems/doubles.nix
@@ -38,7 +38,7 @@ let
 
     "js-ghcjs"
 
-    "aarch64-genode" "x86_64-genode"
+    "aarch64-genode" "i686-genode" "x86_64-genode"
   ];
 
   allParsed = map parse.mkSystemFromString all;
@@ -50,32 +50,35 @@ in {
 
   none = [];
 
-  arm     = filterDoubles predicates.isAarch32;
-  aarch64 = filterDoubles predicates.isAarch64;
-  x86     = filterDoubles predicates.isx86;
-  i686    = filterDoubles predicates.isi686;
-  x86_64  = filterDoubles predicates.isx86_64;
-  mips    = filterDoubles predicates.isMips;
-  riscv   = filterDoubles predicates.isRiscV;
-  vc4     = filterDoubles predicates.isVc4;
-  js      = filterDoubles predicates.isJavaScript;
-
-  cygwin  = filterDoubles predicates.isCygwin;
-  darwin  = filterDoubles predicates.isDarwin;
-  freebsd = filterDoubles predicates.isFreeBSD;
+  arm           = filterDoubles predicates.isAarch32;
+  aarch64       = filterDoubles predicates.isAarch64;
+  x86           = filterDoubles predicates.isx86;
+  i686          = filterDoubles predicates.isi686;
+  x86_64        = filterDoubles predicates.isx86_64;
+  mips          = filterDoubles predicates.isMips;
+  riscv         = filterDoubles predicates.isRiscV;
+  vc4           = filterDoubles predicates.isVc4;
+  js            = filterDoubles predicates.isJavaScript;
+
+  bigEndian     = filterDoubles predicates.isBigEndian;
+  littleEndian  = filterDoubles predicates.isLittleEndian;
+
+  cygwin        = filterDoubles predicates.isCygwin;
+  darwin        = filterDoubles predicates.isDarwin;
+  freebsd       = filterDoubles predicates.isFreeBSD;
   # Should be better, but MinGW is unclear.
-  gnu     = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; });
-  illumos = filterDoubles predicates.isSunOS;
-  linux   = filterDoubles predicates.isLinux;
-  netbsd  = filterDoubles predicates.isNetBSD;
-  openbsd = filterDoubles predicates.isOpenBSD;
-  unix    = filterDoubles predicates.isUnix;
-  wasi    = filterDoubles predicates.isWasi;
-  redox   = filterDoubles predicates.isRedox;
-  windows = filterDoubles predicates.isWindows;
-  genode  = filterDoubles predicates.isGenode;
-
-  embedded = filterDoubles predicates.isNone;
+  gnu           = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; });
+  illumos       = filterDoubles predicates.isSunOS;
+  linux         = filterDoubles predicates.isLinux;
+  netbsd        = filterDoubles predicates.isNetBSD;
+  openbsd       = filterDoubles predicates.isOpenBSD;
+  unix          = filterDoubles predicates.isUnix;
+  wasi          = filterDoubles predicates.isWasi;
+  redox         = filterDoubles predicates.isRedox;
+  windows       = filterDoubles predicates.isWindows;
+  genode        = filterDoubles predicates.isGenode;
+
+  embedded      = filterDoubles predicates.isNone;
 
   mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "armv7a-linux" "aarch64-linux" "powerpc64le-linux"];
 }
diff --git a/nixpkgs/lib/systems/examples.nix b/nixpkgs/lib/systems/examples.nix
index ca562d2e4565..5403f73405c2 100644
--- a/nixpkgs/lib/systems/examples.nix
+++ b/nixpkgs/lib/systems/examples.nix
@@ -46,16 +46,16 @@ rec {
 
   armv7a-android-prebuilt = {
     config = "armv7a-unknown-linux-androideabi";
-    sdkVer = "24";
-    ndkVer = "18b";
+    sdkVer = "29";
+    ndkVer = "21";
     platform = platforms.armv7a-android;
     useAndroidPrebuilt = true;
   };
 
   aarch64-android-prebuilt = {
     config = "aarch64-unknown-linux-android";
-    sdkVer = "24";
-    ndkVer = "18b";
+    sdkVer = "29";
+    ndkVer = "21";
     platform = platforms.aarch64-multiplatform;
     useAndroidPrebuilt = true;
   };
diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix
index 36ddd186d7b7..3a6db53c276d 100644
--- a/nixpkgs/lib/tests/misc.nix
+++ b/nixpkgs/lib/tests/misc.nix
@@ -102,6 +102,16 @@ runTests {
     expected = 9;
   };
 
+  testToHexString = {
+    expr = toHexString 250;
+    expected = "FA";
+  };
+
+  testToBaseDigits = {
+    expr = toBaseDigits 2 6;
+    expected = [ 1 1 0 ];
+  };
+
 # STRINGS
 
   testConcatMapStrings = {
@@ -435,32 +445,90 @@ runTests {
       expected = builtins.toJSON val;
   };
 
-  testToPretty = {
-    expr = mapAttrs (const (generators.toPretty {})) rec {
+  testToPretty =
+    let
+      deriv = derivation { name = "test"; builder = "/bin/sh"; system = builtins.currentSystem; };
+    in {
+    expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
       int = 42;
       float = 0.1337;
       bool = true;
+      emptystring = "";
       string = ''fno"rd'';
+      newlinestring = "\n";
       path = /. + "/foo";
       null_ = null;
       function = x: x;
       functionArgs = { arg ? 4, foo }: arg;
       list = [ 3 4 function [ false ] ];
+      emptylist = [];
       attrs = { foo = null; "foo bar" = "baz"; };
-      drv = derivation { name = "test"; system = builtins.currentSystem; };
+      emptyattrs = {};
+      drv = deriv;
     };
     expected = rec {
       int = "42";
       float = "~0.133700";
       bool = "true";
+      emptystring = ''""'';
       string = ''"fno\"rd"'';
+      newlinestring = "\"\\n\"";
       path = "/foo";
       null_ = "null";
-      function = "<λ>";
-      functionArgs = "<λ:{(arg),foo}>";
+      function = "<function>";
+      functionArgs = "<function, args: {arg?, foo}>";
       list = "[ 3 4 ${function} [ false ] ]";
-      attrs = "{ \"foo\" = null; \"foo bar\" = \"baz\"; }";
-      drv = "<δ:test>";
+      emptylist = "[ ]";
+      attrs = "{ foo = null; \"foo bar\" = \"baz\"; }";
+      emptyattrs = "{ }";
+      drv = "<derivation ${deriv.drvPath}>";
+    };
+  };
+
+  testToPrettyMultiline = {
+    expr = mapAttrs (const (generators.toPretty { })) rec {
+      list = [ 3 4 [ false ] ];
+      attrs = { foo = null; bar.foo = "baz"; };
+      newlinestring = "\n";
+      multilinestring = ''
+        hello
+        there
+        test
+      '';
+      multilinestring' = ''
+        hello
+        there
+        test'';
+    };
+    expected = rec {
+      list = ''
+        [
+          3
+          4
+          [
+            false
+          ]
+        ]'';
+      attrs = ''
+        {
+          bar = {
+            foo = "baz";
+          };
+          foo = null;
+        }'';
+      newlinestring = "''\n  \n''";
+      multilinestring = ''
+        '''
+          hello
+          there
+          test
+        ''''';
+      multilinestring' = ''
+        '''
+          hello
+          there
+          test''''';
+
     };
   };
 
@@ -532,4 +600,30 @@ runTests {
     name = "";
     expected = "unknown";
   };
+
+  testFreeformOptions = {
+    expr =
+      let
+        submodule = { lib, ... }: {
+          freeformType = lib.types.attrsOf (lib.types.submodule {
+            options.bar = lib.mkOption {};
+          });
+          options.bar = lib.mkOption {};
+        };
+
+        module = { lib, ... }: {
+          options.foo = lib.mkOption {
+            type = lib.types.submodule submodule;
+          };
+        };
+
+        options = (evalModules {
+          modules = [ module ];
+        }).options;
+
+        locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
+      in map (o: o.loc) locs;
+    expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
+  };
+
 }
diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh
index 6258244457aa..309c5311361c 100755
--- a/nixpkgs/lib/tests/modules.sh
+++ b/nixpkgs/lib/tests/modules.sh
@@ -49,7 +49,7 @@ checkConfigError() {
         reportFailure "$@"
         return 1
     else
-        if echo "$err" | grep --silent "$errorContains" ; then
+        if echo "$err" | grep -zP --silent "$errorContains" ; then
             pass=$((pass + 1))
             return 0;
         else
@@ -62,17 +62,17 @@ checkConfigError() {
 
 # Check boolean option.
 checkConfigOutput "false" config.enable ./declare-enable.nix
-checkConfigError 'The option .* defined in .* does not exist.' config.enable ./define-enable.nix
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./define-enable.nix
 
 # Check integer types.
 # unsigned
 checkConfigOutput "42" config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
-checkConfigError 'The option value .* in .* is not of type.*unsigned integer.*' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
+checkConfigError 'A definition for option .* is not of type.*unsigned integer.*. Definition values:\n- In .*: -23' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
 # positive
-checkConfigError 'The option value .* in .* is not of type.*positive integer.*' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
+checkConfigError 'A definition for option .* is not of type.*positive integer.*. Definition values:\n- In .*: 0' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
 # between
 checkConfigOutput "42" config.value ./declare-int-between-value.nix ./define-value-int-positive.nix
-checkConfigError 'The option value .* in .* is not of type.*between.*-21 and 43.*inclusive.*' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
+checkConfigError 'A definition for option .* is not of type.*between.*-21 and 43.*inclusive.*. Definition values:\n- In .*: -23' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
 
 # Check either types
 # types.either
@@ -125,7 +125,7 @@ checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-foo-enable
 set -- config.enable ./define-enable.nix ./declare-enable.nix
 checkConfigOutput "true" "$@"
 checkConfigOutput "false" "$@" ./disable-define-enable.nix
-checkConfigError "The option .*enable.* defined in .* does not exist" "$@" ./disable-declare-enable.nix
+checkConfigError "The option .*enable.* does not exist. Definition values:\n- In .*: true" "$@" ./disable-declare-enable.nix
 checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix
 checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix
 
@@ -142,17 +142,17 @@ checkConfigError 'infinite recursion encountered' "$@"
 
 # Check _module.check.
 set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix
-checkConfigError 'The option .* defined in .* does not exist.' "$@"
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*' "$@"
 checkConfigOutput "true" "$@" ./define-module-check.nix
 
 # Check coerced value.
 checkConfigOutput "\"42\"" config.value ./declare-coerced-value.nix
 checkConfigOutput "\"24\"" config.value ./declare-coerced-value.nix ./define-value-string.nix
-checkConfigError 'The option value .* in .* is not.*string or signed integer convertible to it' config.value ./declare-coerced-value.nix ./define-value-list.nix
+checkConfigError 'A definition for option .* is not.*string or signed integer convertible to it.*. Definition values:\n- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix
 
 # Check coerced value with unsound coercion
 checkConfigOutput "12" config.value ./declare-coerced-value-unsound.nix
-checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
+checkConfigError 'A definition for option .* is not of type .*. Definition values:\n- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
 checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
 
 # Check mkAliasOptionModule.
@@ -183,7 +183,7 @@ checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.ni
 checkConfigOutput "true" config.enable ./disable-recursive/main.nix
 checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo.nix}
 checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix}
-checkConfigError 'The option .* defined in .* does not exist' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
 
 # Check that imports can depend on derivations
 checkConfigOutput "true" config.enable ./import-from-store.nix
@@ -207,9 +207,61 @@ checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-c
 
 
 # Even with multiple assignments, a type error should be thrown if any of them aren't valid
-checkConfigError 'The option value .* in .* is not of type .*' \
+checkConfigError 'A definition for option .* is not of type .*' \
   config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
 
+## Freeform modules
+# Assigning without a declared option should work
+checkConfigOutput 24 config.value ./freeform-attrsOf.nix ./define-value-string.nix
+# No freeform assigments shouldn't make it error
+checkConfigOutput '{ }' config ./freeform-attrsOf.nix
+# but only if the type matches
+checkConfigError 'A definition for option .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix
+# and properties should be applied
+checkConfigOutput yes config.value ./freeform-attrsOf.nix ./define-value-string-properties.nix
+# Options should still be declarable, and be able to have a type that doesn't match the freeform type
+checkConfigOutput false config.enable ./freeform-attrsOf.nix ./define-value-string.nix ./declare-enable.nix
+checkConfigOutput 24 config.value ./freeform-attrsOf.nix ./define-value-string.nix ./declare-enable.nix
+# and this should work too with nested values
+checkConfigOutput false config.nest.foo ./freeform-attrsOf.nix ./freeform-nested.nix
+checkConfigOutput bar config.nest.bar ./freeform-attrsOf.nix ./freeform-nested.nix
+# Check whether a declared option can depend on an freeform-typed one
+checkConfigOutput null config.foo ./freeform-attrsOf.nix ./freeform-str-dep-unstr.nix
+checkConfigOutput 24 config.foo ./freeform-attrsOf.nix ./freeform-str-dep-unstr.nix ./define-value-string.nix
+# Check whether an freeform-typed value can depend on a declared option, this can only work with lazyAttrsOf
+checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.nix ./freeform-unstr-dep-str.nix
+checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix
+checkConfigOutput 24 config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix
+
+## types.anything
+# Check that attribute sets are merged recursively
+checkConfigOutput null config.value.foo ./types-anything/nested-attrs.nix
+checkConfigOutput null config.value.l1.foo ./types-anything/nested-attrs.nix
+checkConfigOutput null config.value.l1.l2.foo ./types-anything/nested-attrs.nix
+checkConfigOutput null config.value.l1.l2.l3.foo ./types-anything/nested-attrs.nix
+# Attribute sets that are coercible to strings shouldn't be recursed into
+checkConfigOutput foo config.value.outPath ./types-anything/attrs-coercible.nix
+# Multiple lists aren't concatenated together
+checkConfigError 'The option .* has conflicting definitions' config.value ./types-anything/lists.nix
+# Check that all equalizable atoms can be used as long as all definitions are equal
+checkConfigOutput 0 config.value.int ./types-anything/equal-atoms.nix
+checkConfigOutput false config.value.bool ./types-anything/equal-atoms.nix
+checkConfigOutput '""' config.value.string ./types-anything/equal-atoms.nix
+checkConfigOutput / config.value.path ./types-anything/equal-atoms.nix
+checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix
+checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix
+# Functions can't be merged together
+checkConfigError "The option .* has conflicting definition values" config.value.multiple-lambdas ./types-anything/functions.nix
+checkConfigOutput '<LAMBDA>' config.value.single-lambda ./types-anything/functions.nix
+# Check that all mk* modifiers are applied
+checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix
+checkConfigOutput '{ }' config.value.mkiftrue ./types-anything/mk-mods.nix
+checkConfigOutput 1 config.value.mkdefault ./types-anything/mk-mods.nix
+checkConfigOutput '{ }' config.value.mkmerge ./types-anything/mk-mods.nix
+checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix
+checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix
+checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
+
 cat <<EOF
 ====== module tests ======
 $pass Pass
diff --git a/nixpkgs/lib/tests/modules/define-option-dependently.nix b/nixpkgs/lib/tests/modules/define-option-dependently.nix
index 6abce29366ae..ad85f99a919f 100644
--- a/nixpkgs/lib/tests/modules/define-option-dependently.nix
+++ b/nixpkgs/lib/tests/modules/define-option-dependently.nix
@@ -7,7 +7,7 @@
   # Always defined, but the value depends on the presence of an option.
   config = {
     value = if options ? enable then 360 else 7;
-  } 
+  }
   # Only define if possible.
   // lib.optionalAttrs (options ? enable) {
     enable = true;
diff --git a/nixpkgs/lib/tests/modules/define-value-string-properties.nix b/nixpkgs/lib/tests/modules/define-value-string-properties.nix
new file mode 100644
index 000000000000..972304c01128
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-string-properties.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+
+  imports = [{
+    value = lib.mkDefault "def";
+  }];
+
+  value = lib.mkMerge [
+    (lib.mkIf false "nope")
+    "yes"
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-attrsOf.nix b/nixpkgs/lib/tests/modules/freeform-attrsOf.nix
new file mode 100644
index 000000000000..8cc577f38a6c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-attrsOf.nix
@@ -0,0 +1,3 @@
+{ lib, ... }: {
+  freeformType = with lib.types; attrsOf (either str (attrsOf str));
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix b/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix
new file mode 100644
index 000000000000..36d6c0b13fca
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix
@@ -0,0 +1,3 @@
+{ lib, ... }: {
+  freeformType = with lib.types; lazyAttrsOf (either str (lazyAttrsOf str));
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-nested.nix b/nixpkgs/lib/tests/modules/freeform-nested.nix
new file mode 100644
index 000000000000..5da27f5a8b4f
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-nested.nix
@@ -0,0 +1,7 @@
+{ lib, ... }: {
+  options.nest.foo = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+  };
+  config.nest.bar = "bar";
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix b/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix
new file mode 100644
index 000000000000..a2dfbc80cfa6
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix
@@ -0,0 +1,8 @@
+{ lib, config, ... }: {
+  options.foo = lib.mkOption {
+    type = lib.types.nullOr lib.types.str;
+    default = null;
+  };
+
+  config.foo = lib.mkIf (config ? value) config.value;
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix b/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix
new file mode 100644
index 000000000000..549d89afecac
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix
@@ -0,0 +1,8 @@
+{ lib, config, ... }: {
+  options.value = lib.mkOption {
+    type = lib.types.nullOr lib.types.str;
+    default = null;
+  };
+
+  config.foo = lib.mkIf (config.value != null) config.value;
+}
diff --git a/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix b/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix
new file mode 100644
index 000000000000..085cbd638f17
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+
+  options.value = lib.mkOption {
+    type = lib.types.anything;
+  };
+
+  config.value = {
+    outPath = "foo";
+    err = throw "err";
+  };
+
+}
diff --git a/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix b/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix
new file mode 100644
index 000000000000..972711201a09
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix
@@ -0,0 +1,26 @@
+{ lib, ... }: {
+
+  options.value = lib.mkOption {
+    type = lib.types.anything;
+  };
+
+  config = lib.mkMerge [
+    {
+      value.int = 0;
+      value.bool = false;
+      value.string = "";
+      value.path = /.;
+      value.null = null;
+      value.float = 0.1;
+    }
+    {
+      value.int = 0;
+      value.bool = false;
+      value.string = "";
+      value.path = /.;
+      value.null = null;
+      value.float = 0.1;
+    }
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/types-anything/functions.nix b/nixpkgs/lib/tests/modules/types-anything/functions.nix
new file mode 100644
index 000000000000..079518913918
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types-anything/functions.nix
@@ -0,0 +1,17 @@
+{ lib, ... }: {
+
+  options.value = lib.mkOption {
+    type = lib.types.anything;
+  };
+
+  config = lib.mkMerge [
+    {
+      value.single-lambda = x: x;
+      value.multiple-lambdas = x: x;
+    }
+    {
+      value.multiple-lambdas = x: x;
+    }
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/types-anything/lists.nix b/nixpkgs/lib/tests/modules/types-anything/lists.nix
new file mode 100644
index 000000000000..bd846afd3d18
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types-anything/lists.nix
@@ -0,0 +1,16 @@
+{ lib, ... }: {
+
+  options.value = lib.mkOption {
+    type = lib.types.anything;
+  };
+
+  config = lib.mkMerge [
+    {
+      value = [ null ];
+    }
+    {
+      value = [ null ];
+    }
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix b/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix
new file mode 100644
index 000000000000..f84ad01df017
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix
@@ -0,0 +1,44 @@
+{ lib, ... }: {
+
+  options.value = lib.mkOption {
+    type = lib.types.anything;
+  };
+
+  config = lib.mkMerge [
+    {
+      value.mkiffalse = lib.mkIf false {};
+    }
+    {
+      value.mkiftrue = lib.mkIf true {};
+    }
+    {
+      value.mkdefault = lib.mkDefault 0;
+    }
+    {
+      value.mkdefault = 1;
+    }
+    {
+      value.mkmerge = lib.mkMerge [
+        {}
+      ];
+    }
+    {
+      value.mkbefore = lib.mkBefore true;
+    }
+    {
+      value.nested = lib.mkMerge [
+        {
+          foo = lib.mkDefault 0;
+          bar = lib.mkIf false 0;
+        }
+        (lib.mkIf true {
+          foo = lib.mkIf true (lib.mkForce 1);
+          bar = {
+            baz = lib.mkDefault "baz";
+          };
+        })
+      ];
+    }
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix b/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix
new file mode 100644
index 000000000000..e57d33ef8717
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix
@@ -0,0 +1,22 @@
+{ lib, ... }: {
+
+  options.value = lib.mkOption {
+    type = lib.types.anything;
+  };
+
+  config = lib.mkMerge [
+    {
+      value.foo = null;
+    }
+    {
+      value.l1.foo = null;
+    }
+    {
+      value.l1.l2.foo = null;
+    }
+    {
+      value.l1.l2.l3.foo = null;
+    }
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/release.nix b/nixpkgs/lib/tests/release.nix
index eebee1b49bc8..800d8a65c14f 100644
--- a/nixpkgs/lib/tests/release.nix
+++ b/nixpkgs/lib/tests/release.nix
@@ -17,7 +17,6 @@ pkgs.runCommandNoCC "nixpkgs-lib-tests" {
     export TEST_ROOT=$(pwd)/test-tmp
     export NIX_BUILD_HOOK=
     export NIX_CONF_DIR=$TEST_ROOT/etc
-    export NIX_DB_DIR=$TEST_ROOT/db
     export NIX_LOCALSTATE_DIR=$TEST_ROOT/var
     export NIX_LOG_DIR=$TEST_ROOT/var/log/nix
     export NIX_STATE_DIR=$TEST_ROOT/var/nix
diff --git a/nixpkgs/lib/tests/systems.nix b/nixpkgs/lib/tests/systems.nix
index ea8ceedd43f7..f691b2da3165 100644
--- a/nixpkgs/lib/tests/systems.nix
+++ b/nixpkgs/lib/tests/systems.nix
@@ -15,14 +15,14 @@ in with lib.systems.doubles; lib.runTests {
   testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ js ++ genode ++ redox);
 
   testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-none" "armv7a-linux" "armv7l-linux" "arm-none" "armv7a-darwin" ];
-  testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ];
+  testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ];
   testmips = mseteq mips [ "mipsel-linux" ];
   testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "x86_64-genode" "x86_64-redox" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" "x86_64-none" ];
 
   testcygwin = mseteq cygwin [ "i686-cygwin" "x86_64-cygwin" ];
   testdarwin = mseteq darwin [ "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" ];
   testfreebsd = mseteq freebsd [ "i686-freebsd" "x86_64-freebsd" ];
-  testgenode = mseteq genode [ "aarch64-genode" "x86_64-genode" ];
+  testgenode = mseteq genode [ "aarch64-genode" "i686-genode" "x86_64-genode" ];
   testredox = mseteq redox [ "x86_64-redox" ];
   testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */);
   testillumos = mseteq illumos [ "x86_64-solaris" ];
diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix
index 5788dd435e59..9501a2906cae 100644
--- a/nixpkgs/lib/trivial.nix
+++ b/nixpkgs/lib/trivial.nix
@@ -171,7 +171,7 @@ rec {
      On each release the first letter is bumped and a new animal is chosen
      starting with that new letter.
   */
-  codeName = "Nightingale";
+  codeName = "Okapi";
 
   /* Returns the current nixpkgs version suffix as string. */
   versionSuffix =
@@ -332,4 +332,55 @@ rec {
   */
   isFunction = f: builtins.isFunction f ||
     (f ? __functor && isFunction (f.__functor f));
+
+  /* Convert the given positive integer to a string of its hexadecimal
+     representation. For example:
+
+     toHexString 0 => "0"
+
+     toHexString 16 => "10"
+
+     toHexString 250 => "FA"
+  */
+  toHexString = i:
+    let
+      toHexDigit = d:
+        if d < 10
+        then toString d
+        else
+          {
+            "10" = "A";
+            "11" = "B";
+            "12" = "C";
+            "13" = "D";
+            "14" = "E";
+            "15" = "F";
+          }.${toString d};
+    in
+      lib.concatMapStrings toHexDigit (toBaseDigits 16 i);
+
+  /* `toBaseDigits base i` converts the positive integer i to a list of its
+     digits in the given base. For example:
+
+     toBaseDigits 10 123 => [ 1 2 3 ]
+
+     toBaseDigits 2 6 => [ 1 1 0 ]
+
+     toBaseDigits 16 250 => [ 15 10 ]
+  */
+  toBaseDigits = base: i:
+    let
+      go = i:
+        if i < base
+        then [i]
+        else
+          let
+            r = i - ((i / base) * base);
+            q = (i - r) / base;
+          in
+            [r] ++ go q;
+    in
+      assert (base >= 2);
+      assert (i >= 0);
+      lib.reverseList (go i);
 }
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; };