summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/customisation.nix8
-rw-r--r--lib/default.nix5
-rw-r--r--lib/generators.nix24
-rw-r--r--lib/licenses.nix5
-rw-r--r--lib/meta.nix12
-rw-r--r--lib/modules.nix22
-rw-r--r--lib/strings.nix10
-rw-r--r--lib/systems/default.nix13
-rw-r--r--lib/systems/doubles.nix60
-rw-r--r--lib/systems/examples.nix24
-rw-r--r--lib/systems/parse.nix9
-rwxr-xr-xlib/tests/modules.sh3
-rw-r--r--lib/tests/modules/declare-submoduleWith-modules.nix4
-rw-r--r--lib/tests/systems.nix6
-rw-r--r--lib/trivial.nix5
-rw-r--r--lib/types.nix21
16 files changed, 160 insertions, 71 deletions
diff --git a/lib/customisation.nix b/lib/customisation.nix
index 37a7951896b..c17cb0d0f8e 100644
--- a/lib/customisation.nix
+++ b/lib/customisation.nix
@@ -219,16 +219,17 @@ rec {
 
   /* Like the above, but aims to support cross compilation. It's still ugly, but
      hopefully it helps a little bit. */
-  makeScopeWithSplicing = splicePackages: newScope: otherSplices: keep: f:
+  makeScopeWithSplicing = splicePackages: newScope: otherSplices: keep: extra: f:
     let
-      spliced = splicePackages {
+      spliced0 = splicePackages {
         pkgsBuildBuild = otherSplices.selfBuildBuild;
         pkgsBuildHost = otherSplices.selfBuildHost;
         pkgsBuildTarget = otherSplices.selfBuildTarget;
         pkgsHostHost = otherSplices.selfHostHost;
         pkgsHostTarget = self; # Not `otherSplices.selfHostTarget`;
         pkgsTargetTarget = otherSplices.selfTargetTarget;
-      } // keep self;
+      };
+      spliced = extra spliced0 // spliced0 // keep self;
       self = f self // {
         newScope = scope: newScope (spliced // scope);
         callPackage = newScope spliced; # == self.newScope {};
@@ -239,6 +240,7 @@ rec {
           newScope
           otherSplices
           keep
+          extra
           (lib.fixedPoints.extends g f);
         packages = f;
       };
diff --git a/lib/default.nix b/lib/default.nix
index 50320669e28..ccae0bbc3ab 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -66,8 +66,9 @@ let
       stringLength sub substring tail trace;
     inherit (self.trivial) id const pipe concat or and bitAnd bitOr bitXor
       bitNot boolToString mergeAttrs flip mapNullable inNixShell isFloat min max
-      importJSON importTOML warn info showWarnings nixpkgsVersion version mod compare
-      splitByAndCompare functionArgs setFunctionArgs isFunction toHexString toBaseDigits;
+      importJSON importTOML warn warnIf info showWarnings nixpkgsVersion version
+      mod compare splitByAndCompare functionArgs setFunctionArgs isFunction
+      toHexString toBaseDigits;
     inherit (self.fixedPoints) fix fix' converge extends composeExtensions
       composeManyExtensions makeExtensible makeExtensibleWithCustomName;
     inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath
diff --git a/lib/generators.nix b/lib/generators.nix
index 501a23599f4..c8144db50ac 100644
--- a/lib/generators.nix
+++ b/lib/generators.nix
@@ -307,4 +307,28 @@ rec {
 ${expr "" v}
 </plist>'';
 
+  /* Translate a simple Nix expression to Dhall notation.
+   * Note that integers are translated to Integer and never
+   * the Natural type.
+  */
+  toDhall = { }@args: v:
+    with builtins;
+    let concatItems = lib.strings.concatStringsSep ", ";
+    in if isAttrs v then
+      "{ ${
+        concatItems (lib.attrsets.mapAttrsToList
+          (key: value: "${key} = ${toDhall args value}") v)
+      } }"
+    else if isList v then
+      "[ ${concatItems (map (toDhall args) v)} ]"
+    else if isInt v then
+      "${if v < 0 then "" else "+"}${toString v}"
+    else if isBool v then
+      (if v then "True" else "False")
+    else if isFunction v then
+      abort "generators.toDhall: cannot convert a function to Dhall"
+    else if isNull v then
+      abort "generators.toDhall: cannot convert a null to Dhall"
+    else
+      builtins.toJSON v;
 }
