1
0
Fork 1
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-01-22 14:45:27 +00:00

module system: extensible option types

This commit is contained in:
Eric Sagnes 2016-09-07 10:03:32 +09:00 committed by Nicolas B. Pierron
parent d10356b825
commit e14de56613
7 changed files with 317 additions and 70 deletions

View file

@ -231,12 +231,20 @@ rec {
correspond to the definition of 'loc' in 'opt.file'. */ correspond to the definition of 'loc' in 'opt.file'. */
mergeOptionDecls = loc: opts: mergeOptionDecls = loc: opts:
foldl' (res: opt: foldl' (res: opt:
if opt.options ? default && res ? default || let t = res.type;
opt.options ? example && res ? example || t' = opt.options.type;
opt.options ? description && res ? description || mergedType = t.typeMerge t'.functor;
opt.options ? apply && res ? apply || typesMergeable = mergedType != null;
# Accept to merge options which have identical types. typeSet = if (bothHave "type") && typesMergeable
opt.options ? type && res ? type && opt.options.type.name != res.type.name 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 then
throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}." throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
else else
@ -258,7 +266,7 @@ rec {
in opt.options // res // in opt.options // res //
{ declarations = res.declarations ++ [opt.file]; { declarations = res.declarations ++ [opt.file];
options = submodules; options = submodules;
} } // typeSet
) { inherit loc; declarations = []; options = []; } opts; ) { inherit loc; declarations = []; options = []; } opts;
/* Merge all the definitions of an option to produce the final /* Merge all the definitions of an option to produce the final
@ -422,12 +430,14 @@ rec {
options = opt.options or options = opt.options or
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}."); (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
f = tp: f = tp:
let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
in
if tp.name == "option set" || tp.name == "submodule" then if tp.name == "option set" || tp.name == "submodule" then
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}." 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 optionSetIn "attrsOf" 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 optionSetIn "loaOf" then types.loaOf (types.submodule options)
else if tp.name == "list of option sets" then types.listOf (types.submodule options) else if optionSetIn "listOf" then types.listOf (types.submodule options)
else if tp.name == "null or option set" then types.nullOr (types.submodule options) else if optionSetIn "nullOr" then types.nullOr (types.submodule options)
else tp; else tp;
in in
if opt.type.getSubModules or null == null if opt.type.getSubModules or null == null

View file

@ -92,7 +92,7 @@ rec {
internal = opt.internal or false; internal = opt.internal or false;
visible = opt.visible or true; visible = opt.visible or true;
readOnly = opt.readOnly or false; 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 ? example then { example = scrubOptionValue opt.example; } else {})
// (if opt ? default then { default = scrubOptionValue opt.default; } else {}) // (if opt ? default then { default = scrubOptionValue opt.default; } else {})

View file

@ -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"; isOptionType = isType "option-type";
mkOptionType = mkOptionType =
{ # Human-readable representation of the type. { # Human-readable representation of the type, should be equivalent to
# the type function name.
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 , # Function applied to each definition that should return true if
# its type-correct, false otherwise. # its type-correct, false otherwise.
check ? (x: true) check ? (x: true)
@ -36,12 +69,26 @@ rec {
getSubOptions ? prefix: {} getSubOptions ? prefix: {}
, # List of modules if any, or null if none. , # List of modules if any, or null if none.
getSubModules ? null 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. # modules.
substSubModules ? m: null 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"; { _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 { bool = mkOptionType {
name = "boolean"; name = "bool";
description = "boolean";
check = isBool; check = isBool;
merge = mergeEqualOption; merge = mergeEqualOption;
}; };
int = mkOptionType { int = mkOptionType rec {
name = "integer"; name = "int";
description = "integer";
check = isInt; check = isInt;
merge = mergeOneOption; merge = mergeOneOption;
}; };
str = mkOptionType { str = mkOptionType {
name = "string"; name = "str";
description = "string";
check = isString; check = isString;
merge = mergeOneOption; merge = mergeOneOption;
}; };
# Merge multiple definitions by concatenating them (with the given # Merge multiple definitions by concatenating them (with the given
# separator between the values). # separator between the values).
separatedString = sep: mkOptionType { separatedString = sep: mkOptionType rec {
name = "string"; name = "separatedString";
description = "string";
check = isString; check = isString;
merge = loc: defs: concatStringsSep sep (getValues defs); 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"; lines = separatedString "\n";
@ -86,7 +143,8 @@ rec {
string = separatedString ""; string = separatedString "";
attrs = mkOptionType { attrs = mkOptionType {
name = "attribute set"; name = "attrs";
description = "attribute set";
check = isAttrs; check = isAttrs;
merge = loc: foldl' (res: def: mergeAttrs res def.value) {}; merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
}; };
@ -114,8 +172,9 @@ rec {
# drop this in the future: # drop this in the future:
list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf; list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
listOf = elemType: mkOptionType { listOf = elemType: mkOptionType rec {
name = "list of ${elemType.name}s"; name = "listOf";
description = "list of ${elemType.description}s";
check = isList; check = isList;
merge = loc: defs: merge = loc: defs:
map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def: map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def:
@ -132,10 +191,12 @@ rec {
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = m: listOf (elemType.substSubModules m); substSubModules = m: listOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
}; };
attrsOf = elemType: mkOptionType { attrsOf = elemType: mkOptionType rec {
name = "attribute set of ${elemType.name}s"; name = "attrsOf";
description = "attribute set of ${elemType.description}s";
check = isAttrs; check = isAttrs;
merge = loc: defs: merge = loc: defs:
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: 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>"]); getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = m: attrsOf (elemType.substSubModules m); substSubModules = m: attrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
}; };
# List or attribute set of ... # List or attribute set of ...
@ -165,18 +227,21 @@ rec {
def; def;
listOnly = listOf elemType; listOnly = listOf elemType;
attrOnly = attrsOf elemType; attrOnly = attrsOf elemType;
in mkOptionType { in mkOptionType rec {
name = "list or attribute set of ${elemType.name}s"; name = "loaOf";
description = "list or attribute set of ${elemType.description}s";
check = x: isList x || isAttrs x; check = x: isList x || isAttrs x;
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs); merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]); getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = m: loaOf (elemType.substSubModules m); substSubModules = m: loaOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
}; };
# List or element of ... # List or element of ...
loeOf = elemType: mkOptionType { loeOf = elemType: mkOptionType rec {
name = "element or list of ${elemType.name}s"; name = "loeOf";
description = "element or list of ${elemType.description}s";
check = x: isList x || elemType.check x; check = x: isList x || elemType.check x;
merge = loc: defs: merge = loc: defs:
let let
@ -189,18 +254,22 @@ rec {
else if !isString res then else if !isString res then
throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}." throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
else res; else res;
functor = (defaultFunctor name) // { wrapped = elemType; };
}; };
uniq = elemType: mkOptionType { uniq = elemType: mkOptionType rec {
inherit (elemType) name check; name = "uniq";
inherit (elemType) description check;
merge = mergeOneOption; merge = mergeOneOption;
getSubOptions = elemType.getSubOptions; getSubOptions = elemType.getSubOptions;
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = m: uniq (elemType.substSubModules m); substSubModules = m: uniq (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
}; };
nullOr = elemType: mkOptionType { nullOr = elemType: mkOptionType rec {
name = "null or ${elemType.name}"; name = "nullOr";
description = "null or ${elemType.description}";
check = x: x == null || elemType.check x; check = x: x == null || elemType.check x;
merge = loc: defs: merge = loc: defs:
let nrNulls = count (def: def.value == null) defs; in let nrNulls = count (def: def.value == null) defs; in
@ -211,6 +280,7 @@ rec {
getSubOptions = elemType.getSubOptions; getSubOptions = elemType.getSubOptions;
getSubModules = elemType.getSubModules; getSubModules = elemType.getSubModules;
substSubModules = m: nullOr (elemType.substSubModules m); substSubModules = m: nullOr (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
}; };
submodule = opts: submodule = opts:
@ -236,6 +306,12 @@ rec {
args = { name = ""; }; }).options; args = { name = ""; }; }).options;
getSubModules = opts'; getSubModules = opts';
substSubModules = m: submodule m; 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: enum = values:
@ -245,23 +321,35 @@ rec {
else if builtins.isInt v then builtins.toString v else if builtins.isInt v then builtins.toString v
else ''<${builtins.typeOf v}>''; else ''<${builtins.typeOf v}>'';
in in
mkOptionType { mkOptionType rec {
name = "one of ${concatMapStringsSep ", " show values}"; name = "enum";
description = "one of ${concatMapStringsSep ", " show values}";
check = flip elem values; check = flip elem values;
merge = mergeOneOption; merge = mergeOneOption;
functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
}; };
either = t1: t2: mkOptionType { either = t1: t2: mkOptionType rec {
name = "${t1.name} or ${t2.name}"; name = "either";
description = "${t1.description} or ${t2.description}";
check = x: t1.check x || t2.check x; check = x: t1.check x || t2.check x;
merge = mergeOneOption; 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 # Obsolete alternative to configOf. It takes its option
# declarations from the options attribute of containing option # declarations from the options attribute of containing option
# declaration. # declaration.
optionSet = mkOptionType { 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. # Augment the given type with an additional type check function.

View file

@ -65,4 +65,92 @@ options = {
</para> </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> </section>

View file

@ -62,23 +62,45 @@
<listitem><para>A string. Multiple definitions are concatenated with a <listitem><para>A string. Multiple definitions are concatenated with a
collon <literal>":"</literal>.</para></listitem> collon <literal>":"</literal>.</para></listitem>
</varlistentry> </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
"|"</literal>.</para></listitem>
</varlistentry>
</variablelist> </variablelist>
</section> </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.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
"|"</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> <section><title>Composed Types</title>
<para>Composed types allow to create complex types by taking another type(s) <para>Composed types are types that take a type as parameter. <literal>listOf
or value(s) as parameter(s). int</literal> and <literal>either int str</literal> are examples of
It is possible to compose types multiple times, e.g. <literal>with types; composed types.</para>
nullOr (enum [ "left" "right" ])</literal>.</para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
@ -111,12 +133,6 @@
merged. It is used to ensure option definitions are declared only merged. It is used to ensure option definitions are declared only
once.</para></listitem> once.</para></listitem>
</varlistentry> </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> <varlistentry>
<term><varname>types.either</varname> <replaceable>t1</replaceable> <term><varname>types.either</varname> <replaceable>t1</replaceable>
<replaceable>t2</replaceable></term> <replaceable>t2</replaceable></term>
@ -125,14 +141,6 @@
str</literal>. Multiple definitions cannot be str</literal>. Multiple definitions cannot be
merged.</para></listitem> merged.</para></listitem>
</varlistentry> </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> </variablelist>
</section> </section>
@ -191,7 +199,6 @@ options.mod = mkOption {
type = with types; listOf (submodule modOptions); type = with types; listOf (submodule modOptions);
};</screen></example> };</screen></example>
<section><title>Composed with <literal>listOf</literal></title> <section><title>Composed with <literal>listOf</literal></title>
<para>When composed with <literal>listOf</literal>, submodule allows multiple <para>When composed with <literal>listOf</literal>, submodule allows multiple
@ -317,9 +324,13 @@ code before creating a new type.</para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><varname>name</varname></term> <term><varname>name</varname></term>
<listitem><para>A string representation of the type function name, name <listitem><para>A string representation of the type function
usually changes accordingly parameters passed to name.</para></listitem>
types.</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>
<varlistentry> <varlistentry>
<term><varname>check</varname></term> <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: type parameter, this function should be defined as <literal>m:
composedType (elemType.substSubModules m)</literal>.</para></listitem> composedType (elemType.substSubModules m)</literal>.</para></listitem>
</varlistentry> </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> </variablelist>
</section> </section>

View file

@ -75,7 +75,10 @@ following incompatible changes:</para>
<itemizedlist> <itemizedlist>
<listitem> <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> </listitem>
</itemizedlist> </itemizedlist>

View file

@ -256,7 +256,7 @@ if isOption opt then
// optionalAttrs (opt ? default) { inherit (opt) default; } // optionalAttrs (opt ? default) { inherit (opt) default; }
// optionalAttrs (opt ? example) { inherit (opt) example; } // optionalAttrs (opt ? example) { inherit (opt) example; }
// optionalAttrs (opt ? description) { inherit (opt) description; } // 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; } // optionalAttrs (opt ? options) { inherit (opt) options; }
// { // {
# to disambiguate the xml output. # to disambiguate the xml output.