summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-28 00:56:22 +0100
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-28 22:45:55 +0100
commit0e333688cea468a28516bf6935648c03ed62a7bb (patch)
tree06657556c1e80363a51010d546cac68b98fde90a
parentf4dadc5df8561405df9aabf4fa2c2dcd13234b22 (diff)
downloadnixlib-0e333688cea468a28516bf6935648c03ed62a7bb.tar
nixlib-0e333688cea468a28516bf6935648c03ed62a7bb.tar.gz
nixlib-0e333688cea468a28516bf6935648c03ed62a7bb.tar.bz2
nixlib-0e333688cea468a28516bf6935648c03ed62a7bb.tar.lz
nixlib-0e333688cea468a28516bf6935648c03ed62a7bb.tar.xz
nixlib-0e333688cea468a28516bf6935648c03ed62a7bb.tar.zst
nixlib-0e333688cea468a28516bf6935648c03ed62a7bb.zip
Big cleanup of the NixOS module system
The major changes are:

* The evaluation is now driven by the declared options.  In
  particular, this fixes the long-standing problem with lack of
  laziness of disabled option definitions.  Thus, a configuration like

    config = mkIf false {
      environment.systemPackages = throw "bla";
    };

  will now evaluate without throwing an error.  This also improves
  performance since we're not evaluating unused option definitions.

* The implementation of properties is greatly simplified.

* There is a new type constructor "submodule" that replaces
  "optionSet".  Unlike "optionSet", "submodule" gets its option
  declarations as an argument, making it more like "listOf" and other
  type constructors.  A typical use is:

    foo = mkOption {
      type = type.attrsOf (type.submodule (
        { config, ... }:
        { bar = mkOption { ... };
          xyzzy = mkOption { ... };
        }));
    };

  Existing uses of "optionSet" are automatically mapped to
  "submodule".

* Modules are now checked for unsupported attributes: you get an error
  if a module contains an attribute other than "config", "options" or
  "imports".

* The new implementation is faster and uses much less memory.
-rw-r--r--lib/default.nix5
-rw-r--r--lib/lists.nix5
-rw-r--r--lib/modules.nix576
-rw-r--r--lib/options.nix46
-rw-r--r--lib/properties.nix464
-rw-r--r--lib/types.nix148
-rw-r--r--nixos/lib/eval-config.nix13
-rw-r--r--nixos/modules/config/shells-environment.nix9
-rw-r--r--nixos/modules/system/activation/activation-script.nix2
9 files changed, 294 insertions, 974 deletions
diff --git a/lib/default.nix b/lib/default.nix
index 033269e6b609..fc92e04503b2 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -8,7 +8,6 @@ let
   sources = import ./sources.nix;
   modules = import ./modules.nix;
   options = import ./options.nix;
-  properties = import ./properties.nix;
   types = import ./types.nix;
   meta = import ./meta.nix;
   debug = import ./debug.nix;
@@ -21,13 +20,13 @@ let
 
 in
   { inherit trivial lists strings stringsWithDeps attrsets sources options
-      properties modules types meta debug maintainers licenses platforms systems;
+      modules types meta debug maintainers licenses platforms systems;
     # Pull in some builtins not included elsewhere.
     inherit (builtins) pathExists readFile;
   }
   # !!! don't include everything at top-level; perhaps only the most
   # commonly used functions.
   // trivial // lists // strings // stringsWithDeps // attrsets // sources
-  // properties // options // types // meta // debug // misc // modules
+  // options // types // meta // debug // misc // modules
   // systems
   // customisation
