summary refs log tree commit diff
diff options
context:
space:
mode:
authorThomas Strobel <ts468@cam.ac.uk>2016-02-20 01:47:01 +0100
committerThomas Strobel <ts468@cam.ac.uk>2016-02-29 01:09:00 +0100
commitcad8957eabcbf73062226d28366fd446c15c8737 (patch)
tree0299e96391c14f612e7bd1cf3b2274198425fd61
parentc483224c82c8e94324c03576e64c5dfbf16bd2f8 (diff)
downloadnixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.tar
nixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.tar.gz
nixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.tar.bz2
nixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.tar.lz
nixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.tar.xz
nixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.tar.zst
nixpkgs-cad8957eabcbf73062226d28366fd446c15c8737.zip
Add the tool "nixos-typecheck" that can check an option declaration to:
 - Enforce that an option declaration has a "defaultText" if and only if the
   type of the option derives from "package", "packageSet" or "nixpkgsConfig"
   and if a "default" attribute is defined.

 - Enforce that the value of the "example" attribute is wrapped with "literalExample"
   if the type of the option derives from "package", "packageSet" or "nixpkgsConfig".

 - Warn if a "defaultText" is defined in an option declaration if the type of
   the option does not derive from "package", "packageSet" or "nixpkgsConfig".

 - Warn if no "type" is defined in an option declaration.
-rw-r--r--lib/modules.nix99
-rw-r--r--lib/options.nix145
-rw-r--r--lib/types.nix217
-rw-r--r--nixos/default.nix2
-rw-r--r--nixos/doc/manual/default.nix6
-rw-r--r--nixos/doc/manual/development/option-declarations.xml106
-rw-r--r--nixos/lib/eval-config.nix15
-rw-r--r--nixos/lib/typechecker.nix91
-rw-r--r--nixos/modules/config/i18n.nix1
-rw-r--r--nixos/modules/config/sysctl.nix1
-rw-r--r--nixos/modules/installer/tools/nixos-option.sh4
-rw-r--r--nixos/modules/installer/tools/nixos-typecheck.sh52
-rw-r--r--nixos/modules/installer/tools/tools.nix8
-rw-r--r--nixos/modules/misc/meta.nix1
-rw-r--r--nixos/modules/misc/nixpkgs.nix49
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/misc/gitlab.nix3
-rw-r--r--nixos/modules/services/misc/ihaskell.nix4
-rw-r--r--nixos/modules/services/misc/nixos-manual.nix5
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix3
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix3
-rw-r--r--nixos/modules/system/boot/kernel.nix1
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix5
-rw-r--r--nixos/release.nix2
24 files changed, 700 insertions, 124 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
index 12ec7004d1e..d08dc9f85a2 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -23,8 +23,6 @@ rec {
                   specialArgs ? {}
                 , # This would be remove in the future, Prefer _module.args option instead.
                   args ? {}
-                , # This would be remove in the future, Prefer _module.check option instead.
-                  check ? true
                 }:
     let
       # This internal module declare internal options under the `_module'
