aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Sagnes <eric.sagnes@gmail.com>2016-09-07 10:03:32 +0900
committerNicolas B. Pierron <nicolas.b.pierron@gmail.com>2016-11-06 00:05:58 +0100
commite14de56613fc8e42fb6249031efe9e7abbb65286 (patch)
tree4a0d884bf0b5698cf42705ff25c5806ace87951f
parentd10356b82558fe50e1ad0fa1fb5e151c43ed3e0a (diff)
module system: extensible option types
-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 8db17c605799..e66d6a6926cb 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 444ec37e6eaf..2092b65bbc3a 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 991fa0e5c291..26523f59f256 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 7be5e9d51d46..ce432a7fa6ca 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 9ef7bb30a576..8e6ac53ad480 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 efff8b895a1a..743f3dce2302 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 17c17d05e288..27eacda48a87 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.