diff --git a/lib/lists.nix b/lib/lists.nix
index 262a529b2b6e..eb7e6baf5ed8 100644
--- a/lib/lists.nix
+++ b/lib/lists.nix
@@ -165,10 +165,11 @@ in rec {
 
   zipLists = zipListsWith (fst: snd: { inherit fst snd; });
 
-  
-  # Reverse the order of the elements of a list.
+
+  # Reverse the order of the elements of a list.  FIXME: O(n^2)!
   reverseList = fold (e: acc: acc ++ [ e ]) [];
 
+
   # Sort a list based on a comparator function which compares two
   # elements and returns true if the first argument is strictly below
   # the second argument.  The returned list is sorted in an increasing
diff --git a/lib/modules.nix b/lib/modules.nix
index f914947e7849..f5a82d7d8e9d 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -1,379 +1,245 @@
-# NixOS module handling.
-
-let lib = import ./default.nix; in
-
-with { inherit (builtins) head; };
-with import ./trivial.nix;
-with import ./lists.nix;
-with import ./misc.nix;
-with import ./attrsets.nix;
-with import ./options.nix;
-with import ./properties.nix;
+with import ./.. {};
+with lib;
 
 rec {
 
-  # Unfortunately this can also be a string.
-  isPath = x: !(
-     builtins.isFunction x
-  || builtins.isAttrs x
-  || builtins.isInt x
-  || builtins.isBool x
-  || builtins.isList x
-  );
-
-
-  importIfPath = path:
-    if isPath path then
-      import path
-    else
-      path;
-
-
-  applyIfFunction = f: arg:
-    if builtins.isFunction f then
-      f arg
-    else
-      f;
-
-
-  isModule = m:
-       (m ? config && isAttrs m.config && ! isOption m.config)
-    || (m ? options && isAttrs m.options && ! isOption m.options);
-
-
-  # Convert module to a set which has imports / options and config
-  # attributes.
-  unifyModuleSyntax = m:
+  /* Evaluate a set of modules.  The result is a set of two
+     attributes: ‘options’: the nested set of all option declarations,
+     and ‘config’: the nested set of all option values. */
+  evalModules = modules: args:
     let
-      delayedModule = delayProperties m;
-
-      getImports =
-        toList (rmProperties (delayedModule.require or []));
-      getImportedPaths = filter isPath getImports;
-      getImportedSets = filter (x: !isPath x) getImports;
-
-      getConfig =
-        removeAttrs delayedModule ["require" "key" "imports"];
-
-    in
-      if isModule m then
-        { key = "<unknown location>"; } // m
-      else
-        { key = "<unknown location>";
-          imports = (m.imports or []) ++ getImportedPaths;
-          config = getConfig;
-        } // (
-          if getImportedSets != [] then
-            assert length getImportedSets == 1;
-            { options = head getImportedSets; }
-          else
-            {}
-        );
-
-
-  unifyOptionModule = {key ? "<unknown location>"}: name: index: m: (args:
+      args' = args // result;
+      closed = closeModules modules args';
+      # Note: the list of modules is reversed to maintain backward
+      # compatibility with the old module system.  Not sure if this is
+      # the most sensible policy.
+      options = mergeModules (reverseList closed);
+      config = yieldConfig options;
+      yieldConfig = mapAttrs (n: v: if isOption v then v.value else yieldConfig v);
+      result = { inherit options config; };
+    in result;
+
+  /* Close a set of modules under the ‘imports’ relation. */
+  closeModules = modules: args:
     let
-      module = lib.applyIfFunction m args;
-      key_ = rec {
-        file = key;
-        option = name;
-        number = index;
-        outPath = key;
+      coerceToModule = n: x:
+        if isAttrs x || builtins.isFunction x then
+          unifyModuleSyntax "anon-${toString n}" (applyIfFunction x args)
+        else
+          unifyModuleSyntax (toString x) (applyIfFunction (import x) args);
+      toClosureList = imap (path: coerceToModule path);
+    in
+      builtins.genericClosure {
+        startSet = toClosureList modules;
+        operator = m: toClosureList m.imports;
       };
-    in if lib.isModule module then
-      { key = key_; } // module
-    else
-      { key = key_; options = module; }
-  );
 
+  /* Massage a module into canonical form, that is, a set consisting
+     of ‘options’, ‘config’ and ‘imports’ attributes. */
+  unifyModuleSyntax = key: m:
+    if m ? config || m ? options || m ? imports then
+      let badAttrs = removeAttrs m ["imports" "options" "config"]; in
+      if badAttrs != {} then
+        throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. ${builtins.toXML m} "
+      else
+        { inherit key;
+          imports = m.imports or [];
+          options = m.options or {};
+          config = m.config or {};
+        }
+    else
+      { inherit key;
+        imports = m.require or [];
+        options = {};
+        config = m;
+      };
 
-  moduleClosure = initModules: args:
+  applyIfFunction = f: arg: if builtins.isFunction f then f arg else f;
+
+  /* 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
+     corresponding option definitions in all machines, returning them
+     in the ‘value’ attribute of each option. */
+  mergeModules = modules:
+    mergeModules' [] (map (m: m.options) modules) (concatMap (m: pushDownProperties m.config) modules);
+
+  mergeModules' = loc: options: configs:
+    zipAttrsWith (name: vals:
+      let loc' = loc ++ [name]; in
+      if all isOption vals then
+        let opt = fixupOptionType loc' (mergeOptionDecls loc' vals);
+        in evalOptionValue loc' opt (catAttrs name configs)
+      else if any isOption vals then
+        throw "There are options with the prefix `${showOption loc'}', which is itself an option."
+      else
+        mergeModules' loc' vals (concatMap pushDownProperties (catAttrs name configs))
+    ) options;
+
+  /* Merge multiple option declarations into a single declaration.  In
+     general, there should be only one declaration of each option.
+     The exception is the ‘options’ attribute, which specifies
+     sub-options.  These can be specified multiple times to allow one
+     module to add sub-options to an option declared somewhere else
+     (e.g. multiple modules define sub-options for ‘fileSystems’). */
+  mergeOptionDecls = loc: opts:
+    fold (opt1: opt2:
+      if opt1 ? default && opt2 ? default ||
+         opt1 ? example && opt2 ? example ||
+         opt1 ? description && opt2 ? description ||
+         opt1 ? merge && opt2 ? merge ||
+         opt1 ? apply && opt2 ? apply ||
+         opt1 ? type && opt2 ? type
+      then
+        throw "Conflicting declarations of the option `${showOption loc}'."
+      else
+        opt1 // opt2
+          // optionalAttrs (opt1 ? options && opt2 ? options)
+            { options = [ opt1.options opt2.options ]; }
+    ) {} opts;
+
+  /* Merge all the definitions of an option to produce the final
+     config value. */
+  evalOptionValue = loc: opt: defs:
     let
-      moduleImport = origin: index: m:
-        let m' = applyIfFunction (importIfPath m) args;
-        in (unifyModuleSyntax m') // {
-          # used by generic closure to avoid duplicated imports.
-          key =
-            if isPath m then m
-            else m'.key or (newModuleName origin index);
-        };
-
-      getImports = m: m.imports or [];
-
-      newModuleName = origin: index:
-        "${origin.key}:<import-${toString index}>";
-
-      topLevel = {
-        key = "<top-level>";
+      # Process mkMerge and mkIf properties.
+      defs' = concatMap dischargeProperties defs;
+      # Process mkOverride properties, adding in the default
+      # value specified in the option declaration (if any).
+      defsFinal = filterOverrides (optional (opt ? default) (mkOptionDefault opt.default) ++ defs');
+      # Type-check the remaining definitions, and merge them
+      # if possible.
+      merged =
+        if defsFinal == [] then
+          throw "The option `${showOption loc}' is used but not defined."
+        else
+          if all opt.type.check defsFinal then
+            opt.type.merge defsFinal
+            #throw "The option `${showOption loc}' has multiple values (with no way to merge them)."
+          else
+            throw "A value of the option `${showOption loc}' has a bad type.";
+      # Finally, apply the ‘apply’ function to the merged
+      # value.  This allows options to yield a value computed
+      # from the definitions.
+      value = (opt.apply or id) merged;
+    in opt //
+      { inherit value;
+        definitions = defsFinal;
+        isDefined = defsFinal != [];
       };
 
-    in
-      (lazyGenericClosure {
-        startSet = imap (moduleImport topLevel) initModules;
-        operator = m: imap (moduleImport m) (getImports m);
-      });
+  /* Given a config set, expand mkMerge properties, and push down the
+     mkIf properties into the children.  The result is a list of
+     config sets that do not have properties at top-level.  For
+     example,
 
+       mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
 
-  moduleApply = funs: module:
-    lib.mapAttrs (name: value:
-      if builtins.hasAttr name funs then
-        let fun = lib.getAttr name funs; in
-        fun value
-      else
-        value
-    ) module;
+     is transformed into
 
+       [ { boot = set1; } { boot = mkIf cond set2; services mkIf cond set3; } ].
 
-  # Handle mkMerge function left behind after a delay property.
-  moduleFlattenMerge = module:
-    if module ? config &&
-       isProperty module.config &&
-       isMerge module.config.property
-    then
-      (map (cfg: { key = module.key; config = cfg; }) module.config.content)
-      ++ [ (module // { config = {}; }) ]
+     This transform is the critical step that allows mkIf conditions
+     to refer to the full configuration without creating an infinite
+     recursion.
+  */
+  pushDownProperties = cfg:
+    if cfg._type or "" == "merge" then
+      concatMap pushDownProperties cfg.contents
+    else if cfg._type or "" == "if" then
+      map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
     else
-      [ module ];
-
-
-  # Handle mkMerge attributes which are left behind by previous delay
-  # properties and convert them into a list of modules. Delay properties
-  # inside the config attribute of a module and create a second module if a
-  # mkMerge attribute was left behind.
-  #
-  # Module -> [ Module ]
-  delayModule = module:
-    map (moduleApply { config = delayProperties; }) (moduleFlattenMerge module);
-
-
-  evalDefinitions = opt: values:
-    if opt.type.delayOnGlobalEval or false then
-      map (delayPropertiesWithIter opt.type.iter opt.name)
-        (evalLocalProperties values)
+      # FIXME: handle mkOverride?
+      [ cfg ];
+
+  /* Given a config value, expand mkMerge properties, and discharge
+     any mkIf conditions.  That is, this is the place where mkIf
+     conditions are actually evaluated.  The result is a list of
+     config values.  For example, ‘mkIf false x’ yields ‘[]’,
+     ‘mkIf true x’ yields ‘[x]’, and
+
+       mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
+
+     yields ‘[ 1 2 ]’.
+  */
+  dischargeProperties = def:
+    if def._type or "" == "merge" then
+      concatMap dischargeProperties def.contents
+    else if def._type or "" == "if" then
+      if def.condition then
+        dischargeProperties def.content
+      else
+        [ ]
     else
-      evalProperties values;
-
-
-  selectModule = name: m:
-    { inherit (m) key;
-    } // (
-      if m ? options && builtins.hasAttr name m.options then
-        { options = lib.getAttr name m.options; }
-      else {}
-    ) // (
-      if m ? config && builtins.hasAttr name m.config then
-        { config = lib.getAttr name m.config; }
-      else {}
-    );
-
-  filterModules = name: modules:
-    filter (m: m ? config || m ? options) (
-      map (selectModule name) modules
-    );
+      [ def ];
 
+  /* Given a list of config value, process the mkOverride properties,
+     that is, return the values that have the highest (that
+     is,. numerically lowest) priority, and strip the mkOverride
+     properties.  For example,
 
-  modulesNames = modules:
-    lib.concatMap (m: []
-    ++ optionals (m ? options) (lib.attrNames m.options)
-    ++ optionals (m ? config) (lib.attrNames m.config)
-    ) modules;
+       [ (mkOverride 10 "a") (mkOverride 20 "b") "z" (mkOverride 10 "d")  ]
 
-
-  moduleZip = funs: modules:
-    lib.mapAttrs (name: fun:
-      fun (catAttrs name modules)
-    ) funs;
-
-
-  moduleMerge = path: modules_:
+     yields ‘[ "a" "d" ]’.  Note that "z" has the default priority 100.
+  */
+  filterOverrides = defs:
     let
-      addName = name:
-        if path == "" then name else path + "." + name;
-
-      modules = concatLists (map delayModule modules_);
-
-      modulesOf = name: filterModules name modules;
-      declarationsOf = name: filter (m: m ? options) (modulesOf name);
-      definitionsOf  = name: filter (m: m ? config ) (modulesOf name);
-
-      recurseInto = name:
-        moduleMerge (addName name) (modulesOf name);
-
-      recurseForOption = name: modules: args:
-        moduleMerge name (
-          moduleClosure modules args
-        );
-
-      errorSource = modules:
-        "The error may come from the following files:\n" + (
-          lib.concatStringsSep "\n" (
-            map (m:
-              if m ? key then toString m.key else "<unknown location>"
-            ) modules
-          )
-        );
-
-      eol = "\n";
-
-      allNames = modulesNames modules;
-
-      getResults = m:
-        let fetchResult = s: mapAttrs (n: v: v.result) s; in {
-          options = fetchResult m.options;
-          config = fetchResult m.config;
-        };
-
-      endRecursion =  { options = {}; config = {}; };
-
-    in if modules == [] then endRecursion else
-      getResults (fix (crossResults: moduleZip {
-        options = lib.zipWithNames allNames (name: values: rec {
-          config = lib.getAttr name crossResults.config;
-
-          declarations = declarationsOf name;
-          declarationSources =
-            map (m: {
-              source = m.key;
-            }) declarations;
-
-          hasOptions = values != [];
-          isOption = any lib.isOption values;
-
-          decls = # add location to sub-module options.
-            map (m:
-              mapSubOptions
-                (unifyOptionModule {inherit (m) key;} name)
-                m.options
-            ) declarations;
-
-          decl =
-            lib.addErrorContext "${eol
-              }while enhancing option `${addName name}':${eol
-              }${errorSource declarations}${eol
-            }" (
-              addOptionMakeUp
-                { name = addName name; recurseInto = recurseForOption; }
-                (mergeOptionDecls decls)
-            );
-
-          value = decl // (with config; {
-            inherit (config) isNotDefined;
-            isDefined = ! isNotDefined;
-            declarations = declarationSources;
-            definitions = definitionSources;
-            config = strictResult;
-          });
-
-          recurse = (recurseInto name).options;
-
-          result =
-            if isOption then value
-            else if !hasOptions then {}
-            else if all isAttrs values then recurse
-            else
-              throw "${eol
-                }Unexpected type where option declarations are expected.${eol
-                }${errorSource declarations}${eol
-              }";
-
-        });
-
-        config = lib.zipWithNames allNames (name: values_: rec {
-          option = lib.getAttr name crossResults.options;
-
-          definitions = definitionsOf name;
-          definitionSources =
-            map (m: {
-              source = m.key;
-              value = m.config;
-            }) definitions;
-
-          values = values_ ++
-            optionals (option.isOption && option.decl ? extraConfigs)
-              option.decl.extraConfigs;
-
-          defs = evalDefinitions option.decl values;
-
-          isNotDefined = defs == [];
-
-          value =
-            lib.addErrorContext "${eol
-              }while evaluating the option `${addName name}':${eol
-              }${errorSource (modulesOf name)}${eol
-            }" (
-              let opt = option.decl; in
-              opt.apply (
-                if isNotDefined then
-                  opt.default or (throw "Option `${addName name}' not defined and does not have a default value.")
-                else opt.merge defs
-              )
-            );
-
-          strictResult = builtins.tryEval (builtins.toXML value);
-
-          recurse = (recurseInto name).config;
-
-          configIsAnOption = v: isOption (rmProperties v);
-          errConfigIsAnOption =
-            let badModules = filter (m: configIsAnOption m.config) definitions; in
-            "${eol
-              }Option ${addName name} is defined in the configuration section.${eol
-              }${errorSource badModules}${eol
-            }";
-
-          errDefinedWithoutDeclaration =
-            let badModules = definitions; in
-            "${eol
-              }Option '${addName name}' defined without option declaration.${eol
-              }${errorSource badModules}${eol
-            }";
-
-          result =
-            if option.isOption then value
-            else if !option.hasOptions then throw errDefinedWithoutDeclaration
-            else if any configIsAnOption values then throw errConfigIsAnOption
-            else if all isAttrs values then recurse
-            # plain value during the traversal
-            else throw errDefinedWithoutDeclaration;
-
-        });
-      } modules));
-
-
-  fixMergeModules = initModules: {...}@args:
-    lib.fix (result:
-      # This trick avoids an infinite loop because names of attribute
-      # are know and it is not required to evaluate the result of
-      # moduleMerge to know which attributes are present as arguments.
-      let module = { inherit (result) options config; }; in
-      moduleMerge "" (
-        moduleClosure initModules (module // args)
-      )
-    );
-
-
-  # Visit all definitions to raise errors related to undeclared options.
-  checkModule = path: {config, options, ...}@m:
+      defaultPrio = 100;
+      getPrio = def: if def._type or "" == "override" then def.priority else defaultPrio;
+      min = x: y: if x < y then x else y;
+      highestPrio = fold (def: prio: min (getPrio def) prio) 9999 defs;
+      strip = def: if def._type or "" == "override" then def.content else def;
+    in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
+
+  /* Hack for backward compatibility: convert options of type
+     optionSet to configOf.  FIXME: remove eventually. */
+  fixupOptionType = loc: opt:
     let
-      eol = "\n";
-      addName = name:
-        if path == "" then name else path + "." + name;
-    in
-    if lib.isOption options then
-      if options ? options then
-        options.type.fold
-          (cfg: res: res && checkModule (options.type.docPath path) cfg._args)
-          true config
-      else
-        true
-    else if isAttrs options && lib.attrNames m.options != [] then
-      all (name:
-        lib.addErrorContext "${eol
-          }while checking the attribute `${addName name}':${eol
-        }" (checkModule (addName name) (selectModule name m))
-      ) (lib.attrNames m.config)
-    else
-      builtins.trace "try to evaluate config ${lib.showVal config}."
-      false;
+      options' = opt.options or
+        (throw "Option `${showOption loc'}' has type optionSet but has no option attribute.");
+      coerce = x:
+        if builtins.isFunction x then x
+        else { config, ... }: { options = x; };
+      options = map coerce (flatten options');
+      f = tp:
+        if tp.name == "option set" then types.submodule options
+        else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options)
+        else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options)
+        else if tp.name == "list of option sets" then types.listOf (types.submodule options)
+        else if tp.name == "null or option set" then types.nullOr (types.submodule options)
+        else tp;
+    in opt // { type = f (opt.type or types.unspecified); };
+
+
+  /* Properties. */
+
+  mkIf = condition: content:
+    { _type = "if";
+      inherit condition content;
+    };
+
+  mkAssert = assertion: message: content:
+    mkIf
+      (if assertion then true else throw "\nFailed assertion: ${message}")
+      content;
+
+  mkMerge = contents:
+    { _type = "merge";
+      inherit contents;
+    };
+
+  mkOverride = priority: content:
+    { _type = "override";
+      inherit priority content;
+    };
+
+  mkOptionDefault = mkOverride 1001;
+  mkDefault = mkOverride 1000;
+  mkForce = mkOverride 50;
+
+  mkFixStrictness = id; # obsolete, no-op
+
+  # FIXME: Add mkOrder back in. It's not currently used anywhere in
+  # NixOS, but it should be useful.
 
 }
