From ca81a89839176c2e0d0b0fb01a931a4a65404fa6 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 28 Jan 2024 00:30:36 +0100 Subject: [PATCH 01/23] lib.types.attrTag: init --- lib/tests/modules.sh | 11 ++++ lib/tests/modules/types-attrTag.nix | 64 +++++++++++++++++++ lib/types.nix | 37 +++++++++++ .../development/option-types.section.md | 10 +++ 4 files changed, 122 insertions(+) create mode 100644 lib/tests/modules/types-attrTag.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index b3bbdf9485ac..318efc4c3d05 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -103,6 +103,17 @@ checkConfigError 'The option .sub.wrong2. does not exist. Definition values:' co checkConfigError '.*This can happen if you e.g. declared your options in .types.submodule.' config.sub ./error-mkOption-in-submodule-config.nix checkConfigError '.*A definition for option .bad. is not of type .non-empty .list of .submodule...\.' config.bad ./error-nonEmptyListOf-submodule.nix +# types.attrTag +checkConfigOutput '^true$' config.okChecks ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.syntaxError. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.syntaxError2. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError2 ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.syntaxError3. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError3 ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.syntaxError4. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError4 ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.mergeError. is not of type .attribute-tagged union of left, right' config.intStrings.mergeError ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.badTagError. is not of type .attribute-tagged union of left, right' config.intStrings.badTagError ./types-attrTag.nix +checkConfigError 'A definition for option .intStrings\.badTagTypeError\.left. is not of type .signed integer.' config.intStrings.badTagTypeError.left ./types-attrTag.nix +checkConfigError 'A definition for option .nested\.right\.left. is not of type .signed integer.' config.nested.right.left ./types-attrTag.nix + # types.pathInStore checkConfigOutput '".*/store/0lz9p8xhf89kb1c1kk6jxrzskaiygnlh-bash-5.2-p15.drv"' config.pathInStore.ok1 ./types.nix checkConfigOutput '".*/store/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15"' config.pathInStore.ok2 ./types.nix diff --git a/lib/tests/modules/types-attrTag.nix b/lib/tests/modules/types-attrTag.nix new file mode 100644 index 000000000000..08854ca73f56 --- /dev/null +++ b/lib/tests/modules/types-attrTag.nix @@ -0,0 +1,64 @@ +{ lib, config, ... }: +let + inherit (lib) mkOption types; + forceDeep = x: builtins.deepSeq x x; +in +{ + options = { + intStrings = mkOption { + type = types.attrsOf + (types.attrTag { + left = types.int; + right = types.str; + }); + }; + nested = mkOption { + type = types.attrTag { + left = types.int; + right = types.attrTag { + left = types.int; + right = types.str; + }; + }; + }; + merged = mkOption { + type = types.attrsOf ( + types.attrTag { + yay = types.int; + } + ); + }; + okChecks = mkOption {}; + }; + imports = [ + { + options.merged = mkOption { + type = types.attrsOf ( + types.attrTag { + nay = types.bool; + } + ); + }; + } + ]; + config = { + intStrings.syntaxError = 1; + intStrings.syntaxError2 = {}; + intStrings.syntaxError3 = { a = true; b = true; }; + intStrings.syntaxError4 = lib.mkMerge [ { a = true; } { b = true; } ]; + intStrings.mergeError = lib.mkMerge [ { int = throw "do not eval"; } { string = throw "do not eval"; } ]; + intStrings.badTagError.rite = throw "do not eval"; + intStrings.badTagTypeError.left = "bad"; + intStrings.numberOne.left = 1; + intStrings.hello.right = "hello world"; + nested.right.left = "not a number"; + merged.negative.nay = false; + merged.positive.yay = 100; + okChecks = + assert config.intStrings.hello.right == "hello world"; + assert config.intStrings.numberOne.left == 1; + assert config.merged.negative.nay == false; + assert config.merged.positive.yay == 100; + true; + }; +} diff --git a/lib/types.nix b/lib/types.nix index 12bf18633e3a..59577856f4e1 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -614,6 +614,43 @@ rec { nestedTypes.elemType = elemType; }; + attrTag = tags: attrTagWith { inherit tags; }; + + attrTagWith = { tags }: + let + choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags); + in + mkOptionType { + name = "attrTag"; + description = "attribute-tagged union of ${choicesStr}"; + getSubModules = null; + substSubModules = m: attrTagWith { tags = mapAttrs (n: v: v.substSubModules m) tags; }; + check = v: isAttrs v && length (attrNames v) == 1 && tags?${head (attrNames v)}; + merge = loc: defs: + let + choice = head (attrNames (head defs).value); + checkedValueDefs = map + (def: + assert (length (attrNames def.value)) == 1; + if (head (attrNames def.value)) != choice + then throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}." + else { inherit (def) file; value = def.value.${choice}; }) + defs; + in + if tags?${choice} + then + { ${choice} = + (mergeDefinitions + (loc ++ [choice]) + tags.${choice} + checkedValueDefs + ).mergedValue; + } + else throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}."; + nestedTypes = tags; + functor = (defaultFunctor "attrTagWith") // { payload = { inherit tags; }; binOp = a: b: { tags = a.tags // b.tags; }; }; + }; + uniq = unique { message = ""; }; unique = { message }: type: mkOptionType rec { diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index 04edf99e70b0..1dfb4354f887 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -334,6 +334,16 @@ Composed types are types that take a type as parameter. `listOf the line `The option