summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2020-01-07 23:51:27 +0000
committerAlyssa Ross <hi@alyssa.is>2020-01-08 00:25:04 +0000
commit096ca241d9505c265fe46cacec7b316eecb401ad (patch)
tree1c4f5daadf7a0f2544ef9b1d219ddcca6867df0d /lib
parent1acfa8ff79fd8895ebc7ca9954830ace0577fff2 (diff)
parent5f6df74f66246a2eb198b0bc3fe1f7d2dff57062 (diff)
downloadnixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.tar
nixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.tar.gz
nixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.tar.bz2
nixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.tar.lz
nixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.tar.xz
nixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.tar.zst
nixpkgs-096ca241d9505c265fe46cacec7b316eecb401ad.zip
Merge remote-tracking branch 'nixpkgs/master' into master
Diffstat (limited to 'lib')
-rw-r--r--lib/default.nix2
-rw-r--r--lib/modules.nix56
-rw-r--r--lib/systems/doubles.nix3
-rwxr-xr-xlib/tests/modules.sh75
-rw-r--r--lib/tests/modules/declare-attrsOfSub-any-enable.nix (renamed from lib/tests/modules/declare-loaOfSub-any-enable.nix)4
-rw-r--r--lib/tests/modules/declare-submoduleWith-modules.nix30
-rw-r--r--lib/tests/modules/declare-submoduleWith-noshorthand.nix13
-rw-r--r--lib/tests/modules/declare-submoduleWith-path.nix12
-rw-r--r--lib/tests/modules/declare-submoduleWith-shorthand.nix14
-rw-r--r--lib/tests/modules/declare-submoduleWith-special.nix17
-rw-r--r--lib/tests/modules/define-attrsOfSub-bar-enable.nix3
-rw-r--r--lib/tests/modules/define-attrsOfSub-bar.nix3
-rw-r--r--lib/tests/modules/define-attrsOfSub-foo-enable-force.nix5
-rw-r--r--lib/tests/modules/define-attrsOfSub-foo-enable-if.nix5
-rw-r--r--lib/tests/modules/define-attrsOfSub-foo-enable.nix3
-rw-r--r--lib/tests/modules/define-attrsOfSub-foo-force-enable.nix (renamed from lib/tests/modules/define-loaOfSub-foo-force-enable.nix)2
-rw-r--r--lib/tests/modules/define-attrsOfSub-foo-if-enable.nix (renamed from lib/tests/modules/define-loaOfSub-foo-if-enable.nix)2
-rw-r--r--lib/tests/modules/define-attrsOfSub-foo.nix3
-rw-r--r--lib/tests/modules/define-attrsOfSub-force-foo-enable.nix (renamed from lib/tests/modules/define-loaOfSub-force-foo-enable.nix)2
-rw-r--r--lib/tests/modules/define-attrsOfSub-if-foo-enable.nix (renamed from lib/tests/modules/define-loaOfSub-if-foo-enable.nix)2
-rw-r--r--lib/tests/modules/define-force-attrsOfSub-foo-enable.nix5
-rw-r--r--lib/tests/modules/define-force-loaOfSub-foo-enable.nix5
-rw-r--r--lib/tests/modules/define-if-attrsOfSub-foo-enable.nix (renamed from lib/tests/modules/define-if-loaOfSub-foo-enable.nix)2
-rw-r--r--lib/tests/modules/define-loaOfSub-bar-enable.nix3
-rw-r--r--lib/tests/modules/define-loaOfSub-bar.nix3
-rw-r--r--lib/tests/modules/define-loaOfSub-foo-enable-force.nix5
-rw-r--r--lib/tests/modules/define-loaOfSub-foo-enable-if.nix5
-rw-r--r--lib/tests/modules/define-loaOfSub-foo-enable.nix3
-rw-r--r--lib/tests/modules/define-loaOfSub-foo.nix3
-rw-r--r--lib/tests/modules/define-submoduleWith-noshorthand.nix3
-rw-r--r--lib/tests/modules/define-submoduleWith-shorthand.nix3
-rw-r--r--lib/tests/modules/loaOf-with-long-list.nix19
-rw-r--r--lib/tests/modules/loaOf-with-many-list-merges.nix19
-rw-r--r--lib/tests/systems.nix2
-rw-r--r--lib/types.nix106
35 files changed, 277 insertions, 165 deletions
diff --git a/lib/default.nix b/lib/default.nix
index 8af53152586..e31edeaaf9e 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -102,7 +102,7 @@ let
       commitIdFromGitRepo cleanSourceWith pathHasContext
       canCleanSource;
     inherit (modules) evalModules closeModules unifyModuleSyntax