diff --git a/lib/options.nix b/lib/options.nix
index b6a88008bb7a..0fa702a616c6 100644
--- a/lib/options.nix
+++ b/lib/options.nix
@@ -2,12 +2,10 @@
 
 let lib = import ./default.nix; in
 
-with { inherit (builtins) head length; };
 with import ./trivial.nix;
 with import ./lists.nix;
 with import ./misc.nix;
 with import ./attrsets.nix;
-with import ./properties.nix;
 
 rec {
 
@@ -137,46 +135,6 @@ rec {
         handleOptionSets
       ];
 
-  # Merge a list of options containning different field.  This is useful to
-  # separate the merge & apply fields from the interface.
-  mergeOptionDecls = opts:
-    if opts == [] then {}
-    else if length opts == 1 then
-      let opt = head opts; in
-      if opt ? options then
-        opt // { options = toList opt.options; }
-      else
-        opt
-    else
-      fold (opt1: opt2:
-        lib.addErrorContext "opt1 = ${lib.showVal opt1}\nopt2 = ${lib.showVal opt2}" (
-        # You cannot merge if two options have the same field.
-        assert opt1 ? default -> ! opt2 ? default;
-        assert opt1 ? example -> ! opt2 ? example;
-        assert opt1 ? description -> ! opt2 ? description;
-        assert opt1 ? merge -> ! opt2 ? merge;
-        assert opt1 ? apply -> ! opt2 ? apply;
-        assert opt1 ? type -> ! opt2 ? type;
-        opt1 // opt2
-        // optionalAttrs (opt1 ? options || opt2 ? options) {
-            options =
-               (toList (opt1.options or []))
-            ++ (toList (opt2.options or []));
-          }
-        // optionalAttrs (opt1 ? extraConfigs || opt2 ? extraConfigs) {
-            extraConfigs = opt1.extraConfigs or [] ++ opt2.extraConfigs or [];
-          }
-        // optionalAttrs (opt1 ? extraArgs || opt2 ? extraArgs) {
-            extraArgs = opt1.extraArgs or {} // opt2.extraArgs or {};
-          }
-        // optionalAttrs (opt1 ? individualExtraArgs || opt2 ? individualExtraArgs) {
-            individualExtraArgs = zipAttrsWith (name: values:
-              if length values == 1 then head values else (head values // (head (tail values)))
-            ) [ (opt1.individualExtraArgs or {}) (opt2.individualExtraArgs or {}) ];
-          }
-      )) {} opts;
-
-  
   # !!! This function will be removed because this can be done with the
   # multiple option declarations.
   addDefaultOptionValues = defs: opts: opts //
