summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSilvan Mosberger <contact@infinisil.com>2022-03-02 18:29:44 +0100
committerGitHub <noreply@github.com>2022-03-02 18:29:44 +0100
commitc1dfec2be66a995dbf6c73e01b3d07a2adf5788b (patch)
tree54b7f1d5a3b522a624a0b1093a6c27294291bf81 /lib
parentbff70d25ffb12e24451f3015ca5aae83284b34ba (diff)
parent023fa7b9239e645f7dfcbbe3ee82ab0093f9978d (diff)
Merge pull request #149689 from Infinisil/types-type
Introduce `types.optionType` and use it for `freeformType`
Diffstat (limited to 'lib')
-rw-r--r--lib/modules.nix3
-rwxr-xr-xlib/tests/modules.sh12
-rw-r--r--lib/tests/modules/freeform-submodules.nix22
-rw-r--r--lib/tests/modules/optionTypeFile.nix28
-rw-r--r--lib/tests/modules/optionTypeMerging.nix27
-rw-r--r--lib/types.nix31
6 files changed, 120 insertions, 3 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
index e9fc1007fc20..79d54e4a5387 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -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 = ''
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index a1c592cf4ef0..e4bb7ad21900 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -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
@@ -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
diff --git a/lib/tests/modules/freeform-submodules.nix b/lib/tests/modules/freeform-submodules.nix
new file mode 100644
index 000000000000..3910435a7b5c
--- /dev/null
+++ b/lib/tests/modules/freeform-submodules.nix
@@ -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;
+}
diff --git a/lib/tests/modules/optionTypeFile.nix b/lib/tests/modules/optionTypeFile.nix
new file mode 100644
index 000000000000..6015d59a72c9
--- /dev/null
+++ b/lib/tests/modules/optionTypeFile.nix
@@ -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;
+ };
+ })
+ ];
+
+}
diff --git a/lib/tests/modules/optionTypeMerging.nix b/lib/tests/modules/optionTypeMerging.nix
new file mode 100644
index 000000000000..74a620c4620c
--- /dev/null
+++ b/lib/tests/modules/optionTypeMerging.nix
@@ -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";
+
+}
diff --git a/lib/types.nix b/lib/types.nix
index c47a35cd0d67..acc7eac104ee 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -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 ? {}