-      applyIfFunction unpackSubmodule packSubmodule mergeModules
+      applyIfFunction mergeModules
       mergeModules' mergeOptionDecls evalOptionValue mergeDefinitions
       pushDownProperties dischargeProperties filterOverrides
       sortProperties fixupOptionType mkIf mkAssert mkMerge mkOverride
diff --git a/lib/modules.nix b/lib/modules.nix
index 44db77b5d1c..48788ae933d 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -103,42 +103,42 @@ rec {
       toClosureList = file: parentKey: imap1 (n: x:
         if isAttrs x || isFunction x then
           let key = "${parentKey}:anon-${toString n}"; in
-          unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args)
+          unifyModuleSyntax file key (applyIfFunction key x args)
         else
           let file = toString x; key = toString x; in
           unifyModuleSyntax file key (applyIfFunction key (import x) args));
     in
       builtins.genericClosure {
         startSet = toClosureList unknownModule "" modules;
-        operator = m: toClosureList m.file m.key m.imports;
+        operator = m: toClosureList m._file m.key m.imports;
       };
 
   /* Massage a module into canonical form, that is, a set consisting
      of ‘options’, ‘config’ and ‘imports’ attributes. */
   unifyModuleSyntax = file: key: m:
-    let metaSet = if m ? meta
-      then { meta = m.meta; }
-      else {};
+    let addMeta = config: if m ? meta
+      then mkMerge [ config { meta = m.meta; } ]
+      else config;
     in
     if m ? config || m ? options then
       let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in
       if badAttrs != {} then
         throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
       else
-        { file = m._file or file;
+        { _file = m._file or file;
           key = toString m.key or key;
           disabledModules = m.disabledModules or [];
           imports = m.imports or [];
           options = m.options or {};
-          config = mkMerge [ (m.config or {}) metaSet ];
+          config = addMeta (m.config or {});
         }
     else
-      { file = m._file or file;
+      { _file = m._file or file;
         key = toString m.key or key;
         disabledModules = m.disabledModules or [];
         imports = m.require or [] ++ m.imports or [];
         options = {};
-        config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ];
+        config = addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]);
       };
 
   applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
