Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce types.optionType and use it for freeformType #149689

Merged
merged 2 commits into from
Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ rec {
};

_module.freeformType = mkOption {
# Disallow merging for now, but could be implemented nicely with a `types.optionType`
type = types.nullOr (types.uniq types.attrs);
type = types.nullOr types.optionType;
internal = true;
default = null;
description = ''
Expand Down
12 changes: 12 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ checkConfigOutput '^"24"$' config.foo ./freeform-attrsOf.nix ./freeform-str-dep-
checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.nix ./freeform-unstr-dep-str.nix
checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix
checkConfigOutput '^"24"$' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix
# submodules in freeformTypes should have their locations annotated
checkConfigOutput '/freeform-submodules.nix"$' config.fooDeclarations.0 ./freeform-submodules.nix
# freeformTypes can get merged using `types.type`, including submodules
checkConfigOutput '^10$' config.free.xxx.foo ./freeform-submodules.nix
checkConfigOutput '^10$' config.free.yyy.bar ./freeform-submodules.nix

## types.anything
# Check that attribute sets are merged recursively
Expand Down Expand Up @@ -299,6 +304,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
Expand Down
22 changes: 22 additions & 0 deletions lib/tests/modules/freeform-submodules.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{ lib, options, ... }: with lib.types; {

options.fooDeclarations = lib.mkOption {
default = (options.free.type.getSubOptions [])._freeformOptions.foo.declarations;
};

options.free = lib.mkOption {
type = submodule {
config._module.freeformType = lib.mkMerge [
(attrsOf (submodule {
options.foo = lib.mkOption {};
}))
(attrsOf (submodule {
options.bar = lib.mkOption {};
}))
];
};
};

config.free.xxx.foo = 10;
config.free.yyy.bar = 10;
}
28 changes: 28 additions & 0 deletions lib/tests/modules/optionTypeFile.nix
Original file line number Diff line number Diff line change
@@ -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;
};
})
];

}
27 changes: 27 additions & 0 deletions lib/tests/modules/optionTypeMerging.nix
Original file line number Diff line number Diff line change
@@ -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";

}
31 changes: 30 additions & 1 deletion lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 ? {}
Expand Down
7 changes: 7 additions & 0 deletions nixos/doc/manual/development/option-types.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions nixos/doc/manual/from_md/development/option-types.section.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<literal>types.optionType</literal>
</term>
<listitem>
<para>
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 <literal>_module.freeformType</literal> option.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<literal>types.attrs</literal>
Expand Down