@@ -285,6 +243,7 @@ rec {
             else
               [];
         in
+          # FIXME: expensive (O(n^2)
           [ docOption ] ++ subOptions ++ rest
       ) [] options;
 
@@ -308,4 +267,7 @@ rec {
   literalExample = text: { _type = "literalExample"; inherit text; };
 
 
+  /* Helper functions. */
+  showOption = concatStringsSep ".";
+
 }
diff --git a/lib/properties.nix b/lib/properties.nix
deleted file mode 100644
index 22aa8d891d8a..000000000000
--- a/lib/properties.nix
+++ /dev/null
@@ -1,464 +0,0 @@
-# Nixpkgs/NixOS properties.  Generalize the problem of delayable (not yet
-# evaluable) properties like mkIf.
-
-let lib = import ./default.nix; in
-
-with { inherit (builtins) head tail; };
-with import ./trivial.nix;
-with import ./lists.nix;
-with import ./misc.nix;
-with import ./attrsets.nix;
-
-rec {
-
-  inherit (lib) isType;
-
-  # Tell that nothing is defined.  When properties are evaluated, this type
-  # is used to remove an entry.  Thus if your property evaluation semantic
-  # implies that you have to mute the content of an attribute, then your
-  # property should produce this value.
-  isNotdef = isType "notdef";
-  mkNotdef = {_type = "notdef";};
-
-  # General property type, it has a property attribute and a content
-  # attribute.  The property attribute refers to an attribute set which
-  # contains a _type attribute and a list of functions which are used to
-  # evaluate this property.  The content attribute is used to stack properties
-  # on top of each other.
-  #
-  # The optional functions which may be contained in the property attribute
-  # are:
-  #  - onDelay: run on a copied property.
-  #  - onGlobalDelay: run on all copied properties.
-  #  - onEval: run on an evaluated property.
-  #  - onGlobalEval: run on a list of property stack on top of their values.
-  isProperty = isType "property";
-  mkProperty = p@{property, content, ...}: p // {
-    _type = "property";
-  };
-
-  # Go through the stack of properties and apply the function `op' on all
-  # property and call the function `nul' on the final value which is not a
-  # property.  The stack is traversed in reversed order.  The `op' function
-  # should expect a property with a content which have been modified.
-  #
-  # Warning: The `op' function expects only one argument in order to avoid
-  # calls to mkProperties as the argument is already a valid property which
-  # contains the result of the folding inside the content attribute.
-  foldProperty = op: nul: attrs:
-    if isProperty attrs then
-      op (attrs // {
-        content = foldProperty op nul attrs.content;
-      })
-    else
-      nul attrs;
-
-  # Simple function which can be used as the `op' argument of the
-  # foldProperty function.  Properties that you don't want to handle can be
-  # ignored with the `id' function.  `isSearched' is a function which should
-  # check the type of a property and return a boolean value.  `thenFun' and
-  # `elseFun' are functions which behave as the `op' argument of the
-  # foldProperty function.
-  foldFilter = isSearched: thenFun: elseFun: attrs:
-    if isSearched attrs.property then
-      thenFun attrs
-    else
-      elseFun attrs;
-
-
-  # Move properties from the current attribute set to the attribute
-  # contained in this attribute set.  This trigger property handlers called
-  # `onDelay' and `onGlobalDelay'.
-  delayPropertiesWithIter = iter: path: attrs:
-    let cleanAttrs = rmProperties attrs; in
-    if isProperty attrs then
-      iter (a: v:
-        lib.addErrorContext "while moving properties on the attribute `${a}':" (
-          triggerPropertiesGlobalDelay a (
-            triggerPropertiesDelay a (
-              copyProperties attrs v
-      )))) path cleanAttrs
-    else
-      attrs;
-
-  delayProperties = # implicit attrs argument.
-    let
-      # mapAttrs except that it also recurse into potential mkMerge
-      # functions.  This may cause a strictness issue because looking the
-      # type of a string implies evaluating it.
-      iter = fun: path: value:
-        lib.mapAttrs (attr: val:
-          if isProperty val && isMerge val.property then
-            val // { content = map (fun attr) val.content; }
-          else
-            fun attr val
-        ) value;
-    in
-      delayPropertiesWithIter iter "";
-
-  # Call onDelay functions.
-  triggerPropertiesDelay = name: attrs:
-    let
-      callOnDelay = p@{property, ...}:
-        if property ? onDelay then
-          property.onDelay name p
-        else
-          p;
-    in
-      foldProperty callOnDelay id attrs;
-
-  # Call onGlobalDelay functions.
-  triggerPropertiesGlobalDelay = name: attrs:
-    let
-      globalDelayFuns = uniqListExt {
-        getter = property: property._type;
-        inputList = foldProperty (p@{property, content, ...}:
-          if property ? onGlobalDelay then
-            [ property ] ++ content
-          else
-            content
-        ) (a: []) attrs;
-      };
-
-      callOnGlobalDelay = property: content:
-        property.onGlobalDelay name content;
-    in
-      fold callOnGlobalDelay attrs globalDelayFuns;
-
-  # Expect a list of values which may have properties and return the same
-  # list of values where all properties have been evaluated and where all
-  # ignored values are removed.  This trigger property handlers called
-  # `onEval' and `onGlobalEval'.
-  evalProperties = valList:
-    if valList != [] then
-      filter (x: !isNotdef x) (
-        triggerPropertiesGlobalEval (
-          evalLocalProperties valList
-        )
-      )
-    else
-      valList;
-
-  evalLocalProperties = valList:
-    filter (x: !isNotdef x) (
-      map triggerPropertiesEval valList
-    );
-
-  # Call onEval function
-  triggerPropertiesEval = val:
-    foldProperty (p@{property, ...}:
-      if property ? onEval then
-        property.onEval p
-      else
-        p
-    ) id val;
-
-  # Call onGlobalEval function
-  triggerPropertiesGlobalEval = valList:
-    let
-      globalEvalFuns = uniqListExt {
-        getter = property: property._type;
-        inputList =
-          fold (attrs: list:
-            foldProperty (p@{property, content, ...}:
-              if property ? onGlobalEval then
-                [ property ] ++ content
-              else
-                content
-            ) (a: list) attrs
-          ) [] valList;
-      };
-
-      callOnGlobalEval = property: valList: property.onGlobalEval valList;
-    in
-      fold callOnGlobalEval valList globalEvalFuns;
-
-  # Remove all properties on top of a value and return the value.
-  rmProperties =
-    foldProperty (p@{content, ...}: content) id;
-
-  # Copy properties defined on a value on another value.
-  copyProperties = attrs: newAttrs:
-    foldProperty id (x: newAttrs) attrs;
-
-  /* Merge. */
-
-  # Create "merge" statement which is skipped by the delayProperty function
-  # and interpreted by the underlying system using properties (modules).
-
-  # Create a "Merge" property which only contains a condition.
-  isMerge = isType "merge";
-  mkMerge = content: mkProperty {
-    property = {
-      _type = "merge";
-      onDelay = name: val: throw "mkMerge is not the first of the list of properties.";
-      onEval = val: throw "mkMerge is not allowed on option definitions.";
-    };
-    inherit content;
-  };
-
-  /* If. ThenElse. Always. */
-
-  # create "if" statement that can be delayed on sets until a "then-else" or
-  # "always" set is reached.  When an always set is reached the condition
-  # is ignore.
-
-  # Create a "If" property which only contains a condition.
-  isIf = isType "if";
-  mkIf = condition: content: mkProperty {
-    property = {
-      _type = "if";
-      onGlobalDelay = onIfGlobalDelay;
-      onEval = onIfEval;
-      inherit condition;
-    };
-    inherit content;
-  };
-
-  mkAssert = assertion: message: content:
-    mkIf
-      (if assertion then true else throw "\nFailed assertion: ${message}")
-      content;
-
-  # Evaluate the "If" statements when either "ThenElse" or "Always"
-  # statement is encountered.  Otherwise it removes multiple If statements and
-  # replaces them by one "If" statement where the condition is the list of all
-  # conditions joined with a "and" operation.
-  onIfGlobalDelay = name: content:
-    let
-      # extract if statements and non-if statements and repectively put them
-      # in the attribute list and attrs.
-      ifProps =
-        foldProperty
-          (foldFilter (p: isIf p)
-            # then, push the condition inside the list list
-            (p@{property, content, ...}:
-              { inherit (content) attrs;
-                list = [property] ++ content.list;
-              }
-            )
-            # otherwise, add the propertie.
-            (p@{property, content, ...}:
-              { inherit (content) list;
-                attrs = p // { content = content.attrs; };
-              }
-            )
-          )
-          (attrs: { list = []; inherit attrs; })
-          content;
-
-      # compute the list of if statements.
-      evalIf = content: condition: list:
-        if list == [] then
-          mkIf condition content
-        else
-          let p = head list; in
-          evalIf content (condition && p.condition) (tail list);
-    in
-      evalIf ifProps.attrs true ifProps.list;
-
-  # Evaluate the condition of the "If" statement to either get the value or
-  # to ignore the value.
-  onIfEval = p@{property, content, ...}:
-    if property.condition then
-      content
-    else
-      mkNotdef;
-
-  /* mkOverride */
-
-  # Create an "Override" statement which allow the user to define
-  # priorities between values.  The default priority is 100. The lowest
-  # priorities are kept.  The template argument must reproduce the same
-  # attribute set hierarchy to override leaves of the hierarchy.
-  isOverride = isType "override";
-  mkOverrideTemplate = priority: template: content: mkProperty {
-    property = {
-      _type = "override";
-      onDelay = onOverrideDelay;
-      onGlobalEval = onOverrideGlobalEval;
-      inherit priority template;
-    };
-    inherit content;
-  };
-
-  # Like mkOverrideTemplate, but without the template argument.
-  mkOverride = priority: content: mkOverrideTemplate priority {} content;
-
-  # Sugar to override the default value of the option by making a new
-  # default value based on the configuration.
-  mkDefaultValue = mkOverride 1000;
-  mkDefault = mkOverride 1000;
-  mkForce = mkOverride 50;
-  mkStrict = mkOverride 0;
-
-  # Make the template traversal in function of the property traversal.  If
-  # the template define a non-empty attribute set, then the property is
-  # copied only on all mentionned attributes inside the template.
-  # Otherwise, the property is kept on all sub-attribute definitions.
-  onOverrideDelay = name: p@{property, content, ...}:
-    let inherit (property) template; in
-    if isAttrs template && template != {} then
-      if hasAttr name template then
-        p // {
-          property = p.property // {
-            template = builtins.getAttr name template;
-          };
-        }
-      # Do not override the attribute \name\
-      else
-        content
-    # Override values defined inside the attribute \name\.
-    else
-      p;
-
-  # Keep values having lowest priority numbers only throwing away those having
-  # a higher priority assigned.
-  onOverrideGlobalEval = valList:
-    let
-      defaultPrio = 100;
-
-      inherit (builtins) lessThan;
-
-      getPrioVal =
-        foldProperty
-          (foldFilter isOverride
-            (p@{property, content, ...}:
-              if content ? priority && lessThan content.priority property.priority then
-                content
-              else
-                content // {
-                  inherit (property) priority;
-                }
-            )
-            (p@{property, content, ...}:
-              content // {
-                value = p // { content = content.value; };
-              }
-            )
-          ) (value: { inherit value; });
-
-      addDefaultPrio = x:
-        if x ? priority then x
-        else x // { priority = defaultPrio; };
-
-      prioValList = map (x: addDefaultPrio (getPrioVal x)) valList;
-
-      higherPrio =
-        if prioValList == [] then
-          defaultPrio
-        else
-          fold (x: min:
-            if lessThan x.priority min then
-              x.priority
-            else
-              min
-          ) (head prioValList).priority (tail prioValList);
-    in
-      map (x:
-        if x.priority == higherPrio then
-          x.value
-        else
-          mkNotdef
-      ) prioValList;
-
-  /* mkOrder */
-
-  # Order definitions based on there index value.  This property is useful
-  # when the result of the merge function depends on the order on the
-  # initial list.  (e.g. concatStrings) Definitions are ordered based on
-  # their rank.  The lowest ranked definition would be the first to element
-  # of the list used by the merge function.  And the highest ranked
-  # definition would be the last.  Definitions which does not have any rank
-  # value have the default rank of 100.
-  isOrder = isType "order";
-  mkOrder = rank: content: mkProperty {
-    property = {
-      _type = "order";
-      onGlobalEval = onOrderGlobalEval;
-      inherit rank;
-    };
-    inherit content;
-  };
-
-  mkHeader = mkOrder 10;
-  mkFooter = mkOrder 1000;
-
-  # Fetch the rank of each definition (add the default rank is none) and
-  # sort them based on their ranking.
-  onOrderGlobalEval = valList:
-    let
-      defaultRank = 100;
-
-      inherit (builtins) lessThan;
-
-      getRankVal =
-        foldProperty
-          (foldFilter isOrder
-            (p@{property, content, ...}:
-              if content ? rank then
-                content
-              else
-                content // {
-                  inherit (property) rank;
-                }
-            )
-            (p@{property, content, ...}:
-              content // {
-                value = p // { content = content.value; };
-              }
-            )
-          ) (value: { inherit value; });
-
-      addDefaultRank = x:
-        if x ? rank then x
-        else x // { rank = defaultRank; };
-
-      rankValList = map (x: addDefaultRank (getRankVal x)) valList;
-
-      cmp = x: y:
-        builtins.lessThan x.rank y.rank;
-    in
-      map (x: x.value) (sort cmp rankValList);
-
-  /* mkFixStrictness */
-
-  # This is a hack used to restore laziness on some option definitions.
-  # Some option definitions are evaluated when they are not used.  This
-  # error is caused by the strictness of type checking builtins.  Builtins
-  # like 'isAttrs' are too strict because they have to evaluate their
-  # arguments to check if the type is correct.  This evaluation, cause the
-  # strictness of properties.
-  #
-  # Properties can be stacked on top of each other.  The stackability of
-  # properties on top of the option definition is nice for user manipulation
-  # but require to check if the content of the property is not another
-  # property.  Such testing implies to verify if this is an attribute set
-  # and if it possess the type 'property'. (see isProperty & typeOf/isType)
-  #
-  # To avoid strict evaluation of option definitions, 'mkFixStrictness' is
-  # introduced.  This property protects an option definition by replacing
-  # the base of the stack of properties by 'mkNotDef', when this property is
-  # evaluated it returns the original definition.
-  #
-  # This property is useful over any elements which depends on options which
-  # are raising errors when they get evaluated without the proper settings.
-  #
-  # Plain list and attribute set are lazy structures, which means that the
-  # container gets evaluated but not the content.  Thus, using this property
-  # on top of plain list or attribute set is pointless.
-  #
-  # This is a Hack, you should avoid it!
-
-  # This property has a long name because you should avoid it.
-  isFixStrictness = attrs: (typeOf attrs) == "fix-strictness";
-  mkFixStrictness = value:
-    mkProperty {
-      property = {
-        _type = "fix-strictness";
-        onEval = p: value;
-      };
-      content = mkNotdef;
-    };
-
-}
diff --git a/lib/types.nix b/lib/types.nix
index 156d72ac5e73..0545cd6a3c27 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -3,10 +3,11 @@
 
 let lib = import ./default.nix; in
 