@@ -171,17 +171,6 @@ rec {
   else
     f;
 
-  /* We have to pack and unpack submodules. We cannot wrap the expected
-     result of the function as we would no longer be able to list the arguments
-     of the submodule. (see applyIfFunction) */
-  unpackSubmodule = unpack: m: args:
-    if isType "submodule" m then
-      { _file = m.file; } // (unpack m.submodule args)
-    else unpack m args;
-
-  packSubmodule = file: m:
-    { _type = "submodule"; file = file; submodule = m; };
-
   /* Merge a list of modules.  This will recurse over the option
      declarations in all modules, combining them into a single set.
      At the same time, for each option declaration, it will merge the
@@ -189,7 +178,7 @@ rec {
      in the ‘value’ attribute of each option. */
   mergeModules = prefix: modules:
     mergeModules' prefix modules
-      (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
+      (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);
 
   mergeModules' = prefix: options: configs:
     let
@@ -223,7 +212,7 @@ rec {
                ) {} modules;
       # an attrset 'name' => list of submodules that declare ‘name’.
       declsByName = byName "options" (module: option:
-          [{ inherit (module) file; options = option; }]
+          [{ inherit (module) _file; options = option; }]
         ) options;
       # an attrset 'name' => list of submodules that define ‘name’.
       defnsByName = byName "config" (module: value:
@@ -250,7 +239,7 @@ rec {
               firstOption = findFirst (m: isOption m.options) "" decls;
               firstNonOption = findFirst (m: !isOption m.options) "" decls;
             in
-              throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
+              throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'."
           else
             mergeModules' loc decls defns
       ))
@@ -267,7 +256,14 @@ rec {
 
      'opts' is a list of modules.  Each module has an options attribute which
      correspond to the definition of 'loc' in 'opt.file'. */
-  mergeOptionDecls = loc: opts:
+  mergeOptionDecls =
+   let
+    packSubmodule = file: m:
+      { _file = file; imports = [ m ]; };
+    coerceOption = file: opt:
+      if isFunction opt then packSubmodule file opt
+      else packSubmodule file { options = opt; };
+   in loc: opts:
     foldl' (res: opt:
       let t  = res.type;
           t' = opt.options.type;
@@ -284,7 +280,7 @@ rec {
          bothHave "apply" ||
          (bothHave "type" && (! typesMergeable))
       then
-        throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
+        throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
       else
         let
           /* Add the modules of the current option to the list of modules
@@ -293,16 +289,14 @@ rec {
              current option declaration as the file use for the submodule.  If the
              submodule defines any filename, then we ignore the enclosing option file. */
           options' = toList opt.options.options;
-          coerceOption = file: opt:
-            if isFunction opt then packSubmodule file opt
-            else packSubmodule file { options = opt; };
+
           getSubModules = opt.options.type.getSubModules or null;
           submodules =
-            if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options
-            else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options
+            if getSubModules != null then map (packSubmodule opt._file) getSubModules ++ res.options
+            else if opt.options ? options then map (coerceOption opt._file) options' ++ res.options
             else res.options;
         in opt.options // res //
-          { declarations = res.declarations ++ [opt.file];
+          { declarations = res.declarations ++ [opt._file];
             options = submodules;
           } // typeSet
     ) { inherit loc; declarations = []; options = []; } opts;
diff --git a/lib/systems/doubles.nix b/lib/systems/doubles.nix
index 700c895b3ab..96e602d0e16 100644
--- a/lib/systems/doubles.nix
+++ b/lib/systems/doubles.nix
@@ -27,6 +27,8 @@ let
     "riscv32-linux" "riscv64-linux"
 
     "aarch64-none" "avr-none" "arm-none" "i686-none" "x86_64-none" "powerpc-none" "msp430-none" "riscv64-none" "riscv32-none" "vc4-none"
+
+    "js-ghcjs"
   ];
 
   allParsed = map parse.mkSystemFromString all;
@@ -46,6 +48,7 @@ in {
   mips    = filterDoubles predicates.isMips;
   riscv   = filterDoubles predicates.isRiscV;
   vc4     = filterDoubles predicates.isVc4;
+  js      = filterDoubles predicates.isJavaScript;
 
   cygwin  = filterDoubles predicates.isCygwin;
   darwin  = filterDoubles predicates.isDarwin;
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index cf344122cf4..f69befd15c6 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -87,36 +87,36 @@ checkConfigOutput "false" "$@" ./define-force-enable.nix
 checkConfigOutput "false" "$@" ./define-enable-force.nix
 
 # Check mkForce with option and submodules.
-checkConfigError 'attribute .*foo.* .* not found' config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix
-checkConfigOutput 'false' config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix
-set -- config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' config.attrsOfSub.foo.enable ./declare-attrsOfSub-any-enable.nix
+checkConfigOutput 'false' config.attrsOfSub.foo.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix
+set -- config.attrsOfSub.foo.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo-enable.nix
 checkConfigOutput 'true' "$@"
-checkConfigOutput 'false' "$@" ./define-force-loaOfSub-foo-enable.nix
-checkConfigOutput 'false' "$@" ./define-loaOfSub-force-foo-enable.nix
-checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-force-enable.nix
-checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-enable-force.nix
+checkConfigOutput 'false' "$@" ./define-force-attrsOfSub-foo-enable.nix
+checkConfigOutput 'false' "$@" ./define-attrsOfSub-force-foo-enable.nix
+checkConfigOutput 'false' "$@" ./define-attrsOfSub-foo-force-enable.nix
+checkConfigOutput 'false' "$@" ./define-attrsOfSub-foo-enable-force.nix
 
 # Check overriding effect of mkForce on submodule definitions.
-checkConfigError 'attribute .*bar.* .* not found' config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix
-checkConfigOutput 'false' config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix ./define-loaOfSub-bar.nix
-set -- config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix ./define-loaOfSub-bar-enable.nix
+checkConfigError 'attribute .*bar.* .* not found' config.attrsOfSub.bar.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix
+checkConfigOutput 'false' config.attrsOfSub.bar.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix ./define-attrsOfSub-bar.nix
+set -- config.attrsOfSub.bar.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix ./define-attrsOfSub-bar-enable.nix
 checkConfigOutput 'true' "$@"
-checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-force-loaOfSub-foo-enable.nix
-checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-loaOfSub-force-foo-enable.nix
-checkConfigOutput 'true' "$@" ./define-loaOfSub-foo-force-enable.nix
-checkConfigOutput 'true' "$@" ./define-loaOfSub-foo-enable-force.nix
+checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-force-attrsOfSub-foo-enable.nix
+checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-attrsOfSub-force-foo-enable.nix
+checkConfigOutput 'true' "$@" ./define-attrsOfSub-foo-force-enable.nix
+checkConfigOutput 'true' "$@" ./define-attrsOfSub-foo-enable-force.nix
 
 # Check mkIf with submodules.
-checkConfigError 'attribute .*foo.* .* not found' config.loaOfSub.foo.enable ./declare-enable.nix ./declare-loaOfSub-any-enable.nix
-set -- config.loaOfSub.foo.enable ./declare-enable.nix ./declare-loaOfSub-any-enable.nix
-checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-if-loaOfSub-foo-enable.nix
-checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-loaOfSub-if-foo-enable.nix
-checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-loaOfSub-foo-if-enable.nix
-checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-enable-if.nix
-checkConfigOutput 'true' "$@" ./define-enable.nix ./define-if-loaOfSub-foo-enable.nix
-checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-if-foo-enable.nix
-checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-if-enable.nix
-checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-enable-if.nix
+checkConfigError 'attribute .*foo.* .* not found' config.attrsOfSub.foo.enable ./declare-enable.nix ./declare-attrsOfSub-any-enable.nix
+set -- config.attrsOfSub.foo.enable ./declare-enable.nix ./declare-attrsOfSub-any-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-if-attrsOfSub-foo-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-attrsOfSub-if-foo-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-attrsOfSub-foo-if-enable.nix
+checkConfigOutput 'false' "$@" ./define-attrsOfSub-foo-enable-if.nix
+checkConfigOutput 'true' "$@" ./define-enable.nix ./define-if-attrsOfSub-foo-enable.nix
+checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-if-foo-enable.nix
+checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-foo-if-enable.nix
+checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-foo-enable-if.nix
 
 # Check disabledModules with config definitions and option declarations.
 set -- config.enable ./define-enable.nix ./declare-enable.nix
@@ -138,7 +138,7 @@ checkConfigError 'while evaluating the module argument .*custom.* in .*import-cu
 checkConfigError 'infinite recursion encountered' "$@"
 
 # Check _module.check.
-set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-loaOfSub-foo.nix
+set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix
 checkConfigError 'The option .* defined in .* does not exist.' "$@"
 checkConfigOutput "true" "$@" ./define-module-check.nix
 
@@ -152,18 +152,31 @@ 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 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
 
-# Check loaOf with long list.
-checkConfigOutput "1 2 3 4 5 6 7 8 9 10" config.result ./loaOf-with-long-list.nix
-
-# Check loaOf with many merges of lists.
-checkConfigOutput "1 2 3 4 5 6 7 8 9 10" config.result ./loaOf-with-many-list-merges.nix
-
 # Check mkAliasOptionModule.
 checkConfigOutput "true" config.enable ./alias-with-priority.nix
 checkConfigOutput "true" config.enableAlias ./alias-with-priority.nix
 checkConfigOutput "false" config.enable ./alias-with-priority-can-override.nix
 checkConfigOutput "false" config.enableAlias ./alias-with-priority-can-override.nix
 
+# submoduleWith
+
+## specialArgs should work
+checkConfigOutput "foo" config.submodule.foo ./declare-submoduleWith-special.nix
+
+## shorthandOnlyDefines config behaves as expected
+checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
+checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
+checkConfigError 'value is a boolean while a set was expected' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
+checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
+
+## 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
+
+## Paths should be allowed as values and work as expected
+# Temporarily disabled until https://github.com/NixOS/nixpkgs/pull/76861
+#checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
+
 cat <<EOF
 ====== module tests ======
 $pass Pass
diff --git a/lib/tests/modules/declare-loaOfSub-any-enable.nix b/lib/tests/modules/declare-attrsOfSub-any-enable.nix
index 71dad1c9135..986d07227e1 100644
--- a/lib/tests/modules/declare-loaOfSub-any-enable.nix
+++ b/lib/tests/modules/declare-attrsOfSub-any-enable.nix
@@ -17,10 +17,10 @@ in
 
 {
   options = {
-    loaOfSub = lib.mkOption {
+    attrsOfSub = lib.mkOption {
       default = {};
       example = {};
-      type = lib.types.loaOf (lib.types.submodule [ submod ]);
+      type = lib.types.attrsOf (lib.types.submodule [ submod ]);
       description = ''
         Some descriptive text
       '';
diff --git a/lib/tests/modules/declare-submoduleWith-modules.nix b/lib/tests/modules/declare-submoduleWith-modules.nix
new file mode 100644
index 00000000000..4736ab41751
--- /dev/null
+++ b/lib/tests/modules/declare-submoduleWith-modules.nix
@@ -0,0 +1,30 @@
+{ lib, ... }: {
+  options.submodule = lib.mkOption {
+    type = lib.types.submoduleWith {
+      modules = [
+        {
+          options.inner = lib.mkOption {
+            type = lib.types.bool;
+            default = false;
+          };
+        }
+        {
+          outer = true;
+        }
+      ];
+    };
+    default = {};
+  };
+
+  config.submodule = lib.mkMerge [
+    ({ lib, ... }: {
+      options.outer = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+      };
+    })
+    {
+      inner = true;
+    }
+  ];
+}
diff --git a/lib/tests/modules/declare-submoduleWith-noshorthand.nix b/lib/tests/modules/declare-submoduleWith-noshorthand.nix
new file mode 100644
index 00000000000..af3b4ba470f
--- /dev/null
+++ b/lib/tests/modules/declare-submoduleWith-noshorthand.nix
@@ -0,0 +1,13 @@
+{ lib, ... }: let
+  sub.options.config = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+  };
+in {
+  options.submodule = lib.mkOption {
+    type = lib.types.submoduleWith {
+      modules = [ sub ];
+    };
+    default = {};
+  };
+}
diff --git a/lib/tests/modules/declare-submoduleWith-path.nix b/lib/tests/modules/declare-submoduleWith-path.nix
new file mode 100644
index 00000000000..477647f3212
--- /dev/null
+++ b/lib/tests/modules/declare-submoduleWith-path.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+  options.submodule = lib.mkOption {
+    type = lib.types.submoduleWith {
+      modules = [
+        ./declare-enable.nix
+      ];
+    };
+    default = {};
+  };
+
+  config.submodule = ./define-enable.nix;
+}
diff --git a/lib/tests/modules/declare-submoduleWith-shorthand.nix b/lib/tests/modules/declare-submoduleWith-shorthand.nix
new file mode 100644
index 00000000000..63ac16293e2
--- /dev/null
+++ b/lib/tests/modules/declare-submoduleWith-shorthand.nix
@@ -0,0 +1,14 @@
+{ lib, ... }: let
+  sub.options.config = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+  };
+in {
+  options.submodule = lib.mkOption {
+    type = lib.types.submoduleWith {
+      modules = [ sub ];
+      shorthandOnlyDefinesConfig = true;
+    };
+    default = {};
+  };
+}
diff --git a/lib/tests/modules/declare-submoduleWith-special.nix b/lib/tests/modules/declare-submoduleWith-special.nix
new file mode 100644
index 00000000000..6b15c5bde20
--- /dev/null
+++ b/lib/tests/modules/declare-submoduleWith-special.nix
@@ -0,0 +1,17 @@
+{ lib, ... }: {
+  options.submodule = lib.mkOption {
+    type = lib.types.submoduleWith {
+      modules = [
+        ({ lib, ... }: {
+          options.foo = lib.mkOption {
+            default = lib.foo;
+          };
+        })
+      ];
+      specialArgs.lib = lib // {
+        foo = "foo";
+      };
+    };
+    default = {};
+  };
+}
diff --git a/lib/tests/modules/define-attrsOfSub-bar-enable.nix b/lib/tests/modules/define-attrsOfSub-bar-enable.nix
new file mode 100644
index 00000000000..99c55d8b360
--- /dev/null
+++ b/lib/tests/modules/define-attrsOfSub-bar-enable.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.bar.enable = true;
+}
diff --git a/lib/tests/modules/define-attrsOfSub-bar.nix b/lib/tests/modules/define-attrsOfSub-bar.nix
new file mode 100644
index 00000000000..2a33068a568
--- /dev/null
+++ b/lib/tests/modules/define-attrsOfSub-bar.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.bar = {};
+}
diff --git a/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix b/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix
new file mode 100644
index 00000000000..c9ee36446f1
--- /dev/null
+++ b/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+  attrsOfSub.foo.enable = lib.mkForce false;
+}
diff --git a/lib/tests/modules/define-attrsOfSub-foo-enable-if.nix b/lib/tests/modules/define-attrsOfSub-foo-enable-if.nix
new file mode 100644
index 00000000000..0b3baddb5ec
--- /dev/null
+++ b/lib/tests/modules/define-attrsOfSub-foo-enable-if.nix
@@ -0,0 +1,5 @@
+{ config, lib, ... }:
+
+{
+  attrsOfSub.foo.enable = lib.mkIf config.enable true;
+}
diff --git a/lib/tests/modules/define-attrsOfSub-foo-enable.nix b/lib/tests/modules/define-attrsOfSub-foo-enable.nix
new file mode 100644
index 00000000000..39cd63cef72
--- /dev/null
+++ b/lib/tests/modules/define-attrsOfSub-foo-enable.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.foo.enable = true;
+}
diff --git a/lib/tests/modules/define-loaOfSub-foo-force-enable.nix b/lib/tests/modules/define-attrsOfSub-foo-force-enable.nix
index dce0ef547b3..009da7c77cd 100644
--- a/lib/tests/modules/define-loaOfSub-foo-force-enable.nix
+++ b/lib/tests/modules/define-attrsOfSub-foo-force-enable.nix
@@ -1,7 +1,7 @@
 { lib, ... }:
 
 {
-  loaOfSub.foo = lib.mkForce {
+  attrsOfSub.foo = lib.mkForce {
     enable = false;
   };
 }
diff --git a/lib/tests/modules/define-loaOfSub-foo-if-enable.nix b/lib/tests/modules/define-attrsOfSub-foo-if-enable.nix
index 236b2840ee5..93702dfa86f 100644
--- a/lib/tests/modules/define-loaOfSub-foo-if-enable.nix
+++ b/lib/tests/modules/define-attrsOfSub-foo-if-enable.nix
@@ -1,7 +1,7 @@
 { config, lib, ... }:
 
 {
-  loaOfSub.foo = lib.mkIf config.enable {
+  attrsOfSub.foo = lib.mkIf config.enable {
     enable = true;
   };
 }
diff --git a/lib/tests/modules/define-attrsOfSub-foo.nix b/lib/tests/modules/define-attrsOfSub-foo.nix
new file mode 100644
index 00000000000..e6bb531dedd
--- /dev/null
+++ b/lib/tests/modules/define-attrsOfSub-foo.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.foo = {};
+}
diff --git a/lib/tests/modules/define-loaOfSub-force-foo-enable.nix b/lib/tests/modules/define-attrsOfSub-force-foo-enable.nix
index df5722274ee..5c02dd34314 100644
--- a/lib/tests/modules/define-loaOfSub-force-foo-enable.nix
+++ b/lib/tests/modules/define-attrsOfSub-force-foo-enable.nix
@@ -1,7 +1,7 @@
 { lib, ... }:
 
 {
-  loaOfSub = lib.mkForce {
+  attrsOfSub = lib.mkForce {
     foo.enable = false;
   };
 }
diff --git a/lib/tests/modules/define-loaOfSub-if-foo-enable.nix b/lib/tests/modules/define-attrsOfSub-if-foo-enable.nix
index bd2d068d31a..a3fe6051d41 100644
--- a/lib/tests/modules/define-loaOfSub-if-foo-enable.nix
+++ b/lib/tests/modules/define-attrsOfSub-if-foo-enable.nix
@@ -1,7 +1,7 @@
 { config, lib, ... }:
 
 {
-  loaOfSub = lib.mkIf config.enable {
+  attrsOfSub = lib.mkIf config.enable {
     foo.enable = true;
   };
 }
diff --git a/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix b/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix
new file mode 100644
index 00000000000..dafb2360e1f
--- /dev/null
+++ b/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+lib.mkForce {
+  attrsOfSub.foo.enable = false;
+}
diff --git a/lib/tests/modules/define-force-loaOfSub-foo-enable.nix b/lib/tests/modules/define-force-loaOfSub-foo-enable.nix
deleted file mode 100644
index bfd8e084b59..00000000000
--- a/lib/tests/modules/define-force-loaOfSub-foo-enable.nix
+++ /dev/null
@@ -1,5 +0,0 @@
-{ lib, ... }:
-
-lib.mkForce {
-  loaOfSub.foo.enable = false;
-}
diff --git a/lib/tests/modules/define-if-loaOfSub-foo-enable.nix b/lib/tests/modules/define-if-attrsOfSub-foo-enable.nix
index 4288d74dec0..6a8e32e802a 100644
--- a/lib/tests/modules/define-if-loaOfSub-foo-enable.nix
+++ b/lib/tests/modules/define-if-attrsOfSub-foo-enable.nix
@@ -1,5 +1,5 @@
 { config, lib, ... }:
 
 lib.mkIf config.enable {
-  loaOfSub.foo.enable = true;
+  attrsOfSub.foo.enable = true;
 }
diff --git a/lib/tests/modules/define-loaOfSub-bar-enable.nix b/lib/tests/modules/define-loaOfSub-bar-enable.nix
deleted file mode 100644
index 422bb0a600b..00000000000
--- a/lib/tests/modules/define-loaOfSub-bar-enable.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  loaOfSub.bar.enable = true;
-}
diff --git a/lib/tests/modules/define-loaOfSub-bar.nix b/lib/tests/modules/define-loaOfSub-bar.nix
deleted file mode 100644
index c24315e09b6..00000000000
--- a/lib/tests/modules/define-loaOfSub-bar.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  loaOfSub.bar = {};
-}
diff --git a/lib/tests/modules/define-loaOfSub-foo-enable-force.nix b/lib/tests/modules/define-loaOfSub-foo-enable-force.nix
deleted file mode 100644
index c1d7b198be5..00000000000
--- a/lib/tests/modules/define-loaOfSub-foo-enable-force.nix
+++ /dev/null
@@ -1,5 +0,0 @@
-{ lib, ... }:
-
-{
-  loaOfSub.foo.enable = lib.mkForce false;
-}
diff --git a/lib/tests/modules/define-loaOfSub-foo-enable-if.nix b/lib/tests/modules/define-loaOfSub-foo-enable-if.nix
deleted file mode 100644
index 44b2c96cd02..00000000000
--- a/lib/tests/modules/define-loaOfSub-foo-enable-if.nix
+++ /dev/null
@@ -1,5 +0,0 @@
-{ config, lib, ... }:
-
-{
-  loaOfSub.foo.enable = lib.mkIf config.enable true;
-}
diff --git a/lib/tests/modules/define-loaOfSub-foo-enable.nix b/lib/tests/modules/define-loaOfSub-foo-enable.nix
deleted file mode 100644
index 822425c71bb..00000000000
--- a/lib/tests/modules/define-loaOfSub-foo-enable.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  loaOfSub.foo.enable = true;
-}
diff --git a/lib/tests/modules/define-loaOfSub-foo.nix b/lib/tests/modules/define-loaOfSub-foo.nix
deleted file mode 100644
index e9b2e631f2e..00000000000
--- a/lib/tests/modules/define-loaOfSub-foo.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  loaOfSub.foo = {};
-}
diff --git a/lib/tests/modules/define-submoduleWith-noshorthand.nix b/lib/tests/modules/define-submoduleWith-noshorthand.nix
new file mode 100644
index 00000000000..35e1607b6f1
--- /dev/null
+++ b/lib/tests/modules/define-submoduleWith-noshorthand.nix
@@ -0,0 +1,3 @@
+{
+  submodule.config.config = true;
+}
diff --git a/lib/tests/modules/define-submoduleWith-shorthand.nix b/lib/tests/modules/define-submoduleWith-shorthand.nix
new file mode 100644
index 00000000000..17df248db8e
--- /dev/null
+++ b/lib/tests/modules/define-submoduleWith-shorthand.nix
@@ -0,0 +1,3 @@
+{
+  submodule.config = true;
+}
diff --git a/lib/tests/modules/loaOf-with-long-list.nix b/lib/tests/modules/loaOf-with-long-list.nix
deleted file mode 100644
index f30903c47e5..00000000000
--- a/lib/tests/modules/loaOf-with-long-list.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-{ config, lib, ... }:
-
-{
-  options = {
-    loaOfInt = lib.mkOption {
-      type = lib.types.loaOf lib.types.int;
-    };
-
-    result = lib.mkOption {
-      type = lib.types.str;
-    };
-  };
-
-  config = {
-    loaOfInt = [ 1 2 3 4 5 6 7 8 9 10 ];
-
-    result = toString (lib.attrValues config.loaOfInt);
-  };
-}
diff --git a/lib/tests/modules/loaOf-with-many-list-merges.nix b/lib/tests/modules/loaOf-with-many-list-merges.nix
deleted file mode 100644
index f8f8a8da82b..00000000000
--- a/lib/tests/modules/loaOf-with-many-list-merges.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-{ config, lib, ... }:
-
-{
-  options = {
-    loaOfInt = lib.mkOption {
-      type = lib.types.loaOf lib.types.int;
-    };
-
-    result = lib.mkOption {
-      type = lib.types.str;
-    };
-  };
-
-  config = {
-    loaOfInt = lib.mkMerge (map lib.singleton [ 1 2 3 4 5 6 7 8 9 10 ]);
-
-    result = toString (lib.attrValues config.loaOfInt);
-  };
-}
diff --git a/lib/tests/systems.nix b/lib/tests/systems.nix
index 818749442f9..6f52912994d 100644
--- a/lib/tests/systems.nix
+++ b/lib/tests/systems.nix
@@ -12,7 +12,7 @@ let
     expected = lib.sort lib.lessThan y;
   };
 in with lib.systems.doubles; lib.runTests {
-  testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded);
+  testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ js);
 
   testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "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" ];