diff --git a/lib/licenses.nix b/lib/licenses.nix
index 46ac0443a03..88d598d9207 100644
--- a/lib/licenses.nix
+++ b/lib/licenses.nix
@@ -125,6 +125,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) ({
     fullName = ''BSD 4-clause "Original" or "Old" License'';
   };
 
+  bsdOriginalUC = spdx {
+    spdxId = "BSD-4-Clause-UC";
+    fullName = "BSD 4-Clause University of California-Specific";
+  };
+
   bsdProtection = spdx {
     spdxId = "BSD-Protection";
     fullName = "BSD Protection License";
diff --git a/lib/meta.nix b/lib/meta.nix
index 2e83c4247dd..bc04394dcf0 100644
--- a/lib/meta.nix
+++ b/lib/meta.nix
@@ -87,4 +87,16 @@ rec {
         then { system = elem; }
         else { parsed = elem; };
     in lib.matchAttrs pattern platform;
+
+  /* Check if a package is available on a given platform.
+
+     A package is available on a platform if both
+
+       1. One of `meta.platforms` pattern matches the given platform.
+
+       2. None of `meta.badPlatforms` pattern matches the given platform.
+  */
+  availableOn = platform: pkg:
+    lib.any (platformMatch platform) pkg.meta.platforms &&
+    lib.all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []);
 }
diff --git a/lib/modules.nix b/lib/modules.nix
index d3f10944e70..99b9a8a31ea 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -23,6 +23,7 @@ let
     isAttrs
     isBool
     isFunction
+    isList
     isString
     length
     mapAttrs
@@ -37,7 +38,7 @@ let
     setAttrByPath
     toList
     types
-    warn
+    warnIf
     ;
   inherit (lib.options)
     isOption
@@ -127,7 +128,7 @@ rec {
         let collected = collectModules
           (specialArgs.modulesPath or "")
           (modules ++ [ internalModule ])
-          ({ inherit lib options config; } // specialArgs);
+          ({ inherit lib options config specialArgs; } // specialArgs);
         in mergeModules prefix (reverseList collected);
 
       options = merged.matchedOptions;
@@ -188,6 +189,9 @@ rec {
       loadModule = args: fallbackFile: fallbackKey: m:
         if isFunction m || isAttrs m then
           unifyModuleSyntax fallbackFile fallbackKey (applyIfFunction fallbackKey m args)
+        else if isList m then
+          let defs = [{ file = fallbackFile; value = m; }]; in
+          throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
         else unifyModuleSyntax (toString m) (toString m) (applyIfFunction (toString m) (import m) args);
 
       /*
@@ -295,13 +299,11 @@ rec {
       # a module will resolve strictly the attributes used as argument but
       # not their values.  The values are forwarding the result of the
       # evaluation of the option.
-      requiredArgs = builtins.attrNames (lib.functionArgs f);
       context = name: ''while evaluating the module argument `${name}' in "${key}":'';
-      extraArgs = builtins.listToAttrs (map (name: {
-        inherit name;
-        value = builtins.addErrorContext (context name)
-          (args.${name} or config._module.args.${name});
-      }) requiredArgs);
+      extraArgs = builtins.mapAttrs (name: _:
+        builtins.addErrorContext (context name)
+          (args.${name} or config._module.args.${name})
+      ) (lib.functionArgs f);
 
       # Note: we append in the opposite order such that we can add an error
       # context on the explicited arguments of "args" too. This update
@@ -516,8 +518,8 @@ rec {
       value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
 
       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}";
+        warnIf (opt.type.deprecationMessage != null)
+          "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;
diff --git a/lib/strings.nix b/lib/strings.nix
index 5010d9159cb..49fa0196a0b 100644
--- a/lib/strings.nix
+++ b/lib/strings.nix
@@ -606,7 +606,7 @@ rec {
      This function will fail if the input string is longer than the
      requested length.
 
-     Type: fixedWidthString :: int -> string -> string
+     Type: fixedWidthString :: int -> string -> string -> string
 
      Example:
        fixedWidthString 5 "0" (toString 15)
@@ -644,8 +644,8 @@ rec {
   floatToString = float: let
     result = toString float;
     precise = float == fromJSON result;
-  in if precise then result
-    else lib.warn "Imprecise conversion from float to string ${result}" result;
+  in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
+    result;
 
   /* Check whether a value can be coerced to a string */
   isCoercibleToString = x:
@@ -659,7 +659,7 @@ rec {
      Example:
        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
        => false
-       isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/"
+       isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
        => true
        isStorePath pkgs.python
        => true
@@ -667,7 +667,7 @@ rec {
        => false
   */
   isStorePath = x:
-    if isCoercibleToString x then
+    if !(isList x) && isCoercibleToString x then
       let str = toString x; in
       substring 0 1 str == "/"
       && dirOf str == storeDir
diff --git a/lib/systems/default.nix b/lib/systems/default.nix
index 1e38dbf531b..21b00374da4 100644
--- a/lib/systems/default.nix
+++ b/lib/systems/default.nix
@@ -112,6 +112,19 @@ rec {
         aarch64 = "arm64";
       }.${final.parsed.cpu.name} or final.parsed.cpu.name;
 
+      darwinPlatform =
+        if final.isMacOS then "macos"
+        else if final.isiOS then "ios"
+        else null;
+      # The canonical name for this attribute is darwinSdkVersion, but some
+      # platforms define the old name "sdkVer".
+      darwinSdkVersion = final.sdkVer or "10.12";
+      darwinMinVersion = final.darwinSdkVersion;
+      darwinMinVersionVariable =
+        if final.isMacOS then "MACOSX_DEPLOYMENT_TARGET"
+        else if final.isiOS then "IPHONEOS_DEPLOYMENT_TARGET"
+        else null;
+
       emulator = pkgs: let
         qemu-user = pkgs.qemu.override {
           smartcardSupport = false;
diff --git a/lib/systems/doubles.nix b/lib/systems/doubles.nix
index 07327fa2273..6f638be585b 100644
--- a/lib/systems/doubles.nix
+++ b/lib/systems/doubles.nix
@@ -6,43 +6,53 @@ let
   inherit (lib.attrsets) matchAttrs;
 
   all = [
-    "aarch64-linux"
-    "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux"
+    # Cygwin
+    "i686-cygwin" "x86_64-cygwin"
 
-    "mipsel-linux"
+    # Darwin
+    "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin"
 
-    "i686-cygwin" "i686-freebsd" "i686-linux" "i686-netbsd" "i686-openbsd"
+    # FreeBSD
+    "i686-freebsd" "x86_64-freebsd"
 
-    "x86_64-cygwin" "x86_64-freebsd" "x86_64-linux"
-    "x86_64-netbsd" "x86_64-openbsd" "x86_64-solaris"
+    # Genode
+    "aarch64-genode" "i686-genode" "x86_64-genode"
 
-    "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin"
+    # illumos
+    "x86_64-solaris"
 
-    "x86_64-windows" "i686-windows"
+    # JS
+    "js-ghcjs"
 
-    "wasm64-wasi" "wasm32-wasi"
+    # Linux
+    "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux"
+    "armv7l-linux" "i686-linux" "mipsel-linux" "powerpc64-linux"
+    "powerpc64le-linux" "riscv32-linux" "riscv64-linux" "x86_64-linux"
 
-    "x86_64-redox"
+    # MMIXware
+    "mmix-mmixware"
 
-    "powerpc64-linux"
-    "powerpc64le-linux"
+    # NetBSD
+    "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd"
+    "i686-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd"
+    "riscv64-netbsd" "x86_64-netbsd"
 
-    "riscv32-linux" "riscv64-linux"
+    # none
+    "aarch64-none" "arm-none" "armv6l-none" "avr-none" "i686-none" "msp430-none"
+    "or1k-none" "powerpc-none" "riscv32-none" "riscv64-none" "vc4-none"
+    "x86_64-none"
 
-    "arm-none" "armv6l-none" "aarch64-none"
-    "avr-none"
-    "i686-none" "x86_64-none"
-    "powerpc-none"
-    "msp430-none"
-    "riscv64-none" "riscv32-none"
-    "vc4-none"
-    "or1k-none"
+    # OpenBSD
+    "i686-openbsd" "x86_64-openbsd"
 
-    "mmix-mmixware"
+    # Redox
+    "x86_64-redox"
 
-    "js-ghcjs"
+    # WASI
+    "wasm64-wasi" "wasm32-wasi"
 
-    "aarch64-genode" "i686-genode" "x86_64-genode"
+    # Windows
+    "x86_64-windows" "i686-windows"
   ];
 
   allParsed = map parse.mkSystemFromString all;
@@ -73,7 +83,7 @@ in {
   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; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.elfv1; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.elfv2; });
+  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;
diff --git a/lib/systems/examples.nix b/lib/systems/examples.nix
index 8a43b86db70..9c0013c3977 100644
--- a/lib/systems/examples.nix
+++ b/lib/systems/examples.nix
@@ -21,14 +21,10 @@ rec {
     config = "powerpc64le-unknown-linux-musl";
   };
 
-  ppc64-elfv1 = {
-    config = "powerpc64-unknown-linux-elfv1";
-  };
-  ppc64-elfv2 = {
-    config = "powerpc64-unknown-linux-elfv2";
+  ppc64 = {
+    config = "powerpc64-unknown-linux-gnu";
+    gcc = { abi = "elfv2"; }; # for gcc configuration
   };
-  ppc64 = ppc64-elfv2; # default to modern elfv2
-
   ppc64-musl = {
     config = "powerpc64-unknown-linux-musl";
     gcc = { abi = "elfv2"; }; # for gcc configuration
@@ -60,6 +56,7 @@ rec {
 
   armv7a-android-prebuilt = {
     config = "armv7a-unknown-linux-androideabi";
+    rustc.config = "armv7-linux-androideabi";
     sdkVer = "29";
     ndkVer = "21";
     useAndroidPrebuilt = true;
@@ -67,6 +64,7 @@ rec {
 
   aarch64-android-prebuilt = {
     config = "aarch64-unknown-linux-android";
+    rustc.config = "aarch64-linux-android";
     sdkVer = "29";
     ndkVer = "21";
     useAndroidPrebuilt = true;
@@ -219,6 +217,7 @@ rec {
     sdkVer = "14.3";
     xcodeVer = "12.3";
     xcodePlatform = "iPhoneSimulator";
+    darwinPlatform = "ios-simulator";
     useiOSPrebuilt = true;
   };
 
@@ -228,6 +227,7 @@ rec {
     sdkVer = "14.3";
     xcodeVer = "12.3";
     xcodePlatform = "iPhoneSimulator";
+    darwinPlatform = "ios-simulator";
     useiOSPrebuilt = true;
   };
 
@@ -250,11 +250,19 @@ rec {
 
   # BSDs
 
-  amd64-netbsd = {
+  amd64-netbsd = lib.warn "The amd64-netbsd system example is deprecated. Use x86_64-netbsd instead." x86_64-netbsd;
+
+  x86_64-netbsd = {
     config = "x86_64-unknown-netbsd";
     libc = "nblibc";
   };
 
+  x86_64-netbsd-llvm = {
+    config = "x86_64-unknown-netbsd";
+    libc = "nblibc";
+    useLLVM = true;
+  };
+
   #
   # WASM
   #
diff --git a/lib/systems/parse.nix b/lib/systems/parse.nix
index 8e012622ccd..a06ac0d11f7 100644
--- a/lib/systems/parse.nix
+++ b/lib/systems/parse.nix
@@ -337,18 +337,10 @@ rec {
             The "gnu" ABI is ambiguous on 32-bit ARM. Use "gnueabi" or "gnueabihf" instead.
           '';
         }
-        { assertion = platform: platform.system != "powerpc64-linux";
-          message = ''
-            The "gnu" ABI is ambiguous on big-endian 64-bit PPC. Use "elfv1" or "elfv2" instead.
-          '';
-        }
       ];
     };
     gnuabi64     = { abi = "64"; };
 
-    elfv1        = { abi = "elfv1"; };
-    elfv2        = { abi = "elfv2"; };
-
     musleabi     = { float = "soft"; };
     musleabihf   = { float = "hard"; };
     musl         = {};
@@ -452,7 +444,6 @@ rec {
             if lib.versionAtLeast (parsed.cpu.version or "0") "6"
             then abis.gnueabihf
             else abis.gnueabi
-          else if cpu == "powerpc64" then abis.elfv2
           else abis.gnu
         else                     abis.unknown;
     };
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 2eddeec07b1..2e57c2f8e2a 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -175,6 +175,9 @@ checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-noshort
 ## submoduleWith should merge all modules in one swoop
 checkConfigOutput "true" config.submodule.inner ./declare-submoduleWith-modules.nix
 checkConfigOutput "true" config.submodule.outer ./declare-submoduleWith-modules.nix
+# Should also be able to evaluate the type name (which evaluates freeformType,
+# which evaluates all the modules defined by the type)
+checkConfigOutput "submodule" options.submodule.type.description ./declare-submoduleWith-modules.nix
 
 ## Paths should be allowed as values and work as expected
 checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
diff --git a/lib/tests/modules/declare-submoduleWith-modules.nix b/lib/tests/modules/declare-submoduleWith-modules.nix
index 4736ab41751..a8b82d17688 100644
--- a/lib/tests/modules/declare-submoduleWith-modules.nix
+++ b/lib/tests/modules/declare-submoduleWith-modules.nix
@@ -8,9 +8,6 @@
             default = false;
           };
         }
-        {
-          outer = true;
-        }
       ];
     };
     default = {};
@@ -25,6 +22,7 @@
     })
     {
       inner = true;
+      outer = true;
     }
   ];
 }
diff --git a/lib/tests/systems.nix b/lib/tests/systems.nix
index c0800df25ed..36f82b783b4 100644
--- a/lib/tests/systems.nix
+++ b/lib/tests/systems.nix
@@ -15,9 +15,9 @@ in
 with lib.systems.doubles; lib.runTests {
   testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox);
 
-  testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-none" "armv7a-linux" "armv7l-linux" "arm-none" "armv7a-darwin" ];
+  testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-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" ];
+  testmips = mseteq mips [ "mipsel-linux" "mipsel-netbsd" ];
   testmmix = mseteq mmix [ "mmix-mmixware" ];
   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" ];
 
@@ -29,7 +29,7 @@ with lib.systems.doubles; lib.runTests {
   testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */);
   testillumos = mseteq illumos [ "x86_64-solaris" ];
   testlinux = mseteq linux [ "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux" "i686-linux" "mipsel-linux" "riscv32-linux" "riscv64-linux" "x86_64-linux" "powerpc64-linux" "powerpc64le-linux" ];
-  testnetbsd = mseteq netbsd [ "i686-netbsd" "x86_64-netbsd" ];
+  testnetbsd = mseteq netbsd [ "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" "i686-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" ];
   testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ];
   testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "i686-windows" "x86_64-windows" ];
   testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox);
diff --git a/lib/trivial.nix b/lib/trivial.nix
index 268f39d3210..f6f5da5998f 100644
--- a/lib/trivial.nix
+++ b/lib/trivial.nix
@@ -158,7 +158,7 @@ rec {
     seq deepSeq genericClosure;
 
 
-  ## nixpks version strings
+  ## nixpkgs version strings
 
   /* Returns the current full nixpkgs version number. */
   version = release + versionSuffix;
@@ -297,12 +297,15 @@ rec {
   # Usage:
   # {
   #   foo = lib.warn "foo is deprecated" oldFoo;
+  #   bar = lib.warnIf (bar == "") "Empty bar is deprecated" bar;
   # }
   #
   # TODO: figure out a clever way to integrate location information from
   # something like __unsafeGetAttrPos.
 
   warn = msg: builtins.trace "warning: ${msg}";
+  warnIf = cond: msg: if cond then warn msg else id;
+
   info = msg: builtins.trace "INFO: ${msg}";
 
   showWarnings = warnings: res: lib.fold (w: x: warn w x) res warnings;
diff --git a/lib/types.nix b/lib/types.nix
index 4e56cecf629..f47a1f92de7 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -147,9 +147,13 @@ rec {
     , # The deprecation message to display when this type is used by an option
       # If null, the type isn't deprecated
       deprecationMessage ? null
+    , # The types that occur in the definition of this type. This is used to
+      # issue deprecation warnings recursively. Can also be used to reuse
+      # nested types
+      nestedTypes ? {}
     }:
     { _type = "option-type";
-      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage;
+      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage nestedTypes;
       description = if description == null then name else description;
     };
 
@@ -337,7 +341,7 @@ rec {
     };
 
     shellPackage = package // {
-      check = x: (package.check x) && (hasAttr "shellPath" x);
+      check = x: isDerivation x && hasAttr "shellPath" x;
     };
 
     path = mkOptionType {
@@ -365,6 +369,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: listOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     nonEmptyListOf = elemType:
@@ -389,6 +394,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: attrsOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     # A version of attrsOf that's lazy in its values at the expense of
@@ -413,6 +419,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     # TODO: drop this in the future:
@@ -421,6 +428,7 @@ rec {
       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.";
+      nestedTypes.elemType = elemType;
     };
 
     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
@@ -433,6 +441,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: uniq (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     # Null or value of ...
@@ -451,6 +460,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: nullOr (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     functionTo = elemType: mkOptionType {
@@ -535,6 +545,9 @@ rec {
         substSubModules = m: submoduleWith (attrs // {
           modules = m;
         });
+        nestedTypes = lib.optionalAttrs (freeformType != null) {
+          freeformType = freeformType;
+        };
         functor = defaultFunctor name // {
           type = types.submoduleWith;
           payload = {
@@ -596,6 +609,8 @@ rec {
            then functor.type mt1 mt2
            else null;
       functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
+      nestedTypes.left = t1;
+      nestedTypes.right = t2;
     };
 
     # Any of the types in the given list
@@ -627,6 +642,8 @@ rec {
         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
         typeMerge = t1: t2: null;
         functor = (defaultFunctor name) // { wrapped = finalType; };
+        nestedTypes.coercedType = coercedType;
+        nestedTypes.finalType = finalType;
       };
 
     # Obsolete alternative to configOf.  It takes its option