-with import ./lists.nix;
-with import ./attrsets.nix;
-with import ./options.nix;
-with import ./trivial.nix;
+with lib.lists;
+with lib.attrsets;
+with lib.options;
+with lib.trivial;
+with lib.modules;
 
 rec {
 
@@ -20,48 +21,43 @@ rec {
 
 
   # name (name of the type)
-  # check (check the config value. Before returning false it should trace the bad value eg using traceValIfNot)
+  # check (check the config value)
   # merge (default merge function)
-  # iter (iterate on all elements contained in this type)
-  # fold (fold all elements contained in this type)
-  # hasOptions (boolean: whatever this option contains an option set)
-  # delayOnGlobalEval (boolean: should properties go through the evaluation of this option)
   # docPath (path concatenated to the option name contained in the option set)
   isOptionType = isType "option-type";
   mkOptionType =
     { name
     , check ? (x: true)
     , merge ? mergeDefaultOption
-    # Handle complex structure types.
-    , iter ? (f: path: v: f path v)
-    , fold ? (op: nul: v: op v nul)
+    , merge' ? args: merge
     , docPath ? lib.id
-    # If the type can contains option sets.
-    , hasOptions ? false
-    , delayOnGlobalEval ? false
     }:
 
     { _type = "option-type";
-      inherit name check merge iter fold docPath hasOptions delayOnGlobalEval;
+      inherit name check merge merge' docPath;
     };
 
 
   types = rec {
 
+    unspecified = mkOptionType {
+      name = "unspecified";
+    };
+
     bool = mkOptionType {
       name = "boolean";
-      check = lib.traceValIfNot builtins.isBool;
+      check = builtins.isBool;
       merge = fold lib.or false;
     };
 
     int = mkOptionType {
       name = "integer";
-      check = lib.traceValIfNot builtins.isInt;
+      check = builtins.isInt;
     };
 
     string = mkOptionType {
       name = "string";
-      check = lib.traceValIfNot builtins.isString;
+      check = builtins.isString;
       merge = lib.concatStrings;
     };
 
@@ -69,7 +65,7 @@ rec {
     # configuration file contents.
     lines = mkOptionType {
       name = "string";
-      check = lib.traceValIfNot builtins.isString;
+      check = builtins.isString;
       merge = lib.concatStringsSep "\n";
     };
 
@@ -81,48 +77,37 @@ rec {
 
     attrs = mkOptionType {
       name = "attribute set";
-      check = lib.traceValIfNot isAttrs;
+      check = isAttrs;
       merge = fold lib.mergeAttrs {};
     };
 
     # derivation is a reserved keyword.
     package = mkOptionType {
       name = "derivation";
-      check = lib.traceValIfNot isDerivation;
+      check = isDerivation;
     };
 
     path = mkOptionType {
       name = "path";
       # Hacky: there is no ‘isPath’ primop.
-      check = lib.traceValIfNot (x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/");
+      check = x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/";
     };
 
     # drop this in the future:
-    list = builtins.trace "types.list is deprecated, use types.listOf instead" types.listOf;
+    list = builtins.trace "types.list is deprecated; use types.listOf instead" types.listOf;
 
-    listOf = elemType: mkOptionType { 
+    listOf = elemType: mkOptionType {
       name = "list of ${elemType.name}s";
-      check = value: lib.traceValIfNot isList value && all elemType.check value;
-      merge = concatLists;
-      iter = f: path: list: map (elemType.iter f (path + ".*")) list;
-      fold = op: nul: list: lib.fold (e: l: elemType.fold op l e) nul list;
+      check = value: isList value && all elemType.check value;
+      merge = defs: map (def: elemType.merge [def]) (concatLists defs);
       docPath = path: elemType.docPath (path + ".*");
-      inherit (elemType) hasOptions;
-
-      # You cannot define multiple configurations of one entity, therefore
-      # no reason justify to delay properties inside list elements.
-      delayOnGlobalEval = false;
     };
 
     attrsOf = elemType: mkOptionType {
       name = "attribute set of ${elemType.name}s";
-      check = x: lib.traceValIfNot isAttrs x
-        && all elemType.check (lib.attrValues x); 
-      merge = lib.zipAttrsWith (name: elemType.merge);
-      iter = f: path: set: lib.mapAttrs (name: elemType.iter f (path + "." + name)) set;
-      fold = op: nul: set: fold (e: l: elemType.fold op l e) nul (lib.attrValues set);
+      check = x: isAttrs x && all elemType.check (lib.attrValues x);
+      merge = lib.zipAttrsWith (name: elemType.merge' { inherit name; });
       docPath = path: elemType.docPath (path + ".<name>");
-      inherit (elemType) hasOptions delayOnGlobalEval;
     };
 
     # List or attribute set of ...
@@ -143,26 +128,13 @@ rec {
         check = x:
           if isList x       then listOnly.check x
           else if isAttrs x then attrOnly.check x
-          else lib.traceValIfNot (x: false) x;
-        ## The merge function returns an attribute set
-        merge = defs:
-          attrOnly.merge (imap convertIfList defs);
-        iter = f: path: def:
-          if isList def       then listOnly.iter f path def
-          else if isAttrs def then attrOnly.iter f path def
-          else throw "Unexpected value";
-        fold = op: nul: def:
-          if isList def       then listOnly.fold op nul def
-          else if isAttrs def then attrOnly.fold op nul def
-          else throw "Unexpected value";
-
+          else false;
+        merge = defs: attrOnly.merge (imap convertIfList defs);
         docPath = path: elemType.docPath (path + ".<name?>");
-        inherit (elemType) hasOptions delayOnGlobalEval;
-      }
-    ;
+      };
 
     uniq = elemType: mkOptionType {
-      inherit (elemType) name check iter fold docPath hasOptions;
+      inherit (elemType) name check docPath;
       merge = list:
         if length list == 1 then
           head list
@@ -171,54 +143,46 @@ rec {
     };
 
     none = elemType: mkOptionType {
-      inherit (elemType) name check iter fold docPath hasOptions;
+      inherit (elemType) name check docPath;
       merge = list:
         throw "No definitions are allowed for this option.";
     };
 
     nullOr = elemType: mkOptionType {
-      inherit (elemType) name merge docPath hasOptions;
+      inherit (elemType) docPath;
+      name = "null or ${elemType.name}";
       check = x: builtins.isNull x || elemType.check x;
-      iter = f: path: v: if v == null then v else elemType.iter f path v;
-      fold = op: nul: v: if v == null then nul else elemType.fold op nul v;
+      merge = defs:
+        if all isNull defs then null
+        else if any isNull defs then
+          throw "Some but not all values are null."
+        else elemType.merge defs;
     };
 
     functionTo = elemType: mkOptionType {
       name = "function that evaluates to a(n) ${elemType.name}";
-      check = lib.traceValIfNot builtins.isFunction;
+      check = builtins.isFunction;
       merge = fns:
         args: elemType.merge (map (fn: fn args) fns);
-      # These are guesses, I don't fully understand iter, fold, delayOnGlobalEval
-      iter = f: path: v:
-        args: elemType.iter f path (v args);
-      fold = op: nul: v:
-        args: elemType.fold op nul (v args);
-      inherit (elemType) delayOnGlobalEval;
-      hasOptions = false;
-    };
-
-    # usually used with listOf, attrsOf, loaOf like this:
-    # users = mkOption {
-    #   type = loaOf optionSet;
-    #
-    #   # you can omit the list if there is one element only
-    #   options = [ {
-    #     name = mkOption {
-    #       description = "name of the user"
-    #       ...
-    #     };
-    #     # more options here
-    #   } { more options } ];
-    # }
-    # TODO: !!! document passing options as an argument to optionSet,
-    # deprecate the current approach.
-    optionSet = mkOptionType {
-      name = "option set";
-      # merge is done in "options.nix > addOptionMakeUp > handleOptionSets"
-      merge = lib.id;
+    };
+
+    submodule = opts: mkOptionType rec {
+      name = "submodule";
       check = x: isAttrs x || builtins.isFunction x;
-      hasOptions = true;
-      delayOnGlobalEval = true;
+      # FIXME: make error messages include the parent attrpath.
+      merge = merge' {};
+      merge' = args: defs:
+        let
+          coerce = def: if builtins.isFunction def then def else { config = def; };
+          modules = (toList opts) ++ map coerce defs;
+        in (evalModules modules args).config;
+    };
+
+    # Obsolete alternative to configOf.  It takes its option
+    # declarations from the ‘options’ attribute of containing option
+    # declaration.
+    optionSet = mkOptionType {
+      name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
     };
 
   };
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index cd543c958ff6..119bba78ff13 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -19,11 +19,9 @@ rec {
   # Merge the option definitions in all modules, forming the full
   # system configuration.  It's not checked for undeclared options.
   systemModule =
-    pkgs.lib.fixMergeModules configComponents extraArgs;
+    pkgs.lib.evalModules configComponents extraArgs;
 
-  optionDefinitions = systemModule.config;
-  optionDeclarations = systemModule.options;
-  inherit (systemModule) options;
+  config = systemModule.config;
 
   # These are the extra arguments passed to every module.  In
   # particular, Nixpkgs is passed through the "pkgs" argument.
@@ -56,16 +54,11 @@ rec {
           # define nixpkgs.config, so it's pointless to evaluate them.
           baseModules = [ ../modules/misc/nixpkgs.nix ];
           pkgs = import ./nixpkgs.nix { system = system_; config = {}; };
-        }).optionDefinitions.nixpkgs;
+        }).config.nixpkgs;
       in
       {
         inherit system;
         inherit (nixpkgsOptions) config;
       });
 
-  # Optionally check wether all config values have corresponding
-  # option declarations.
-  config =
-    assert optionDefinitions.environment.checkConfigurationOptions -> pkgs.lib.checkModule "" systemModule;
-    systemModule.config;
 }
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 4f7447f435bc..5b8b6bc600ca 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -26,11 +26,10 @@ in
       type = types.attrsOf (mkOptionType {
         name = "a string or a list of strings";
         merge = xs:
-          let xs' = evalProperties xs; in
-          if isList (head xs') then concatLists xs'
-          else if builtins.lessThan 1 (length xs') then abort "variable in ‘environment.variables’ has multiple values"
-          else if !builtins.isString (head xs') then abort "variable in ‘environment.variables’ does not have a string value"
-          else head xs';
+          if isList (head xs) then concatLists xs
+          else if builtins.lessThan 1 (length xs) then abort "variable in ‘environment.variables’ has multiple values"
+          else if !builtins.isString (head xs) then abort "variable in ‘environment.variables’ does not have a string value"
+          else head xs;
       });
       apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
     };
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index ff3c844030b6..b502484a5203 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -52,7 +52,7 @@ in
         idempotent and fast.
       '';
 
-      merge = mergeTypedOption "script" builtins.isAttrs (fold mergeAttrs {});
+      type = types.attrsOf types.unspecified; # FIXME
 
       apply = set: {
         script =