diff --git a/lib/types.nix b/lib/types.nix
index 5e9a28ac4f0..4872a676657 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -242,8 +242,7 @@ rec {
 
     path = mkOptionType {
       name = "path";
-      # Hacky: there is no ‘isPath’ primop.
-      check = x: builtins.substring 0 1 (toString x) == "/";
+      check = x: isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
       merge = mergeEqualOption;
     };
 
@@ -295,26 +294,43 @@ rec {
     # List or attribute set of ...
     loaOf = elemType:
       let
-        convertAllLists = defs:
+        convertAllLists = loc: defs:
           let
             padWidth = stringLength (toString (length defs));
             unnamedPrefix = i: "unnamed-" + fixedWidthNumber padWidth i + ".";
           in
-            imap1 (i: convertIfList (unnamedPrefix i)) defs;
-
-        convertIfList = unnamedPrefix: def:
+            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;
+              res =
+                { inherit (def) file;
+                  value = listToAttrs (
+                    imap1 (elemIdx: elem:
+                      { name  = elem.name or (unnamed elemIdx);
+                        value = elem;
+                      }) def.value);
+                };
+              option = concatStringsSep "." loc;
+              sample = take 3 def.value;
+              list = concatMapStrings (x: ''{ name = "${x.name or "unnamed"}"; ...} '') sample;
+              set = concatMapStrings (x: ''${x.name or "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://git.io/fj2zm for more information.
+                Do
+                  ${option} =
+                    { ${set}...}
+                instead of
+                  ${option} =
+                    [ ${list}...]
+              '';
             in
-              { inherit (def) file;
-                value = listToAttrs (
-                  imap1 (elemIdx: elem:
-                    { name = elem.name or (unnamed elemIdx);
-                      value = elem;
-                    }) def.value);
-              }
+              lib.warn msg res
           else
             def;
         attrOnly = attrsOf elemType;
@@ -322,7 +338,7 @@ 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 defs);
+        merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs);
         getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
         getSubModules = elemType.getSubModules;
         substSubModules = m: loaOf (elemType.substSubModules m);
@@ -358,25 +374,41 @@ rec {
     };
 
     # A submodule (like typed attribute set). See NixOS manual.
-    submodule = opts:
+    submodule = modules: submoduleWith {
+      shorthandOnlyDefinesConfig = true;
+      modules = toList modules;
+    };
+
+    submoduleWith =
+      { modules
+      , specialArgs ? {}
+      , shorthandOnlyDefinesConfig ? false
+      }@attrs:
       let
-        opts' = toList opts;
         inherit (lib.modules) evalModules;
+
+        coerce = unify: value: if isFunction value
+          then setFunctionArgs (args: unify (value args)) (functionArgs value)
+          else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
+
+        allModules = defs: modules ++ imap1 (n: { value, file }:
+          # Annotate the value with the location of its definition for better error messages
+          coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
+        ) defs;
+
       in
       mkOptionType rec {
         name = "submodule";
         check = x: isAttrs x || isFunction x;
         merge = loc: defs:
-          let
-            coerce = def: if isFunction def then def else { config = def; };
-            modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
-          in (evalModules {
-            inherit modules;
+          (evalModules {
+            modules = allModules defs;
+            inherit specialArgs;
             args.name = last loc;
             prefix = loc;
           }).config;
         getSubOptions = prefix: (evalModules
-          { modules = opts'; inherit prefix;
+          { inherit modules prefix specialArgs;
             # This is a work-around due to the fact that some sub-modules,
             # such as the one included in an attribute set, expects a "args"
             # attribute to be given to the sub-module. As the option
@@ -394,13 +426,29 @@ rec {
             # It shouldn't cause an issue since this is cosmetic for the manual.
             args.name = "‹name›";
           }).options;
-        getSubModules = opts';
-        substSubModules = m: submodule m;
-        functor = (defaultFunctor name) // {
-          # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
-          # each submodule with its location.
-          payload = [];
-          binOp = lhs: rhs: [];
+        getSubModules = modules;
+        substSubModules = m: submoduleWith (attrs // {
+          modules = m;
+        });
+        functor = defaultFunctor name // {
+          type = types.submoduleWith;
+          payload = {
+            modules = modules;
+            specialArgs = specialArgs;
+            shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig;
+          };
+          binOp = lhs: rhs: {
+            modules = lhs.modules ++ rhs.modules;
+            specialArgs =
+              let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
+              in if intersecting == {}
+              then lhs.specialArgs // rhs.specialArgs
+              else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
+            shorthandOnlyDefinesConfig =
+              if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
+              then lhs.shorthandOnlyDefinesConfig
+              else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
+          };
         };
       };