lib.types: Introduce types.optionType

This type correctly merges multiple option types together while also
annotating them with file information. In a future commit this will be
used for `_module.freeformType`
This commit is contained in:
Silvan Mosberger 2021-12-08 19:02:29 +01:00
parent 891e65b0fa
commit 5cbeddfde4
6 changed files with 113 additions and 1 deletions

View file

@ -299,6 +299,13 @@ checkConfigOutput "10" config.processedToplevel ./raw.nix
checkConfigError "The option .multiple. is defined multiple times" config.multiple ./raw.nix
checkConfigOutput "bar" config.priorities ./raw.nix
# Test that types.optionType merges types correctly
checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
# Test that types.optionType correctly annotates option locations
checkConfigError 'The option .theOption.nested. in .other.nix. is already declared in .optionTypeFile.nix.' config.theOption.nested ./optionTypeFile.nix
cat <<EOF
====== module tests ======
$pass Pass

View file

@ -0,0 +1,28 @@
{ config, lib, ... }: {
_file = "optionTypeFile.nix";
options.theType = lib.mkOption {
type = lib.types.optionType;
};
options.theOption = lib.mkOption {
type = config.theType;
default = {};
};
config.theType = lib.mkMerge [
(lib.types.submodule {
options.nested = lib.mkOption {
type = lib.types.int;
};
})
(lib.types.submodule {
_file = "other.nix";
options.nested = lib.mkOption {
type = lib.types.str;
};
})
];
}

View file

@ -0,0 +1,27 @@
{ config, lib, ... }: {
options.theType = lib.mkOption {
type = lib.types.optionType;
};
options.theOption = lib.mkOption {
type = config.theType;
};
config.theType = lib.mkMerge [
(lib.types.submodule {
options.int = lib.mkOption {
type = lib.types.int;
default = 10;
};
})
(lib.types.submodule {
options.str = lib.mkOption {
type = lib.types.str;
};
})
];
config.theOption.str = "hello";
}

View file

@ -61,7 +61,11 @@ let
boolToString
;
inherit (lib.modules) mergeDefinitions;
inherit (lib.modules)
mergeDefinitions
fixupOptionType
mergeOptionDecls
;
outer_types =
rec {
isType = type: x: (x._type or "") == type;
@ -525,6 +529,31 @@ rec {
modules = toList modules;
};
# The type of a type!
optionType = mkOptionType {
name = "optionType";
description = "optionType";
check = value: value._type or null == "option-type";
merge = loc: defs:
let
# Prepares the type definitions for mergeOptionDecls, which
# annotates submodules types with file locations
optionModules = map ({ value, file }:
{
_file = file;
# There's no way to merge types directly from the module system,
# but we can cheat a bit by just declaring an option with the type
options = lib.mkOption {
type = value;
};
}
) defs;
# Merges all the types into a single one, including submodule merging.
# This also propagates file information to all submodules
mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
in mergedOption.type;
};
submoduleWith =
{ modules
, specialArgs ? {}

View file

@ -74,6 +74,13 @@ merging is handled.
should only be used when checking, merging and nested evaluation are not
desirable.
`types.optionType`
: The type of an option's type. Its merging operation ensures that nested
options have the correct file location annotated, and that if possible,
multiple option definitions are correctly merged together. The main use
case is as the type of the `_module.freeformType` option.
`types.attrs`
: A free-form attribute set.

View file

@ -111,6 +111,20 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<literal>types.optionType</literal>
</term>
<listitem>
<para>
The type of an options type. Its merging operation ensures
that nested options have the correct file location
annotated, and that if possible, multiple option definitions
are correctly merged together. The main use case is as the
type of the <literal>_module.freeformType</literal> option.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<literal>types.attrs</literal>