git.iko.soy/typeable/nixpkgs
on master help sign in
Merge pull request #149689 from Infinisil/types-type

Introduce `types.optionType` and use it for `freeformType`

commitc1dfec2be66a995dbf6c73e01b3d07a2adf5788b authorSilvan Mosberger <contact@infinisil.com> dateWed 2 Mar 2022, 18:29 (5 years ago) parentbff70d2023fa7b
8 files changed+141−3
lib/modules.nix+1−2 lib/tests/modules.sh+12 lib/tests/modules/freeform-submodules.nix+22 lib/tests/modules/optionTypeFile.nix+28 lib/tests/modules/optionTypeMerging.nix+27 lib/types.nix+30−1 nixos/doc/manual/development/option-types.section.md+7 nixos/doc/manual/from_md/development/option-types.section.xml+14
lib/modules.nix+1−2
@@ -151,8 +151,7 @@ rec {
151151 };
152152
153153 _module.freeformType = mkOption {
154− # Disallow merging for now, but could be implemented nicely with a `types.optionType`
155− type = types.nullOr (types.uniq types.attrs);
154+ type = types.nullOr types.optionType;
156155 internal = true;
157156 default = null;
158157 description = ''
lib/tests/modules.sh+12
@@ -240,6 +240,11 @@ checkConfigOutput '^"24"$' config.foo ./freeform-attrsOf.nix ./freeform-str-dep-
240240checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.nix ./freeform-unstr-dep-str.nix
241241checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix
242242checkConfigOutput '^"24"$' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix
243+# submodules in freeformTypes should have their locations annotated
244+checkConfigOutput '/freeform-submodules.nix"$' config.fooDeclarations.0 ./freeform-submodules.nix
245+# freeformTypes can get merged using `types.type`, including submodules
246+checkConfigOutput '^10$' config.free.xxx.foo ./freeform-submodules.nix
247+checkConfigOutput '^10$' config.free.yyy.bar ./freeform-submodules.nix
243248
244249## types.anything
245250# Check that attribute sets are merged recursively
@@ -299,6 +304,13 @@ checkConfigOutput "10" config.processedToplevel ./raw.nix
299304checkConfigError "The option .multiple. is defined multiple times" config.multiple ./raw.nix
300305checkConfigOutput "bar" config.priorities ./raw.nix
301306
307+# Test that types.optionType merges types correctly
308+checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
309+checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
310+
311+# Test that types.optionType correctly annotates option locations
312+checkConfigError 'The option .theOption.nested. in .other.nix. is already declared in .optionTypeFile.nix.' config.theOption.nested ./optionTypeFile.nix
313+
302314cat <<EOF
303315====== module tests ======
304316$pass Pass
lib/tests/modules/freeform-submodules.nixnew file+22
@@ -0,0 +1,22 @@
1+{ lib, options, ... }: with lib.types; {
2+
3+ options.fooDeclarations = lib.mkOption {
4+ default = (options.free.type.getSubOptions [])._freeformOptions.foo.declarations;
5+ };
6+
7+ options.free = lib.mkOption {
8+ type = submodule {
9+ config._module.freeformType = lib.mkMerge [
10+ (attrsOf (submodule {
11+ options.foo = lib.mkOption {};
12+ }))
13+ (attrsOf (submodule {
14+ options.bar = lib.mkOption {};
15+ }))
16+ ];
17+ };
18+ };
19+
20+ config.free.xxx.foo = 10;
21+ config.free.yyy.bar = 10;
22+}
lib/tests/modules/optionTypeFile.nixnew file+28
@@ -0,0 +1,28 @@
1+{ config, lib, ... }: {
2+
3+ _file = "optionTypeFile.nix";
4+
5+ options.theType = lib.mkOption {
6+ type = lib.types.optionType;
7+ };
8+
9+ options.theOption = lib.mkOption {
10+ type = config.theType;
11+ default = {};
12+ };
13+
14+ config.theType = lib.mkMerge [
15+ (lib.types.submodule {
16+ options.nested = lib.mkOption {
17+ type = lib.types.int;
18+ };
19+ })
20+ (lib.types.submodule {
21+ _file = "other.nix";
22+ options.nested = lib.mkOption {
23+ type = lib.types.str;
24+ };
25+ })
26+ ];
27+
28+}
lib/tests/modules/optionTypeMerging.nixnew file+27
@@ -0,0 +1,27 @@
1+{ config, lib, ... }: {
2+
3+ options.theType = lib.mkOption {
4+ type = lib.types.optionType;
5+ };
6+
7+ options.theOption = lib.mkOption {
8+ type = config.theType;
9+ };
10+
11+ config.theType = lib.mkMerge [
12+ (lib.types.submodule {
13+ options.int = lib.mkOption {
14+ type = lib.types.int;
15+ default = 10;
16+ };
17+ })
18+ (lib.types.submodule {
19+ options.str = lib.mkOption {
20+ type = lib.types.str;
21+ };
22+ })
23+ ];
24+
25+ config.theOption.str = "hello";
26+
27+}
lib/types.nix+30−1
@@ -61,7 +61,11 @@ let
6161 boolToString
6262 ;
6363
64− inherit (lib.modules) mergeDefinitions;
64+ inherit (lib.modules)
65+ mergeDefinitions
66+ fixupOptionType
67+ mergeOptionDecls
68+ ;
6569 outer_types =
6670rec {
6771 isType = type: x: (x._type or "") == type;
@@ -525,6 +529,31 @@ rec {
525529 modules = toList modules;
526530 };
527531
532+ # The type of a type!
533+ optionType = mkOptionType {
534+ name = "optionType";
535+ description = "optionType";
536+ check = value: value._type or null == "option-type";
537+ merge = loc: defs:
538+ let
539+ # Prepares the type definitions for mergeOptionDecls, which
540+ # annotates submodules types with file locations
541+ optionModules = map ({ value, file }:
542+ {
543+ _file = file;
544+ # There's no way to merge types directly from the module system,
545+ # but we can cheat a bit by just declaring an option with the type
546+ options = lib.mkOption {
547+ type = value;
548+ };
549+ }
550+ ) defs;
551+ # Merges all the types into a single one, including submodule merging.
552+ # This also propagates file information to all submodules
553+ mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
554+ in mergedOption.type;
555+ };
556+
528557 submoduleWith =
529558 { modules
530559 , specialArgs ? {}
nixos/doc/manual/development/option-types.section.md+7
@@ -74,6 +74,13 @@ merging is handled.
7474 should only be used when checking, merging and nested evaluation are not
7575 desirable.
7676
77+`types.optionType`
78+
79+: The type of an option's type. Its merging operation ensures that nested
80+ options have the correct file location annotated, and that if possible,
81+ multiple option definitions are correctly merged together. The main use
82+ case is as the type of the `_module.freeformType` option.
83+
7784`types.attrs`
7885
7986: A free-form attribute set.
nixos/doc/manual/from_md/development/option-types.section.xml+14
@@ -111,6 +111,20 @@
111111 </para>
112112 </listitem>
113113 </varlistentry>
114+ <varlistentry>
115+ <term>
116+ <literal>types.optionType</literal>
117+ </term>
118+ <listitem>
119+ <para>
120+ The type of an option’s type. Its merging operation ensures
121+ that nested options have the correct file location
122+ annotated, and that if possible, multiple option definitions
123+ are correctly merged together. The main use case is as the
124+ type of the <literal>_module.freeformType</literal> option.
125+ </para>
126+ </listitem>
127+ </varlistentry>
114128 <varlistentry>
115129 <term>
116130 <literal>types.attrs</literal>