summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--lib/modules.nix32
-rw-r--r--lib/options.nix2
-rw-r--r--lib/types.nix142
-rw-r--r--nixos/doc/manual/development/option-declarations.xml88
-rw-r--r--nixos/doc/manual/development/option-types.xml110
-rw-r--r--nixos/doc/manual/release-notes/rl-1703.xml5
-rw-r--r--nixos/modules/installer/tools/nixos-option.sh2
7 files changed, 314 insertions, 67 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
index 8db17c60579..e66d6a6926c 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -231,12 +231,20 @@ rec {
      correspond to the definition of 'loc' in 'opt.file'. */
   mergeOptionDecls = loc: opts:
     foldl' (res: opt:
-      if opt.options ? default && res ? default ||
-         opt.options ? example && res ? example ||
-         opt.options ? description && res ? description ||
-         opt.options ? apply && res ? apply ||
-         # Accept to merge options which have identical types.
-         opt.options ? type && res ? type && opt.options.type.name != res.type.name
+      let t  = res.type;
+          t' = opt.options.type;
+          mergedType = t.typeMerge t'.functor;
+          typesMergeable = mergedType != null;
+          typeSet = if (bothHave "type") && typesMergeable
+                       then { type = mergedType; }
+                       else {};
+          bothHave = k: opt.options ? ${k} && res ? ${k};
+      in
+      if bothHave "default" ||
+         bothHave "example" ||
+         bothHave "description" ||
+         bothHave "apply" ||
+         (bothHave "type" && (! typesMergeable))
       then
         throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
       else
@@ -258,7 +266,7 @@ rec {
         in opt.options // res //
           { declarations = res.declarations ++ [opt.file];
             options = submodules;
-          }
+          } // typeSet
     ) { inherit loc; declarations = []; options = []; } opts;
 
   /* Merge all the definitions of an option to produce the final
@@ -422,12 +430,14 @@ rec {
       options = opt.options or
         (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
       f = tp:
+        let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
+        in
         if tp.name == "option set" || tp.name == "submodule" then
           throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
-        else if 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 if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
+        else if optionSetIn "loaOf"   then types.loaOf   (types.submodule options)
+        else if optionSetIn "listOf"  then types.listOf  (types.submodule options)
+        else if optionSetIn "nullOr"  then types.nullOr  (types.submodule options)
         else tp;
     in
       if opt.type.getSubModules or null == null
diff --git a/lib/options.nix b/lib/options.nix
index 444ec37e6ea..2092b65bbc3 100644
--- a/lib/options.nix
+++ b/lib/options.nix
@@ -92,7 +92,7 @@ rec {
           internal = opt.internal or false;
           visible = opt.visible or true;
           readOnly = opt.readOnly or false;
-          type = opt.type.name or null;
+          type = opt.type.description or null;
         }
         // (if opt ? example then { example = scrubOptionValue opt.example; } else {})
         // (if opt ? default then { default = scrubOptionValue opt.default; } else {})
diff --git a/lib/types.nix b/lib/types.nix
index 991fa0e5c29..26523f59f25 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -17,10 +17,43 @@ rec {
   };
 
 
+  # Default type merging function
+  # takes two type functors and return the merged type
+  defaultTypeMerge = f: f':
+    let wrapped = f.wrapped.typeMerge f'.wrapped.functor;
+        payload = f.binOp f.payload f'.payload;
+    in
+    # cannot merge different types
+    if f.name != f'.name
+       then null
+    # simple types
+    else if    (f.wrapped == null && f'.wrapped == null)
+            && (f.payload == null && f'.payload == null)
+       then f.type
+    # composed types
+    else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
+       then f.type wrapped
+    # value types
+    else if (f.payload != null && f'.payload != null) && (payload != null)
+       then f.type payload
+    else null;
+
+  # Default type functor
+  defaultFunctor = name: {
+    inherit name;
+    type    = types."${name}" or null;
+    wrapped = null;
+    payload = null;
+    binOp   = a: b: null;
+  };
+
   isOptionType = isType "option-type";
   mkOptionType =
-    { # Human-readable representation of the type.
+    { # Human-readable representation of the type, should be equivalent to
+      # the type function name.
       name
+    , # Description of the type, defined recursively by embedding the the wrapped type if any.
+      description ? null
     , # Function applied to each definition that should return true if
       # its type-correct, false otherwise.
       check ? (x: true)
@@ -36,12 +69,26 @@ rec {
       getSubOptions ? prefix: {}
     , # List of modules if any, or null if none.
       getSubModules ? null
-    , # Function for building the same option type  with a different list of
+    , # Function for building the same option type with a different list of
       # modules.
       substSubModules ? m: null
+    , # Function that merge type declarations.
+      # internal, takes a functor as argument and returns the merged type.
+      # returning null means the type is not mergeable
+      typeMerge ? defaultTypeMerge functor
+    , # The type functor.
+      # internal, representation of the type as an attribute set.
+      #   name: name of the type
+      #   type: type function.
+      #   wrapped: the type wrapped in case of compound types.
+      #   payload: values of the type, two payloads of the same type must be 
+      #            combinable with the binOp binary operation.
+      #   binOp: binary operation that merge two payloads of the same type.
+      functor ? defaultFunctor name
     }:
     { _type = "option-type";
-      inherit name check merge getSubOptions getSubModules substSubModules;
+      inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor;
+      description = if description == null then name else description;
     };
 
 
@@ -52,29 +99,39 @@ rec {
     };
 
     bool = mkOptionType {
-      name = "boolean";
+      name = "bool";
+      description = "boolean";
       check = isBool;
       merge = mergeEqualOption;
     };
 
-    int = mkOptionType {
-      name = "integer";
+    int = mkOptionType rec {
+      name = "int";
+      description = "integer";
       check = isInt;
       merge = mergeOneOption;
     };
 
     str = mkOptionType {
-      name = "string";
+      name = "str";
+      description = "string";
       check = isString;
       merge = mergeOneOption;
     };
 
     # Merge multiple definitions by concatenating them (with the given
     # separator between the values).
-    separatedString = sep: mkOptionType {
-      name = "string";
+    separatedString = sep: mkOptionType rec {
+      name = "separatedString";
+      description = "string";
       check = isString;
       merge = loc: defs: concatStringsSep sep (getValues defs);
+      functor = (defaultFunctor name) // {
+        payload = sep;
+        binOp = sepLhs: sepRhs:
+          if sepLhs == sepRhs then sepLhs
+          else null;
+      };
     };
 
     lines = separatedString "\n";
@@ -86,7 +143,8 @@ rec {
     string = separatedString "";
 
     attrs = mkOptionType {
-      name = "attribute set";
+      name = "attrs";
+      description = "attribute set";
       check = isAttrs;
       merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
     };
@@ -114,8 +172,9 @@ rec {
     # 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";
+    listOf = elemType: mkOptionType rec {
+      name = "listOf";
+      description = "list of ${elemType.description}s";
       check = isList;
       merge = loc: defs:
         map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def:
@@ -132,10 +191,12 @@ rec {
       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: listOf (elemType.substSubModules m);
+      functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
-    attrsOf = elemType: mkOptionType {
-      name = "attribute set of ${elemType.name}s";
+    attrsOf = elemType: mkOptionType rec {
+      name = "attrsOf";
+      description = "attribute set of ${elemType.description}s";
       check = isAttrs;
       merge = loc: defs:
         mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
@@ -147,6 +208,7 @@ rec {
       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: attrsOf (elemType.substSubModules m);
+      functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
     # List or attribute set of ...
@@ -165,18 +227,21 @@ rec {
             def;
         listOnly = listOf elemType;
         attrOnly = attrsOf elemType;
-      in mkOptionType {
-        name = "list or attribute set of ${elemType.name}s";
+      in mkOptionType rec {
+        name = "loaOf";
+        description = "list or attribute set of ${elemType.description}s";
         check = x: isList x || isAttrs x;
         merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
         getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
         getSubModules = elemType.getSubModules;
         substSubModules = m: loaOf (elemType.substSubModules m);
+        functor = (defaultFunctor name) // { wrapped = elemType; };
       };
 
     # List or element of ...
-    loeOf = elemType: mkOptionType {
-      name = "element or list of ${elemType.name}s";
+    loeOf = elemType: mkOptionType rec {
+      name = "loeOf";
+      description = "element or list of ${elemType.description}s";
       check = x: isList x || elemType.check x;
       merge = loc: defs:
         let
@@ -189,18 +254,22 @@ rec {
         else if !isString res then
           throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
         else res;
+      functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
-    uniq = elemType: mkOptionType {
-      inherit (elemType) name check;
+    uniq = elemType: mkOptionType rec {
+      name = "uniq";
+      inherit (elemType) description check;
       merge = mergeOneOption;
       getSubOptions = elemType.getSubOptions;
       getSubModules = elemType.getSubModules;
       substSubModules = m: uniq (elemType.substSubModules m);
+      functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
-    nullOr = elemType: mkOptionType {
-      name = "null or ${elemType.name}";
+    nullOr = elemType: mkOptionType rec {
+      name = "nullOr";
+      description = "null or ${elemType.description}";
       check = x: x == null || elemType.check x;
       merge = loc: defs:
         let nrNulls = count (def: def.value == null) defs; in
@@ -211,6 +280,7 @@ rec {
       getSubOptions = elemType.getSubOptions;
       getSubModules = elemType.getSubModules;
       substSubModules = m: nullOr (elemType.substSubModules m);
+      functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
     submodule = opts:
@@ -236,6 +306,12 @@ rec {
             args = { name = ""; }; }).options;
         getSubModules = opts';
         substSubModules = m: submodule m;
+        functor = (defaultFunctor name) // {
+          # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
+          # each submodule with its location.
+          payload = [];
+          binOp = lhs: rhs: [];
+        };
       };
 
     enum = values:
@@ -245,23 +321,35 @@ rec {
           else if builtins.isInt v then builtins.toString v
           else ''<${builtins.typeOf v}>'';
       in
-      mkOptionType {
-        name = "one of ${concatMapStringsSep ", " show values}";
+      mkOptionType rec {
+        name = "enum";
+        description = "one of ${concatMapStringsSep ", " show values}";
         check = flip elem values;
         merge = mergeOneOption;
+        functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
       };
 
-    either = t1: t2: mkOptionType {
-      name = "${t1.name} or ${t2.name}";
+    either = t1: t2: mkOptionType rec {
+      name = "either";
+      description = "${t1.description} or ${t2.description}";
       check = x: t1.check x || t2.check x;
       merge = mergeOneOption;
+      typeMerge = f':
+        let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
+            mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
+        in
+           if (name == f'.name) && (mt1 != null) && (mt2 != null)
+           then functor.type mt1 mt2
+           else null;
+      functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
     };
 
     # 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";
+      name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "optionSet";
+      description = "option set";
     };
 
     # Augment the given type with an additional type check function.
diff --git a/nixos/doc/manual/development/option-declarations.xml b/nixos/doc/manual/development/option-declarations.xml
index 7be5e9d51d4..ce432a7fa6c 100644
--- a/nixos/doc/manual/development/option-declarations.xml
+++ b/nixos/doc/manual/development/option-declarations.xml
@@ -65,4 +65,92 @@ options = {
 
 </para>
 
+<section xml:id="sec-option-declarations-eot"><title>Extensible Option 
+    Types</title>
+
+  <para>Extensible option types is a feature that allow to extend certain types 
+    declaration through multiple module files.
+    This feature only work with a restricted set of types, namely 
+    <literal>enum</literal> and <literal>submodules</literal> and any composed
+    forms of them.</para>
+
+  <para>Extensible option types can be used for <literal>enum</literal> options 
+    that affects multiple modules, or as an alternative to related 
+    <literal>enable</literal> options.</para>
+
+  <para>As an example, we will take the case of display managers. There is a
+    central display manager module for generic display manager options and a
+    module file per display manager backend (slim, kdm, gdm ...).
+  </para>
+
+  <para>There are two approach to this module structure:
+
+  <itemizedlist>
+    <listitem><para>Managing the display managers independently by adding an
+        enable option to every display manager module backend. (NixOS)</para>
+    </listitem>
+    <listitem><para>Managing the display managers in the central module by
+        adding an option to select which display manager backend to use.</para>
+    </listitem>
+  </itemizedlist>
+  </para>
+
+  <para>Both approachs have problems.</para>
+    
+  <para>Making backends independent can quickly become hard to manage. For
+    display managers, there can be only one enabled at a time, but the type
+    system can not enforce this restriction as there is no relation between
+    each backend <literal>enable</literal> option. As a result, this restriction
+    has to be done explicitely by adding assertions in each display manager
+    backend module.</para>
+
+  <para>On the other hand, managing the display managers backends in the
+    central module will require to change the central module option every time
+    a new backend is added or removed.</para>
+
+  <para>By using extensible option types, it is possible to create a placeholder 
+    option in the central module (<xref linkend='ex-option-declaration-eot-service' 
+      />), and to extend it in each backend module (<xref 
+      linkend='ex-option-declaration-eot-backend-slim' />, <xref 
+      linkend='ex-option-declaration-eot-backend-kdm' />).</para>
+ 
+  <para>As a result, <literal>displayManager.enable</literal> option values can
+  be added without changing the main service module file and the type system
+  automatically enforce that there can only be a single display manager
+  enabled.</para>
+
+<example xml:id='ex-option-declaration-eot-service'><title>Extensible type 
+    placeholder in the service module</title>
+<screen>
+services.xserver.displayManager.enable = mkOption {
+  description = "Display manager to use";
+  type = with types; nullOr (enum [ ]);
+};</screen></example>
+
+<example xml:id='ex-option-declaration-eot-backend-slim'><title>Extending 
+    <literal>services.xserver.displayManager.enable</literal> in the 
+    <literal>slim</literal> module</title>
+<screen>
+services.xserver.displayManager.enable = mkOption {
+  type = with types; nullOr (enum [ "slim" ]);
+};</screen></example>
+
+<example xml:id='ex-option-declaration-eot-backend-kdm'><title>Extending 
+    <literal>services.foo.backend</literal> in the <literal>kdm</literal> 
+    module</title>
+<screen>
+services.xserver.displayManager.enable = mkOption {
+  type = with types; nullOr (enum [ "kdm" ]);
+};</screen></example>
+
+<para>The placeholder declaration is a standard <literal>mkOption</literal> 
+  declaration, but it is important that extensible option declarations only use 
+  the <literal>type</literal> argument.</para>
+
+<para>Extensible option types work with any of the composed variants of 
+  <literal>enum</literal> such as 
+  <literal>with types; nullOr (enum [ "foo" "bar" ])</literal> 
+  or <literal>with types; listOf (enum [ "foo" "bar" ])</literal>.</para>
+
+</section>
 </section>
diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml
index 9ef7bb30a57..8e6ac53ad48 100644
--- a/nixos/doc/manual/development/option-types.xml
+++ b/nixos/doc/manual/development/option-types.xml
@@ -62,23 +62,45 @@
     <listitem><para>A string. Multiple definitions are concatenated with a 
         collon <literal>":"</literal>.</para></listitem>
   </varlistentry>
+</variablelist>
+
+ </section>
+
+ <section><title>Value Types</title>
+
+   <para>Value types are type that take a value parameter. The only value type 
+     in the library is <literal>enum</literal>.</para>
+
+<variablelist>
   <varlistentry>
-    <term><varname>types.separatedString</varname> 
+    <term><varname>types.enum</varname> <replaceable>l</replaceable></term>
+    <listitem><para>One element of the list <replaceable>l</replaceable>, e.g. 
+        <literal>types.enum [ "left" "right" ]</literal>. Multiple definitions 
+        cannot be merged.</para></listitem>
+  </varlistentry>
+  <varlistentry>
+    <term><varname>types.separatedString</varname>
       <replaceable>sep</replaceable></term>
-    <listitem><para>A string with a custom separator 
-        <replaceable>sep</replaceable>, e.g. <literal>types.separatedString 
+    <listitem><para>A string with a custom separator
+        <replaceable>sep</replaceable>, e.g. <literal>types.separatedString
           "|"</literal>.</para></listitem>
   </varlistentry>
+  <varlistentry>
+    <term><varname>types.submodule</varname> <replaceable>o</replaceable></term>
+    <listitem><para>A set of sub options <replaceable>o</replaceable>.
+        <replaceable>o</replaceable> can be an attribute set or a function
+        returning an attribute set. Submodules are used in composed types to
+        create modular options. Submodule are detailed in <xref
+          linkend='section-option-types-submodule' />.</para></listitem>
+  </varlistentry>
 </variablelist>
-
  </section>
 
  <section><title>Composed Types</title>
 
-   <para>Composed types allow to create complex types by taking another type(s) 
-     or value(s) as parameter(s).
-     It is possible to compose types multiple times, e.g. <literal>with types; 
-       nullOr (enum [ "left" "right" ])</literal>.</para>
+   <para>Composed types are types that take a type as parameter. <literal>listOf 
+       int</literal> and <literal>either int str</literal> are examples of 
+     composed types.</para>
 
 <variablelist>
   <varlistentry>
@@ -112,12 +134,6 @@
         once.</para></listitem>
   </varlistentry>
   <varlistentry>
-    <term><varname>types.enum</varname> <replaceable>l</replaceable></term>
-    <listitem><para>One element of the list <replaceable>l</replaceable>, e.g. 
-        <literal>types.enum [ "left" "right" ]</literal>. Multiple definitions 
-        cannot be merged</para></listitem>
-  </varlistentry>
-  <varlistentry>
     <term><varname>types.either</varname> <replaceable>t1</replaceable> 
       <replaceable>t2</replaceable></term>
     <listitem><para>Type <replaceable>t1</replaceable> or type 
@@ -125,14 +141,6 @@
           str</literal>. Multiple definitions cannot be 
         merged.</para></listitem>
   </varlistentry>
-  <varlistentry>
-    <term><varname>types.submodule</varname> <replaceable>o</replaceable></term>
-    <listitem><para>A set of sub options <replaceable>o</replaceable>. 
-        <replaceable>o</replaceable> can be an attribute set or a function 
-        returning an attribute set. Submodules are used in composed types to 
-        create modular options. Submodule are detailed in <xref 
-          linkend='section-option-types-submodule' />.</para></listitem>
-  </varlistentry>
 </variablelist>
 
 </section>
@@ -191,7 +199,6 @@ options.mod = mkOption {
   type = with types; listOf (submodule modOptions);
 };</screen></example>
 
-
 <section><title>Composed with <literal>listOf</literal></title>
 
   <para>When composed with <literal>listOf</literal>, submodule allows multiple 
@@ -317,9 +324,13 @@ code before creating a new type.</para>
 <variablelist>
   <varlistentry>
     <term><varname>name</varname></term>
-    <listitem><para>A string representation of the type function name, name 
-        usually changes accordingly parameters passed to 
-        types.</para></listitem>
+    <listitem><para>A string representation of the type function 
+        name.</para></listitem>
+  </varlistentry>
+  <varlistentry>
+    <term><varname>definition</varname></term>
+    <listitem><para>Description of the type used in documentation. Give 
+        information of the type and any of its arguments.</para></listitem>
   </varlistentry>
   <varlistentry>
     <term><varname>check</varname></term>
@@ -382,6 +393,53 @@ code before creating a new type.</para>
         type parameter, this function should be defined as <literal>m: 
           composedType (elemType.substSubModules m)</literal>.</para></listitem>
   </varlistentry>
+  <varlistentry>
+    <term><varname>typeMerge</varname></term>
+    <listitem><para>A function to merge multiple type declarations. Takes the 
+        type to merge <literal>functor</literal> as parameter. A 
+        <literal>null</literal> return value means that type cannot be 
+        merged.</para>
+      <variablelist>
+        <varlistentry>
+          <term><replaceable>f</replaceable></term>
+          <listitem><para>The type to merge  
+              <literal>functor</literal>.</para></listitem>
+        </varlistentry>
+      </variablelist>
+      <para>Note: There is a generic <literal>defaultTypeMerge</literal> that 
+        work with most of value and composed types.</para>
+    </listitem>
+  </varlistentry>
+  <varlistentry>
+    <term><varname>functor</varname></term>
+    <listitem><para>An attribute set representing the type. It is used for type 
+        operations and has the following keys:</para>
+      <variablelist>
+        <varlistentry>
+          <term><varname>type</varname></term>
+          <listitem><para>The type function.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>wrapped</varname></term>
+          <listitem><para>Holds the type parameter for composed types.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>payload</varname></term>
+          <listitem><para>Holds the value parameter for value types. 
+              The types that have a <literal>payload</literal> are the
+              <literal>enum</literal>, <literal>separatedString</literal> and
+              <literal>submodule</literal> types.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>binOp</varname></term>
+          <listitem><para>A binary operation that can merge the payloads of two 
+              same types. Defined as a function that take two payloads as 
+              parameters and return the payloads merged.</para></listitem>
+        </varlistentry>
+      </variablelist>
+    </listitem>
+  </varlistentry>
 </variablelist>
 
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-1703.xml b/nixos/doc/manual/release-notes/rl-1703.xml
index efff8b895a1..743f3dce230 100644
--- a/nixos/doc/manual/release-notes/rl-1703.xml
+++ b/nixos/doc/manual/release-notes/rl-1703.xml
@@ -75,7 +75,10 @@ following incompatible changes:</para>
 
 <itemizedlist>
   <listitem>
-    <para></para>
+    <para>Module type system have a new extensible option types feature that
+      allow to extend certain types, such as enum, through multiple option
+      declarations of the same option across multiple modules.
+    </para>
   </listitem>
 </itemizedlist>
 
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
index 17c17d05e28..27eacda48a8 100644
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ b/nixos/modules/installer/tools/nixos-option.sh
@@ -256,7 +256,7 @@ if isOption opt then
   // optionalAttrs (opt ? default) { inherit (opt) default; }
   // optionalAttrs (opt ? example) { inherit (opt) example; }
   // optionalAttrs (opt ? description) { inherit (opt) description; }
-  // optionalAttrs (opt ? type) { typename = opt.type.name; }
+  // optionalAttrs (opt ? type) { typename = opt.type.description; }
   // optionalAttrs (opt ? options) { inherit (opt) options; }
   // {
     # to disambiguate the xml output.