summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorSilvan Mosberger <contact@infinisil.com>2020-09-04 13:18:22 +0200
committerSilvan Mosberger <contact@infinisil.com>2020-09-15 21:01:07 +0200
commit67551f46fbc20b3b96ff27503b659a8f3fedb421 (patch)
tree7c9727af4b245ab77eb83ec5025c7c532a246271 /lib
parent6e7bc2c6c90335e7bb7d7ca8cef77c58f0e37444 (diff)
lib/types: Introduce types.anything
This new type has unsurprising merge behavior: Only attribute sets are
merged together (recursively), and only if they don't conflict.

This is in contrast to the existing types:
- types.attrs is problematic because later definitions completely
  override attributes of earlier definitions, and it doesn't support
  mkIf and co.
- types.unspecified is very similar to types.attrs, but it has smart
  merging behavior that often doesn't make sense, and it doesn't support
  all types
Diffstat (limited to 'lib')
-rw-r--r--lib/types.nix36
1 files changed, 36 insertions, 0 deletions
diff --git a/lib/types.nix b/lib/types.nix
index 17e7a939fe3d3..a5f4d87e3efde 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -101,6 +101,42 @@ rec {
   # When adding new types don't forget to document them in
   # nixos/doc/manual/development/option-types.xml!
   types = rec {
+
+    anything = mkOptionType {
+      name = "anything";
+      description = "anything";
+      check = value: true;
+      merge = loc: defs:
+        let
+          getType = value:
+            if isAttrs value && isCoercibleToString value
+            then "stringCoercibleSet"
+            else builtins.typeOf value;
+
+          # Returns the common type of all definitions, throws an error if they
+          # don't have the same type
+          commonType = foldl' (type: def:
+            if getType def.value == type
+            then type
+            else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
+          ) (getType (head defs).value) defs;
+
+          mergeFunction = {
+            # Recursively merge attribute sets
+            set = (attrsOf anything).merge;
+            # Safe and deterministic behavior for lists is to only accept one definition
+            # listOf only used to apply mkIf and co.
+            list =
+              if length defs > 1
+              then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
+              else (listOf anything).merge;
+            # This is the type of packages, only accept a single definition
+            stringCoercibleSet = mergeOneOption;
+            # Otherwise fall back to only allowing all equal definitions
+          }.${commonType} or mergeEqualOption;
+        in mergeFunction loc defs;
+    };
+
     unspecified = mkOptionType {
       name = "unspecified";
     };