diff --git a/doc/release-notes/rl-2511.section.md b/doc/release-notes/rl-2511.section.md index a62e6bc0c001d..e538373e8e2c6 100644 --- a/doc/release-notes/rl-2511.section.md +++ b/doc/release-notes/rl-2511.section.md @@ -176,8 +176,13 @@ ### Deprecations {#sec-nixpkgs-release-25.11-lib-deprecations} -- Create the first release note entry in this section! +- `types.either` silently accepted mismatching types when used in `freeformType`. Module maintainers should fix the used type + In most cases wrapping `either` with `attrsOf` should be sufficient. + Since types.either was used to bootstrap other types. This also affects the following types: + - `oneOf` + - `number` + - `numbers.*` ### Additions and Improvements {#sec-nixpkgs-release-25.11-lib-additions-improvements} diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index fbbccd8172f67..3b402b7d856cd 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -550,6 +550,28 @@ checkConfigOutput '/freeform-submodules.nix"$' config.fooDeclarations.0 ./freefo checkConfigOutput '^10$' config.free.xxx.foo ./freeform-submodules.nix checkConfigOutput '^10$' config.free.yyy.bar ./freeform-submodules.nix +# Regression of either, due to freeform not beeing checked previously +checkConfigOutput '^"foo"$' config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +checkConfigOutput '^"foo"$' config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +checkConfigOutput '^"foo"$' config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +checkConfigOutput '^"foo"$' config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix + +checkConfigOutput '^42$' config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +checkConfigOutput '^42$' config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +checkConfigOutput '^42$' config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +checkConfigOutput '^42$' config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix +# Value OK: Fail if a warning is emitted +NIX_ABORT_ON_WARN=1 checkConfigOutput "^42$" config.number.int ./freeform-attrsof-either.nix + + ## types.anything # Check that attribute sets are merged recursively checkConfigOutput '^null$' config.value.foo ./types-anything/nested-attrs.nix diff --git a/lib/tests/modules/freeform-attrsof-either.nix b/lib/tests/modules/freeform-attrsof-either.nix new file mode 100644 index 0000000000000..3d6ea05a88efc --- /dev/null +++ b/lib/tests/modules/freeform-attrsof-either.nix @@ -0,0 +1,14 @@ +{ lib, ... }: +let + inherit (lib) types mkOption; +in +{ + options.number = mkOption { + type = types.submodule ({ + freeformType = types.attrsOf (types.either types.int types.int); + }); + default = { + int = 42; + }; # should not emit a warning + }; +} diff --git a/lib/tests/modules/freeform-deprecated-malicous-wrong.nix b/lib/tests/modules/freeform-deprecated-malicous-wrong.nix new file mode 100644 index 0000000000000..4d1e86cf5ae1e --- /dev/null +++ b/lib/tests/modules/freeform-deprecated-malicous-wrong.nix @@ -0,0 +1,18 @@ +# Obviously wrong typed +{ + config.either = { + int = "foo"; + }; + + config.eitherBehindNullor = { + int = "foo"; + }; + + config.oneOf = { + int = "foo"; + }; + + config.number = { + str = "foo"; + }; +} diff --git a/lib/tests/modules/freeform-deprecated-malicous-wrong2.nix b/lib/tests/modules/freeform-deprecated-malicous-wrong2.nix new file mode 100644 index 0000000000000..62c2a7befeb5c --- /dev/null +++ b/lib/tests/modules/freeform-deprecated-malicous-wrong2.nix @@ -0,0 +1,19 @@ +# freeeformType should have been (attrsOf either) +# This should also print the warning +{ + config.either = { + int = 42; + }; + + config.eitherBehindNullor = { + int = 42; + }; + + config.oneOf = { + int = 42; + }; + + config.number = { + str = 42; + }; +} diff --git a/lib/tests/modules/freeform-deprecated-malicous.nix b/lib/tests/modules/freeform-deprecated-malicous.nix new file mode 100644 index 0000000000000..47d5bd2f69dd7 --- /dev/null +++ b/lib/tests/modules/freeform-deprecated-malicous.nix @@ -0,0 +1,34 @@ +{ lib, ... }: +let + inherit (lib) types mkOption; +in +{ + options.either = mkOption { + type = types.submodule ({ + freeformType = (types.either types.int types.int); + }); + }; + + options.eitherBehindNullor = mkOption { + type = types.submodule ({ + freeformType = types.nullOr (types.either types.int types.int); + }); + }; + + options.oneOf = mkOption { + type = types.submodule ({ + freeformType = ( + types.oneOf [ + types.int + types.int + ] + ); + }); + }; + + options.number = mkOption { + type = types.submodule ({ + freeformType = (types.number); # either int float + }); + }; +} diff --git a/lib/types.nix b/lib/types.nix index 6b51f9254a00c..c7136de7f2a63 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -1453,7 +1453,12 @@ let headError = { message = "The option `${showOption loc}` is neither a value of type `${t1.description}` nor `${t2.description}`, Definition values: ${showDefs defs}"; }; - value = abort "(t.merge.v2 defs).value must only be accessed when `.headError == null`. This is a bug in code that consumes a module system type."; + value = lib.warn '' + One or more definitions did not pass the type-check of the 'either' type. + ${headError.message} + If `either`, `oneOf` or similar is used in freeformType, ensure that it is preceded by an 'attrsOf' such as: `freeformType = types.attrsOf (types.either t1 t2)`. + Otherwise consider using the correct type for the option `${showOption loc}`. This will be an error in Nixpkgs 26.06. + '' (mergeOneOption loc defs); }; in checkedAndMerged;