@@ -45,9 +43,22 @@ rec {
           _module.check = mkOption {
             type = types.bool;
             internal = true;
-            default = check;
+            default = true;
             description = "Whether to check whether all option definitions have matching declarations.";
           };
+
+          _module.typeInference = mkOption {
+            type = types.nullOr types.str;
+            internal = true;
+            default = null; # TODO: Move away from 'null' after enough testing.
+            description = ''
+              Mode of type inferencing. Possible values are:
+              null = Disable type inferencing completely. Use 'types.unspecified' for every option without type definition.
+              "silent" = Try to infer type of option without type definition, but do not print anything.
+              "printUnspecified" = Try to infer type of option without type definition and print options for which no full type could be inferred.
+              "printAll" = Try to infer type of option without type definition and print all options without type definition.
+            '';
+          };
         };
 
         config = {
@@ -60,7 +71,7 @@ rec {
       # 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 prefix (reverseList closed);
+      options = mergeModules (config._module) prefix (reverseList closed);
 
       # Traverse options and extract the option values into the final
       # config set.  At the same time, check whether all option
@@ -170,11 +181,11 @@ rec {
      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 = prefix: modules:
-    mergeModules' prefix modules
+  mergeModules = _module: prefix: modules:
+    mergeModules' _module prefix modules
       (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
 
-  mergeModules' = prefix: options: configs:
+  mergeModules' = _module: prefix: options: configs:
     listToAttrs (map (name: {
       # We're descending into attribute ‘name’.
       inherit name;
@@ -200,8 +211,8 @@ rec {
             (filter (m: m.config ? ${name}) configs);
         in
           if nrOptions == length decls then
-            let opt = fixupOptionType loc (mergeOptionDecls loc decls);
-            in evalOptionValue loc opt defns'
+            let opt = fixupOptionType _module.typeInference loc (mergeOptionDecls loc decls);
+            in evalOptionValue _module loc opt defns'
           else if nrOptions != 0 then
             let
               firstOption = findFirst (m: isOption m.options) "" decls;
@@ -209,7 +220,7 @@ rec {
             in
               throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
           else
-            mergeModules' loc decls defns;
+            mergeModules' _module loc decls defns;
     }) (concatMap (m: attrNames m.options) options))
     // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
 
@@ -258,7 +269,7 @@ rec {
 
   /* Merge all the definitions of an option to produce the final
      config value. */
-  evalOptionValue = loc: opt: defs:
+  evalOptionValue = _module: loc: opt: defs:
     let
       # Add in the default value for this option, if any.
       defs' =
@@ -270,7 +281,7 @@ rec {
         if opt.readOnly or false && length defs' > 1 then
           throw "The option `${showOption loc}' is read-only, but it's set multiple times."
         else
-          mergeDefinitions loc opt.type defs';
+          mergeDefinitions _module loc opt.type defs';
 
       # Check whether the option is defined, and apply the ‘apply’
       # function to the merged value.  This allows options to yield a
@@ -291,7 +302,7 @@ rec {
       };
 
   # Merge definitions of a value of a given type.
-  mergeDefinitions = loc: type: defs: rec {
+  mergeDefinitions = _module: loc: type: defs: rec {
     defsFinal =
       let
         # Process mkMerge and mkIf properties.
@@ -314,7 +325,7 @@ rec {
     mergedValue = foldl' (res: def:
       if type.check def.value then res
       else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.")
-      (type.merge loc defsFinal) defsFinal;
+      (type.merge _module loc defsFinal) defsFinal;
 
     isDefined = defsFinal != [];
 
@@ -412,7 +423,7 @@ rec {
   /* Hack for backward compatibility: convert options of type
      optionSet to options of type submodule.  FIXME: remove
      eventually. */
-  fixupOptionType = loc: opt:
+  fixupOptionType = typeInference: loc: opt:
     let
       options = opt.options or
         (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
@@ -424,12 +435,66 @@ rec {
         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
       if opt.type.getSubModules or null == null
-      then opt // { type = f (opt.type or types.unspecified); }
+      then opt // { type = f (opt.type or (inferType typeInference loc opt)); }
       else opt // { type = opt.type.substSubModules opt.options; options = []; };
 
 
+  /* Function that tries to infer the type of an option from the default value of the option. */
+  inferType = mode: loc: opt:
+    let
+      doc = x: elemAt x 0;
+      type = x: elemAt x 1;
+      containsUnspecified = x: elemAt x 2;
+      inferType' = def:
+        if isDerivation def then [ "package" types.package false ]
+        else if isBool def then [ "bool" types.bool false ]
+        else if builtins.isString def then [ "str" types.str false ]
+        else if isInt def then [ "int" types.int false ]
+        else if isFunction def then [ "functionTo unspecified" (types.functionTo types.unspecified) true ]
+        else if isList def then
+                let nestedType = if (length def > 0) && (all (x: (type (inferType' x)) == (type (inferType' (head def)))) def)
+                                 then inferType' (head def)
+                                 else [ "unspecified" types.unspecified true ];
+                in [ "listOf ${doc nestedType}" (types.listOf (type nestedType)) (containsUnspecified nestedType) ]
+        else if isAttrs def then
+                let list = mapAttrsToList (_: v: v) (removeAttrs def ["_args"]);
+                    nestedType = if (length list > 0) && (all (x: (type (inferType' x)) == (type (inferType' (head list)))) list)
+                                 then inferType' (head list)
+                                 else [ "unspecified" types.unspecified true ];
+                in [ "attrsOf ${doc nestedType}" (types.attrsOf (type nestedType)) (containsUnspecified nestedType) ]
+        else [ "unspecified" types.unspecified true ];
+
+      inferDoc = x: ''
+        Inferring the type of "${showOption loc}" to "${doc x}".
+        Please verify the inferred type and define the type explicitely in ${showFiles opt.declarations}!
+      '';
+
+      inferredType = printMode:
+        let inferred = inferType' opt.default;
+        in if printMode == "silent" then type inferred
+           else if printMode == "printAll" then builtins.trace (inferDoc inferred) (type inferred)
+           else if printMode == "printUnspecified" && (containsUnspecified inferred) then builtins.trace (inferDoc inferred) (type inferred)
+           else type inferred;
+
+      noInferDoc = ''
+        Could not infer a type for "${showOption loc}", using "unspecified" instead.
+        Please define the type explicitely in ${showFiles opt.declarations}!
+      '';
+
+      hasDefault = (opt ? default) && !(opt ? defaultText);
+      isExternalVisible = (opt.visible or true) && !(opt.internal or false);
+    in
+
+    if isNull mode || !isExternalVisible
+    then types.unspecified
+    else if hasDefault
+         then inferredType mode /* Set to 'true' to see every type that is being inferred, not just those types that result in 'unspecified'. */
+         else if mode != "silent" then builtins.trace noInferDoc types.unspecified else types.unspecified;
+
+
   /* Properties. */
 
   mkIf = condition: content:
@@ -497,7 +562,7 @@ rec {
 
 
   /* Compatibility. */
-  fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
+  fixMergeModules = modules: args: evalModules { inherit args; modules = (modules ++ [{ _module.check = false; }]); };
 
 
   /* Return a module that causes a warning to be shown if the
diff --git a/lib/options.nix b/lib/options.nix
index 444ec37e6ea..d6876e18fe4 100644
--- a/lib/options.nix
+++ b/lib/options.nix
@@ -6,6 +6,7 @@ with import ./trivial.nix;
 with import ./lists.nix;
 with import ./attrsets.nix;
 with import ./strings.nix;
+with {inherit (import ./types.nix) types; };
 
 rec {
 
@@ -42,16 +43,17 @@ rec {
     description = "Sink for option definitions.";
     type = mkOptionType {
       name = "sink";
+      typerep = "(sink)";
       check = x: true;
-      merge = loc: defs: false;
+      merge = config: loc: defs: false;
     };
     apply = x: throw "Option value is not readable because the option is not declared.";
   } // attrs);
 
-  mergeDefaultOption = loc: defs:
+  mergeDefaultOption = config: loc: defs:
     let list = getValues defs; in
     if length list == 1 then head list
-    else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list)
+    else if all isFunction list then x: mergeDefaultOption config loc (map (f: f x) list)
     else if all isList list then concatLists list
     else if all isAttrs list then foldl' lib.mergeAttrs {} list
     else if all isBool list then foldl' lib.or false list
@@ -59,14 +61,14 @@ rec {
     else if all isInt list && all (x: x == head list) list then head list
     else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
 
-  mergeOneOption = loc: defs:
+  mergeOneOption = config: loc: defs:
     if defs == [] then abort "This case should never happen."
     else if length defs != 1 then
       throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}."
     else (head defs).value;
 
   /* "Merge" option definitions by checking that they all have the same value. */
-  mergeEqualOption = loc: defs:
+  mergeEqualOption = config: loc: defs:
     if defs == [] then abort "This case should never happen."
     else foldl' (val: def:
       if def.value != val then
@@ -77,53 +79,154 @@ rec {
   getValues = map (x: x.value);
   getFiles = map (x: x.file);
 
-
   # Generate documentation template from the list of option declaration like
   # the set generated with filterOptionSets.
   optionAttrSetToDocList = optionAttrSetToDocList' [];
 
-  optionAttrSetToDocList' = prefix: options:
+  optionAttrSetToDocList' = prefix: internalModuleConfig: options:
     concatMap (opt:
       let
+        decls = filter (x: x != unknownModule) opt.declarations;
         docOption = rec {
           name = showOption opt.loc;
           description = opt.description or (throw "Option `${name}' has no description.");
-          declarations = filter (x: x != unknownModule) opt.declarations;
+          declarations = decls;
           internal = opt.internal or false;
           visible = opt.visible or true;
           readOnly = opt.readOnly or false;
           type = opt.type.name or null;
         }
-        // (if opt ? example then { example = scrubOptionValue opt.example; } else {})
-        // (if opt ? default then { default = scrubOptionValue opt.default; } else {})
+        // (if opt ? example then { example = detectDerivation decls opt.example; } else {})
+        // (if opt ? default then { default = detectDerivation decls opt.default; } else {})
         // (if opt ? defaultText then { default = opt.defaultText; } else {});
 
         subOptions =
-          let ss = opt.type.getSubOptions opt.loc;
-          in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
+          let ss = opt.type.getSubOptionsPrefixed opt.loc;
+          in if ss != {} then optionAttrSetToDocList' opt.loc internalModuleConfig (ss internalModuleConfig) else [];
       in
         [ docOption ] ++ subOptions) (collect isOption options);
 
+  # TODO: Use "extractOptionAttrSet" instead of "optionAttrSetToDocList'" to reduce the code size.
+  #       It should be a drop-in-replacement. But first, examine the impact on the evaluation time.
+  # optionAttrSetToDocList = extractOptionAttrSet true [];
+
+  # Generate a machine readable specification of the list of option declarations.
+  optionAttrSetToParseableSpecifications = extractOptionAttrSet false [];
+
+  extractOptionAttrSet = toDoc: prefix: internalModuleConfig: options:
+    concatMap (opt:
+      let
+        optionName = showOption opt.loc;
+
+        # Check if a type contains derivations, that is check if a type nests
+        # a 'package', 'packageSet' or 'nixpkgsConfig' type.
+        hasDerivation = any (t: elem t opt.type.nestedTypes) ((map (x: x.typerep) (with types; [package packageSet])) ++ ["(nixpkgsConfig)"]);
+
+        # Check if type is 'path' which can potentially contain a derivation.
+        maybeHiddenDerivation = any (t: elem t opt.type.nestedTypes) (map (x: x.typerep) (with types; [path]));
+
+        isDefaultValue = elem opt.default opt.type.defaultValues;
+
+        /* Enforce that the example attribute is wrapped with 'literalExample'
+           for every type that contains derivations. */
+        example =
+          if opt ? example
+          then (if hasDerivation
+                then (if isLiteralExample opt.example
+                      then { example = detectDerivation decls opt.example; }
+                      else throw "The attribute ${optionName}.example must be wrapped with 'literalExample' in ${concatStringsSep " and " decls}!")
+                else { example = detectDerivation decls opt.example; })
+          else {};
+
+        /* Enforce that the 'defaultText' attribute is defined for every option
+           that has a 'default' attribute that contains derivations. */
+        default =
+          if opt ? default
+          then (if hasDerivation
+                then (if isDefaultValue
+                      then { default = opt.default; }
+                      else (if opt ? defaultText
+                            then { default = literalExample (detectDerivation decls opt.defaultText); }
+                            else throw "The option ${optionName} requires a 'defaultText' attribute in ${concatStringsSep " and " decls}!"))
+                else (if opt ? defaultText
+                      then (if maybeHiddenDerivation
+                            then (if (let eval = builtins.tryEval (findDerivation opt.default); in eval.success && !eval.value)
+                                  then builtins.trace
+                                       "The attribute ${optionName}.defaultText might not be necessary in ${concatStringsSep " and " decls}!"
+                                       { default = literalExample (detectDerivation decls opt.defaultText); }
+                                  else { default = literalExample (detectDerivation decls opt.defaultText); })
+                            else builtins.trace
+                                 "The attribute ${optionName}.defaultText is not used and can be removed in ${concatStringsSep " and " decls}!"
+                                 { default = detectDerivation decls opt.default; })
+                      else { default = detectDerivation decls opt.default; }))
+          else {};
+
+        decls = filter (x: x != unknownModule) opt.declarations;
+
+        docOption = {
+          name = optionName;
+          description = opt.description or (throw "Option `${optionName}' has no description.");
+          declarations = decls;
+          internal = opt.internal or false;
+          visible = opt.visible or true;
+          readOnly = opt.readOnly or false;
+        } // example // default // subOptions // typeKeys;
+
+        typeKeys = if toDoc then { type = opt.type.name or null; } else { type = opt.type.typerep; keys = opt.loc; };
 
-  /* This function recursively removes all derivation attributes from
-     `x' except for the `name' attribute.  This is to make the
-     generation of `options.xml' much more efficient: the XML
-     representation of derivations is very large (on the order of
-     megabytes) and is not actually used by the manual generator. */
-  scrubOptionValue = x:
+        subOptions =
+          if toDoc
+          then {}
+          else let ss = opt.type.getSubOptions;
+               in if ss != {} then { suboptions = (extractOptionAttrSet false [] internalModuleConfig (ss internalModuleConfig)); } else {};
+
+        subOptionsDoc =
+          if toDoc
+          then let ss = opt.type.getSubOptionsPrefixed opt.loc;
+               in if ss != {} then extractOptionAttrSet true opt.loc internalModuleConfig (ss internalModuleConfig) else []
+          else [];
+      in
+        [ docOption ]  ++ subOptionsDoc )
+          (filter (opt: (opt.visible or true) && !(opt.internal or false)) (collect isOption options));
+
+
+  /* This function recursively checks for derivations within an
+     an expression, and throws an error if a derivation or a
+     store path is found. The function is used to ensure that no
+     derivation leaks from the 'default' or 'example' attributes
+     of an option.
+     This makes the generation of `options.xml' much more efficient:
+     the XML representation of derivations is very large (on the
+     order of megabytes) and is not actually used by the manual
+     generator. */
+  detectDerivation = decl: x:
     if isDerivation x then
-      { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
-    else if isList x then map scrubOptionValue x
-    else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
+      throw "Found unexpected derivation in '${x.name}' in '${concatStringsSep " and " decl}'!"
+    else if isString x && isStorePath x then
+      throw "Found unexpected store path in '${x.name}' in '${concatStringsSep " and " decl}'!"
+    else if isList x then map (detectDerivation decl) x
+    else if isAttrs x then mapAttrs (n: v: (detectDerivation decl) v) (removeAttrs x ["_args"])
     else x;
 
+  /* Same as detectDerivation, but returns a boolean instead of
+     throwing an exception. */
+  findDerivation = x:
+    if (isString x && isStorePath x) || isDerivation x then true
+    else if isList x then any findDerivation x
+    else if isAttrs x then any findDerivation (mapAttrsToList (_: v: v) (removeAttrs x ["_args"]))
+    else false;
+
+
 
   /* For use in the ‘example’ option attribute.  It causes the given
      text to be included verbatim in documentation.  This is necessary
      for example values that are not simple values, e.g.,
      functions. */
+  # TODO: A more general name would probably be "literalNix".
   literalExample = text: { _type = "literalExample"; inherit text; };
 
+  isLiteralExample = x: isAttrs x && hasAttr "_type" x && x._type == "literalExample";
+
 
   /* Helper functions. */
   showOption = concatStringsSep ".";
diff --git a/lib/types.nix b/lib/types.nix
index b4d29ac84d2..385103d4214 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -1,6 +1,8 @@
 # Definitions related to run-time type checking.  Used in particular
 # to type-check NixOS configurations.
 
+let lib = import ./default.nix; in
+
 with import ./lists.nix;
 with import ./attrsets.nix;
 with import ./options.nix;
@@ -21,6 +23,8 @@ rec {
   mkOptionType =
     { # Human-readable representation of the type.
       name
+    , # Parseable representation of the type.
+      typerep
     , # Function applied to each definition that should return true if
       # its type-correct, false otherwise.
       check ? (x: true)
@@ -31,40 +35,59 @@ rec {
       # definition values and locations (e.g. [ { file = "/foo.nix";
       # value = 1; } { file = "/bar.nix"; value = 2 } ]).
       merge ? mergeDefaultOption
-    , # Return a flat list of sub-options.  Used to generate
-      # documentation.
-      getSubOptions ? prefix: {}
+    , # Return list of sub-options.
+      getSubOptions ? {}
+    , # Same as 'getSubOptions', but with extra information about the
+      # location of the option which is used to generate documentation.
+      getSubOptionsPrefixed ? null
     , # List of modules if any, or null if none.
       getSubModules ? null
     , # Function for building the same option type  with a different list of
       # modules.
       substSubModules ? m: null
+    , # List of type representations (typerep) of all the elementary types
+      # that are nested within the type. For an elementary type the list is
+      # a singleton of the typerep of itself.
+      # NOTE: Must be specified for every container type!
+      nestedTypes ? null
+    , # List of all default values, and an empty list if no default value exists.
+      defaultValues ? []
     }:
     { _type = "option-type";
-      inherit name check merge getSubOptions getSubModules substSubModules;
+      inherit name typerep check merge getSubOptions getSubModules substSubModules defaultValues;
+      nestedTypes = if (isNull nestedTypes) then (singleton typerep) else nestedTypes;
+      getSubOptionsPrefixed = if (isNull getSubOptionsPrefixed) then (prefix: getSubOptions) else getSubOptionsPrefixed;
     };
 
 
   types = rec {
 
+    #
+    # Elementary types
+    #
+
     unspecified = mkOptionType {
       name = "unspecified";
+      typerep = "(unspecified)";
     };
 
     bool = mkOptionType {
       name = "boolean";
+      typerep = "(boolean)";
       check = isBool;
       merge = mergeEqualOption;
     };
 
     int = mkOptionType {
       name = "integer";
+      typerep = "(integer)";
       check = isInt;
       merge = mergeOneOption;
     };
 
     str = mkOptionType {
       name = "string";
+      typerep = "(string)";
       check = isString;
       merge = mergeOneOption;
     };
@@ -72,73 +95,111 @@ rec {
     # Merge multiple definitions by concatenating them (with the given
     # separator between the values).
     separatedString = sep: mkOptionType {
-      name = "string";
+      name = "string" + (optionalString (sep != "") " separated by ${sep}");
+      typerep = "(separatedString(${escape ["(" ")"] sep}))";
       check = isString;
-      merge = loc: defs: concatStringsSep sep (getValues defs);
+      merge = _module: loc: defs: concatStringsSep sep (getValues defs);
     };
 
-    lines = separatedString "\n";
-    commas = separatedString ",";
-    envVar = separatedString ":";
+    lines = separatedString "\n" // { typerep = "(lines)"; };
+    commas = separatedString "," // { typerep = "(commas)"; };
+    envVar = separatedString ":" // { typerep = "(envVar)"; };
 
     # Deprecated; should not be used because it quietly concatenates
     # strings, which is usually not what you want.
-    string = separatedString "";
+    string = separatedString "" // { typerep = "(string)"; };
 
     attrs = mkOptionType {
       name = "attribute set";
+      typerep = "(attrs)";
       check = isAttrs;
-      merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
+      merge = _module: loc: foldl' (res: def: mergeAttrs res def.value) {};
     };
 
     # derivation is a reserved keyword.
     package = mkOptionType {
       name = "package";
+      typerep = "(package)";
       check = x: isDerivation x || isStorePath x;
-      merge = loc: defs:
-        let res = mergeOneOption loc defs;
+      merge = _module: loc: defs:
+        let res = mergeOneOption _module loc defs;
         in if isDerivation res then res else toDerivation res;
     };
 
+    # The correct type of packageSet would be:
+    # packageSet = attrsOf (either package packageSet)
+    # (Not sure if nix would allow to define a recursive type.)
+    # However, currently it is not possible to check that a packageSet actually
+    # contains packages (that is derivations). The check 'isDerivation' is too
+    # eager for the current implementation of the assertion mechanism and of the
+    # licenses control mechanism. That means it is not generally possible to go
+    # into the attribute set of packages to check that every attribute would
+    # evaluate to a derivation if the package would actually be evaluated. Maybe
+    # that restriction can be lifted in the future, but for now the content of
+    # the packageSet is not checked.
+    # TODO: The 'merge' function is copied from 'mergeDefaultOption' to keep
+    # backwards compatibility with the 'unspecified' type that was used for
+    # package sets previously. Maybe review if the merge function has to change.
+    packageSet = mkOptionType {
+      name = "derivation set";
+      typerep = "(packageSet)";
+      check = isAttrs;
+      merge = _module: loc: defs: foldl' mergeAttrs {} (map (x: x.value) defs);
+    };
+
     path = mkOptionType {
       name = "path";
+      typerep = "(path)";
       # Hacky: there is no ‘isPath’ primop.
       check = x: builtins.substring 0 1 (toString x) == "/";
       merge = mergeOneOption;
     };
 
+
+    #
+    # Container types
+    #
+
     # drop this in the future:
     list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
 
     listOf = elemType: mkOptionType {
       name = "list of ${elemType.name}s";
+      typerep = "(listOf${elemType.typerep})";
       check = isList;
-      merge = loc: defs:
+      merge = _module: loc: defs:
         map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def: imap (m: def':
-            (mergeDefinitions
+            (mergeDefinitions _module
               (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
               elemType
               [{ inherit (def) file; value = def'; }]
             ).optionalValue
           ) def.value) defs)));
-      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
+      getSubOptions = elemType.getSubOptions;
+      getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed (prefix ++ ["*"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: listOf (elemType.substSubModules m);
+      nestedTypes = elemType.nestedTypes;
+      defaultValues = [[]];
     };
 
     attrsOf = elemType: mkOptionType {
       name = "attribute set of ${elemType.name}s";
+      typerep = "(attrsOf${elemType.typerep})";
       check = isAttrs;
-      merge = loc: defs:
+      merge = _module: loc: defs:
         mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
-            (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
+            (mergeDefinitions _module (loc ++ [name]) elemType defs).optionalValue
           )
           # Push down position info.
           (map (def: listToAttrs (mapAttrsToList (n: def':
             { name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs)));
-      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
+      getSubOptions = elemType.getSubOptions;
+      getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed (prefix ++ ["<name>"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: attrsOf (elemType.substSubModules m);
+      nestedTypes = elemType.nestedTypes;
+      defaultValues = [{}];
     };
 
     # List or attribute set of ...
@@ -159,18 +220,23 @@ rec {
         attrOnly = attrsOf elemType;
       in mkOptionType {
         name = "list or attribute set of ${elemType.name}s";
+        typerep = "(loaOf${elemType.typerep})";
         check = x: isList x || isAttrs x;
-        merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
-        getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
+        merge = _module: loc: defs: attrOnly.merge _module loc (imap convertIfList defs);
+        getSubOptions = elemType.getSubOptions;
+        getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed (prefix ++ ["<name?>"]);
         getSubModules = elemType.getSubModules;
         substSubModules = m: loaOf (elemType.substSubModules m);
+        nestedTypes = elemType.nestedTypes;
+        defaultValues = [{} []];
       };
 
     # List or element of ...
     loeOf = elemType: mkOptionType {
       name = "element or list of ${elemType.name}s";
+      typerep = "(loeOf${elemType.typerep})";
       check = x: isList x || elemType.check x;
-      merge = loc: defs:
+      merge = _module: loc: defs:
         let
           defs' = filterOverrides defs;
           res = (head defs').value;
@@ -181,81 +247,136 @@ rec {
         else if !isString res then
           throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
         else res;
+      nestedTypes = elemType.nestedTypes;
+      defaultValues = [[]] ++ elemType.defaultValues;
     };
 
     uniq = elemType: mkOptionType {
-      inherit (elemType) name check;
+      inherit (elemType) check;
+      name = "unique ${elemType.name}";
+      typerep = "(uniq${elemType.typerep})";
       merge = mergeOneOption;
       getSubOptions = elemType.getSubOptions;
+      getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed prefix;
       getSubModules = elemType.getSubModules;
       substSubModules = m: uniq (elemType.substSubModules m);
+      nestedTypes = elemType.nestedTypes;
+      defaultValues = elemType.defaultValues;
     };
 
     nullOr = elemType: mkOptionType {
       name = "null or ${elemType.name}";
+      typerep = "(nullOr${elemType.typerep})";
       check = x: x == null || elemType.check x;
-      merge = loc: defs:
+      merge = _module: loc: defs:
         let nrNulls = count (def: def.value == null) defs; in
         if nrNulls == length defs then null
         else if nrNulls != 0 then
           throw "The option `${showOption loc}' is defined both null and not null, in ${showFiles (getFiles defs)}."
-        else elemType.merge loc defs;
+        else elemType.merge _module loc defs;
       getSubOptions = elemType.getSubOptions;
+      getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed prefix;
       getSubModules = elemType.getSubModules;
       substSubModules = m: nullOr (elemType.substSubModules m);
+      nestedTypes = elemType.nestedTypes;
+      defaultValues = [null] ++ elemType.defaultValues;
+    };
+
+    enum = values:
+      let
+        show = v:
+               if builtins.isString v then ''"${v}"''
+          else if builtins.isInt v then builtins.toString v
+          else ''<${builtins.typeOf v}>'';
+      in
+      mkOptionType {
+        name = "one of ${concatMapStringsSep ", " show values}";
+        typerep = "(enum${concatMapStrings (x: "(${escape ["(" ")"] (builtins.toString x)})") values})";
+        check = flip elem values;
+        merge = mergeOneOption;
+        nestedTypes = [];
+      };
+
+    either = t1: t2: mkOptionType {
+      name = "${t1.name} or ${t2.name}";
+      typerep = "(either${t1.typerep}${t2.typerep})";
+      check = x: t1.check x || t2.check x;
+      merge = mergeOneOption;
+      nestedTypes = t1.nestedTypes ++ t2.nestedTypes;
+      defaultValues = t1.defaultValues ++ t2.defaultValues;
     };
 
+
+    #
+    # Complex types
+    #
+
     submodule = opts:
       let
         opts' = toList opts;
         inherit (import ./modules.nix) evalModules;
+        filterVisible = filter (opt: (if opt ? visible then opt.visible else true) && (if opt ? internal then !opt.internal else true));
       in
       mkOptionType rec {
         name = "submodule";
+        typerep = "(submodule)";
         check = x: isAttrs x || isFunction x;
-        merge = loc: defs:
+        merge = _module: loc: defs:
           let
+            internalModule = [ { inherit _module; } { _module.args.name = lib.mkForce (last loc); } ];
             coerce = def: if isFunction def then def else { config = def; };
-            modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
+            modules = opts' ++ internalModule ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
           in (evalModules {
             inherit modules;
-            args.name = last loc;
             prefix = loc;
           }).config;
-        getSubOptions = prefix: (evalModules
-          { modules = opts'; inherit prefix;
+        getSubOptions = getSubOptionsPrefixed [];
+        getSubOptionsPrefixed = prefix: _module:
+          let
             # FIXME: hack to get shit to evaluate.
-            args = { name = ""; }; }).options;
+            internalModule = [ { inherit _module; } { _module.args.name = lib.mkForce ""; } ];
+          in (evalModules {
+            modules = opts' ++ internalModule;
+            inherit prefix;
+          }).options;
         getSubModules = opts';
         substSubModules = m: submodule m;
+        nestedTypes = concatMap (opt: opt.type.nestedTypes) (collect (lib.isType "option") (getSubOptions {}));
+        defaultValues = [{}];
       };
 
-    enum = values:
-      let
-        show = v:
-               if builtins.isString v then ''"${v}"''
-          else if builtins.isInt v then builtins.toString v
-          else ''<${builtins.typeOf v}>'';
-      in
-      mkOptionType {
-        name = "one of ${concatMapStringsSep ", " show values}";
-        check = flip elem values;
-        merge = mergeOneOption;
-      };
 
-    either = t1: t2: mkOptionType {
-      name = "${t1.name} or ${t2.name}";
-      check = x: t1.check x || t2.check x;
-      merge = mergeOneOption;
-    };
+
+    #
+    # Legacy types
+    #
 
     # 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";
+      typerep = "(optionSet)";
     };
 
+
+    # Try to remove module options of that type wherever possible.
+    # A module option taking a function can not be introspected and documented properly.
+    functionTo = resultType:
+      mkOptionType {
+        name = "function to ${resultType.name}";
+        typerep = "(function${resultType.typerep})";
+        check = builtins.isFunction;
+        merge = mergeOneOption;
+        nestedTypes = resultType.nestedTypes;
+        defaultValues = map (x: {...}:x) resultType.nestedTypes; # INFO: It seems as nix can't compare functions, yet.
+      };
+
+
+    #
+    # misc
+    #
+
     # Augment the given type with an additional type check function.
     addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
 
diff --git a/nixos/default.nix b/nixos/default.nix
index 5d69b79e13a..117a7b5d603 100644
--- a/nixos/default.nix
+++ b/nixos/default.nix
@@ -34,6 +34,8 @@ in
 
   system = eval.config.system.build.toplevel;
 
+  typechecker = eval.config.system.build.typechecker;
+
   vm = vmConfig.system.build.vm;
 
   vmWithBootLoader = vmWithBootLoaderConfig.system.build.vm;
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index 4ce6ea1c111..ea860149736 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -1,4 +1,4 @@
-{ pkgs, options, version, revision, extraSources ? [] }:
+{ pkgs, options, internalModule, version, revision, extraSources ? [] }:
 
 with pkgs;
 with pkgs.lib;
@@ -6,8 +6,10 @@ with pkgs.lib;
 let
 
   # Remove invisible and internal options.
-  optionsList = filter (opt: opt.visible && !opt.internal) (optionAttrSetToDocList options);
+  optionsList = filter (opt: opt.visible && !opt.internal) (optionAttrSetToDocList internalModule options);
 
+  # INFO: Please add 'defaultText' or 'literalExample' to the option
+  #       definition to avoid this substitution!
   # Replace functions by the string <function>
   substFunction = x:
     if builtins.isAttrs x then mapAttrs (name: substFunction) x
diff --git a/nixos/doc/manual/development/option-declarations.xml b/nixos/doc/manual/development/option-declarations.xml
index ea5d1241876..08bde4a275d 100644
--- a/nixos/doc/manual/development/option-declarations.xml
+++ b/nixos/doc/manual/development/option-declarations.xml
@@ -8,7 +8,7 @@
 
 <para>An option declaration specifies the name, type and description
 of a NixOS configuration option.  It is illegal to define an option
-that hasn’t been declared in any module.  A option declaration
+that has not been declared in any module. A option declaration
 generally looks like this:
 
 <programlisting>
@@ -145,6 +145,108 @@ options = {
 
 You can also create new types using the function
 <varname>mkOptionType</varname>.  See
-<filename>lib/types.nix</filename> in Nixpkgs for details.</para>
+<filename>lib/types.nix</filename> in Nixpkgs for details.
+
+An option declaration must follow the following rules:
+<itemizedlist mark='bullet'>
+
+  <listitem>
+    <para>A <varname>defaultText</varname> must be defined if and only if the type of the option
+    derives from <varname>package</varname>, <varname>packageSet</varname> or <varname>nixpkgsConfig
+    </varname>, and if and only if a <varname>default</varname> attribute is defined and if and only if
+    the value of the <varname>default</varname> attribute is not the default of the type of the
+    option declaration.
+
+    For example, a <varname>defaultText</varname> must be defined for
+<programlisting>
+type = types.listOf types.package;
+default = [ pkgs.foo; ];
+defaultText = "[ pkgs.foo; ]";
+</programlisting>.
+
+    But no <varname>defaultText</varname> must be defined for
+<programlisting>
+type = types.listOf types.package;
+default = [];
+</programlisting>,
+    as <varname>[]</varname> is the default of <varname>types.listOf types.package</varname>.
+    </para>
+  </listitem>
+
+  <listitem>
+    <para>A <varname>defaultText</varname> can be defined if the type of the option derives from
+    <varname>path</varname> and if a <varname>default</varname> attribute is defined.</para>
+  </listitem>
+
+  <listitem>
+    <para>The value of the <varname>example</varname> attribute must be wrapped with <varname>
+    literalExample</varname> if the type of the option derives from <varname>package</varname>,
+    <varname>packageSet</varname> or <varname>nixpkgsConfig</varname>.</para>
+  </listitem>
+
+  <listitem>
+    <para>The value of <varname>defaultText</varname> and <varname>literalExample</varname> must
+    be a string which contains a valid nix expression. The nix expression has to evaluate in
+    <code>{pkgs}: <replaceable>value</replaceable></code>.
+    For example:
+    <itemizedlist>
+      <listitem>
+	<para>A <varname>defaultText</varname> could, e.g., be:
+<programlisting>
+type = types.package;
+default = pkgs.foo;
+defaultText = "pkgs.foo";
+</programlisting>
+        But not <code>defaultText = "pkgs.foo;";</code>, as that
+        corresponds to <code>{pkgs}: pkgs.foo;</code>, which is an
+	invalid nix expression due to the ending with <varname>;</varname>.</para>
+      </listitem>
+    
+      <listitem>
+	<para>A <varname>literalExample</varname> could be used as, e.g.:
+<programlisting>
+type = types.path;
+example = literalExample "\"\${pkgs.bar}/bin/bar\"";
+</programlisting>
+        But not <code>literalExample "\${pkgs.bar}/bin/bar";</code>, as that corresponds
+	to <code>{pkgs}: ${pkgs.bar}/bin/bar</code>, which is an invalid nix expression
+	as the <varname>path</varname> is not a <varname>string</varname> anymore.</para>
+      </listitem>
+    </itemizedlist></para>
+  </listitem>
+
+  <listitem>
+    <para>The <varname>type</varname> attribute must be defined for every option declaration.</para>
+  </listitem>
+
+</itemizedlist>
+
+NixOS ships the tool <varname>nixos-typecheck</varname> that can check an option declaration to:
+
+<itemizedlist mark='bullet'>
+
+  <listitem>
+    <para>Enforce that an option declaration has a <varname>defaultText</varname> if and only if the
+    type of the option derives from <varname>package</varname>, <varname>packageSet</varname> or
+    <varname>nixpkgsConfig</varname> and if a <varname>default</varname> attribute is defined.</para>
+  </listitem>
+
+  <listitem>
+    <para>Enforce that the value of the <varname>example</varname> attribute is wrapped with <varname>
+    literalExample</varname> if the type of the option derives from <varname>package</varname>,
+    <varname>packageSet</varname> or <varname>nixpkgsConfig</varname>.</para>
+  </listitem>
+
+  <listitem>
+    <para>Warn if a <varname>defaultText</varname> is defined in an option declaration if the type of
+    the option does not derive from <varname>package</varname>, <varname>packageSet</varname> or
+    <varname>nixpkgsConfig</varname>.</para>
+  </listitem>
+
+  <listitem>
+    <para>Warn if no <varname>type</varname> is defined in an option declaration.</para>
+  </listitem>
+
+</itemizedlist></para>
 
 </section>
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index a87b285c5b7..79890e0add7 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -20,8 +20,13 @@
 , # !!! See comment about args in lib/modules.nix
   specialArgs ? {}
 , modules
+, # Pass through a configuration of the internal modules declared
+  # in lib/modules.nix.
+  _module ? {}
+, # !!! See comment about typeInference in lib/modules.nix
+  typeInference ? null
 , # !!! See comment about check in lib/modules.nix
-  check ? true
+  check ? null
 , prefix ? []
 , lib ? import ../../lib
 }:
@@ -41,13 +46,17 @@ let
     };
   };
 
+  internalModule = { _module = (_module
+                     // (if isNull check then {} else { inherit check; })
+                     // (if isNull typeInference then {} else { inherit typeInference; })); };
+
 in rec {
 
   # Merge the option definitions in all modules, forming the full
   # system configuration.
   inherit (lib.evalModules {
-    inherit prefix check;
-    modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
+    inherit prefix;
+    modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ] ++ [ internalModule ];
     args = extraArgs;
     specialArgs = { modulesPath = ../modules; } // specialArgs;
   }) config options;
diff --git a/nixos/lib/typechecker.nix b/nixos/lib/typechecker.nix
new file mode 100644
index 00000000000..b4d8277f8fe
--- /dev/null
+++ b/nixos/lib/typechecker.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, baseModules, ... }:
+
+with pkgs;
+with pkgs.lib;
+
+let
+
+  optionsSpecs = inferenceMode:
+    let
+      versionModule =
+        { system.nixosVersionSuffix = config.system.nixosVersionSuffix;
+          system.nixosRevision = config.system.nixosRevision;
+          nixpkgs.system = config.nixpkgs.system;
+        };
+
+      internalModule = { _module = config._module; } // (if isNull inferenceMode then {} else { _module.typeInference = mkForce inferenceMode; });
+
+      eval = evalModules {
+        modules = [ versionModule ] ++ baseModules ++ [ internalModule ];
+        args = (config._module.args) // { modules = [ ]; };
+      };
+
+      # Remove invisible and internal options.
+      optionsSpecs' = filter (opt: opt.visible && !opt.internal) (optionAttrSetToParseableSpecifications config._module eval.options);
+
+      # INFO: Please add 'defaultText' or 'literalExample' to the option
+      #       definition to avoid this exception!
+      substFunction = key: decls: x:
+        if builtins.isAttrs x then mapAttrs (name: substFunction key decls) x
+        else if builtins.isList x then map (substFunction key decls) x
+        else if builtins.isFunction x then throw "Found an unexpected <function> in ${key} declared in ${concatStringsSep " and " decls}."
+        else x;
+
+      prefix = toString ../..;
+
+      stripPrefix = fn:
+        if substring 0 (stringLength prefix) fn == prefix then
+          substring (stringLength prefix + 1) 1000 fn
+        else
+          fn;
+
+      # Clean up declaration sites to not refer to the NixOS source tree.
+      cleanupOptions = x: flip map x (opt:
+        let substFunction' = y: substFunction opt.name opt.declarations y;
+        in opt
+           // { declarations = map (fn: stripPrefix fn) opt.declarations; }
+           // optionalAttrs (opt ? example) { example = substFunction' opt.example; }
+           // optionalAttrs (opt ? default) { default = substFunction' opt.default; }
+           // optionalAttrs (opt ? type) { type = substFunction' opt.type; });
+
+    in
+      cleanupOptions optionsSpecs';
+
+in
+
+{
+
+  system.build.typechecker = {
+
+    # The NixOS options as machine readable specifications in JSON format.
+    specifications = stdenv.mkDerivation {
+      name = "options-specs-json";
+
+      buildCommand = ''
+        # Export list of options in different format.
+        dst=$out/share/doc/nixos
+        mkdir -p $dst
+
+        cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON
+          (listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs null)))))
+        } $dst/options-specs.json
+
+        mkdir -p $out/nix-support
+        echo "file json $dst/options-specs.json" >> $out/nix-support/hydra-build-products
+      ''; # */
+
+      meta.description = "List of NixOS options specifications in JSON format";
+    };
+
+    silent = listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs "silent"));
+    printAll = listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs "printAll"));
+    printUnspecified = listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs "printUnspecified"));
+
+  };
+
+}
+
+
+
+
+
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index b20fac6ad3e..3d21bda5734 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -64,6 +64,7 @@ in
       consoleKeyMap = mkOption {
         type = mkOptionType {
           name = "string or path";
+          typerep = "(stringOrPath)";
           check = t: (isString t || types.path.check t);
         };
 
diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix
index 61b02c5ffa6..174d7014ca8 100644
--- a/nixos/modules/config/sysctl.nix
+++ b/nixos/modules/config/sysctl.nix
@@ -6,6 +6,7 @@ let
 
   sysctlOption = mkOptionType {
     name = "sysctl option value";
+    typerep = "(sysctl)";
     check = val:
       let
         checkType = x: isBool x || isString x || isInt x || isNull x;
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
index 17c17d05e28..dd21dc71ee2 100644
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ b/nixos/modules/installer/tools/nixos-option.sh
@@ -115,8 +115,8 @@ let
       let name = head attrsNames; rest = tail attrsNames; in
       if isOption result.options then
         walkOptions rest {
-          options = result.options.type.getSubOptions "";
-          opt = ''(\${result.opt}.type.getSubOptions "")'';
+          options = result.options.type.getSubOptionsPrefix "";
+          opt = ''(\${result.opt}.type.getSubOptionsPrefix "")'';
           cfg = ''\${result.cfg}."\${name}"'';
         }
       else
diff --git a/nixos/modules/installer/tools/nixos-typecheck.sh b/nixos/modules/installer/tools/nixos-typecheck.sh
new file mode 100644
index 00000000000..f9557be0c50
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-typecheck.sh
@@ -0,0 +1,52 @@
+#! /bin/sh
+#! @shell@
+
+if [ -x "@shell@" ]; then export SHELL="@shell@"; fi;
+
+set -e
+
+showSyntax() {
+cat >&1 << EOF
+nixos-typecheck
+usage:
+  nixos-typecheck [action] [args]
+where:
+  action = silent | printAll | printUnspecified | getSpecs
+    with default action: printUnspecified
+  args = any argument supported by nix-build
+EOF
+}
+
+
+# Parse the command line.
+extraArgs=()
+action=printUnspecified
+
+while [ "$#" -gt 0 ]; do
+    i="$1"; shift 1
+    case "$i" in
+      --help)
+        showSyntax
+        ;;
+      silent|printAll|printUnspecified|getSpecs)
+        action="$i"
+        ;;
+      *)
+        extraArgs="$extraArgs $i"
+        ;;
+    esac
+done
+
+
+if [ "$action" = silent ]; then
+    nix-build --no-out-link '<nixpkgs/nixos>' -A typechecker.silent $extraArgs
+elif [ "$action" = printAll ]; then
+    nix-build --no-out-link '<nixpkgs/nixos>' -A typechecker.printAll $extraArgs
+elif [ "$action" = printUnspecified ]; then
+    nix-build --no-out-link '<nixpkgs/nixos>' -A typechecker.printUnspecified $extraArgs
+elif [ "$action" = getSpecs ]; then
+    ln -s $(nix-build --no-out-link '<nixpkgs/nixos>' -A typechecker.specifications $extraArgs)/share/doc/nixos/options-specs.json specifications.json
+else
+    showSyntax
+    exit 1
+fi
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 9ac3b7a5b16..69bd0d26b77 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -54,6 +54,11 @@ let
     inherit (config.system) nixosVersion nixosCodeName nixosRevision;
   };
 
+  nixos-typecheck = makeProg {
+    name = "nixos-typecheck";
+    src = ./nixos-typecheck.sh;
+  };
+
 in
 
 {
@@ -67,10 +72,11 @@ in
         nixos-generate-config
         nixos-option
         nixos-version
+        nixos-typecheck
       ];
 
     system.build = {
-      inherit nixos-install nixos-generate-config nixos-option nixos-rebuild;
+      inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-typecheck;
     };
 
   };
diff --git a/nixos/modules/misc/meta.nix b/nixos/modules/misc/meta.nix
index 22622706f2c..9a453172c63 100644
--- a/nixos/modules/misc/meta.nix
+++ b/nixos/modules/misc/meta.nix
@@ -5,6 +5,7 @@ with lib;
 let
   maintainer = mkOptionType {
     name = "maintainer";
+    typerep = "(maintainer)";
     check = email: elem email (attrValues lib.maintainers);
     merge = loc: defs: listToAttrs (singleton (nameValuePair (last defs).file (last defs).value));
   };
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 5eb38c510b4..511831ae5cd 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -3,32 +3,35 @@
 with lib;
 
 let
-  isConfig = x:
-    builtins.isAttrs x || builtins.isFunction x;
+  nixpkgsConfig = pkgs:
+    let
+      isConfig = x:
+        builtins.isAttrs x || builtins.isFunction x;
 
-  optCall = f: x:
-    if builtins.isFunction f
-    then f x
-    else f;
+      optCall = f: x:
+        if builtins.isFunction f
+        then f x
+        else f;
 
-  mergeConfig = lhs_: rhs_:
-    let
-      lhs = optCall lhs_ { inherit pkgs; };
-      rhs = optCall rhs_ { inherit pkgs; };
+      mergeConfig = lhs_: rhs_:
+        let
+          lhs = optCall lhs_ { inherit pkgs; };
+          rhs = optCall rhs_ { inherit pkgs; };
+        in
+        lhs // rhs //
+        optionalAttrs (lhs ? packageOverrides) {
+          packageOverrides = pkgs:
+            optCall lhs.packageOverrides pkgs //
+            optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs;
+        };
     in
-    lhs // rhs //
-    optionalAttrs (lhs ? packageOverrides) {
-      packageOverrides = pkgs:
-        optCall lhs.packageOverrides pkgs //
-        optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs;
+    mkOptionType {
+      name = "nixpkgs config";
+      typerep = "(nixpkgsConfig)";
+      check = lib.traceValIfNot isConfig;
+      merge = config: args: fold (def: mergeConfig def.value) {};
+      defaultValues = [{}];
     };
-
-  configType = mkOptionType {
-    name = "nixpkgs config";
-    check = traceValIfNot isConfig;
-    merge = args: fold (def: mergeConfig def.value) {};
-  };
-
 in
 
 {
@@ -46,7 +49,7 @@ in
             };
           }
         '';
-      type = configType;
+      type = nixpkgsConfig pkgs;
       description = ''
         The configuration of the Nix Packages collection.  (For
         details, see the Nixpkgs documentation.)  It allows you to set
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index ad1636e002d..88e01069b26 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1,4 +1,5 @@
 [
+  ../lib/typechecker.nix
   ./config/debug-info.nix
   ./config/fonts/corefonts.nix
   ./config/fonts/fontconfig-ultimate.nix
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index cc50bfbea53..e6ebcd6dc14 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -155,18 +155,21 @@ in {
       packages.gitlab = mkOption {
         type = types.package;
         default = pkgs.gitlab;
+        defaultText = "pkgs.gitlab";
         description = "Reference to the gitlab package";
       };
 
       packages.gitlab-shell = mkOption {
         type = types.package;
         default = pkgs.gitlab-shell;
+        defaultText = "pkgs.gitlab-shell";
         description = "Reference to the gitlab-shell package";
       };
 
       packages.gitlab-workhorse = mkOption {
         type = types.package;
         default = pkgs.gitlab-workhorse;
+        defaultText = "pkgs.gitlab-workhorse";
         description = "Reference to the gitlab-workhorse package";
       };
 
diff --git a/nixos/modules/services/misc/ihaskell.nix b/nixos/modules/services/misc/ihaskell.nix
index d0e9b839e75..c177d68c8fe 100644
--- a/nixos/modules/services/misc/ihaskell.nix
+++ b/nixos/modules/services/misc/ihaskell.nix
@@ -15,12 +15,14 @@ in
   options = {
     services.ihaskell = {
       enable = mkOption {
+        type = lib.types.bool;
         default = false;
         example = true;
         description = "Autostart an IHaskell notebook service.";
       };
 
       haskellPackages = mkOption {
+        type = lib.types.packageSet;
         default = pkgs.haskellPackages;
         defaultText = "pkgs.haskellPackages";
         example = literalExample "pkgs.haskell.packages.ghc784";
@@ -33,7 +35,9 @@ in
       };
 
       extraPackages = mkOption {
+        type = types.functionTo (types.listOf types.package);
         default = self: [];
+        defaultText = "self: []";
         example = literalExample ''
           haskellPackages: [
             haskellPackages.wreq
diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix
index 37ea339300d..7ba1c319add 100644
--- a/nixos/modules/services/misc/nixos-manual.nix
+++ b/nixos/modules/services/misc/nixos-manual.nix
@@ -17,6 +17,8 @@ let
       nixpkgs.system = config.nixpkgs.system;
     };
 
+  internalModule = { _module = config._module; };
+
   /* For the purpose of generating docs, evaluate options with each derivation
     in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
     It isn't perfect, but it seems to cover a vast majority of use cases.
@@ -29,7 +31,7 @@ let
     options =
       let
         scrubbedEval = evalModules {
-          modules = [ versionModule ] ++ baseModules;
+          modules = [ versionModule ] ++ baseModules ++ [ internalModule ];
           args = (config._module.args) // { modules = [ ]; };
           specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
         };
@@ -43,6 +45,7 @@ let
           )
           pkgSet;
       in scrubbedEval.options;
+    internalModule = config._module;
   };
 
   entry = "${manual.manual}/share/doc/nixos/index.html";
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 60ec49c9e66..8340287df45 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -48,8 +48,7 @@ let
           if svc ? function then svc.function
           else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
         config = (evalModules
-          { modules = [ { options = res.options; config = svc.config or svc; } ];
-            check = false;
+          { modules = [ { options = res.options; config = svc.config or svc; } ] ++ [ { _module.check = false; } ];
           }).config;
         defaults = {
           extraConfig = "";
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 6af88d4f645..d8acb824f7d 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -16,6 +16,7 @@ in
     services.xserver.windowManager.xmonad = {
       enable = mkEnableOption "xmonad";
       haskellPackages = mkOption {
+        type = lib.types.packageSet;
         default = pkgs.haskellPackages;
         defaultText = "pkgs.haskellPackages";
         example = literalExample "pkgs.haskell.packages.ghc784";
@@ -28,7 +29,9 @@ in
       };
 
       extraPackages = mkOption {
+        type = lib.types.functionTo (lib.types.listOf lib.types.package);
         default = self: [];
+        defaultText = "self: []";
         example = literalExample ''
           haskellPackages: [
             haskellPackages.xmonad-contrib
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index a6bbca9b30b..5b11a3fc61c 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -20,6 +20,7 @@ in
   options = {
 
     boot.kernelPackages = mkOption {
+      type = types.packageSet;
       default = pkgs.linuxPackages;
       # We don't want to evaluate all of linuxPackages for the manual
       # - some of it might not even evaluate correctly.
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index d4cab93b26b..16d0e098b8c 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -17,14 +17,15 @@ in rec {
 
   unitOption = mkOptionType {
     name = "systemd option";
-    merge = loc: defs:
+    typerep = "(systemdOption)";
+    merge = _module: loc: defs:
       let
         defs' = filterOverrides defs;
         defs'' = getValues defs';
       in
         if isList (head defs'')
         then concatLists defs''
-        else mergeOneOption loc defs';
+        else mergeOneOption _module loc defs';
   };
 
   sharedOptions = {
diff --git a/nixos/release.nix b/nixos/release.nix
index cfe152cc163..3fa1676c861 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -101,6 +101,8 @@ in rec {
   manpages = buildFromConfig ({ pkgs, ... }: { }) (config: config.system.build.manual.manpages);
   options = (buildFromConfig ({ pkgs, ... }: { }) (config: config.system.build.manual.optionsJSON)).x86_64-linux;
 
+  optionsSpecs = (buildFromConfig ({ pkgs, ... }: { _module.typeInference = "silent"; }) (config: config.system.build.typechecker.specifications)).x86_64-linux;
+
 
   # Build the initial ramdisk so Hydra can keep track of its size over time.
   initialRamdisk = buildFromConfig ({ pkgs, ... }: { }) (config: config.system.build.initialRamdisk);