lib/types: minimal fix for the regression of either when used in freeformType (#440459)

This commit is contained in:
Johannes Kirschbauer
2025-09-14 04:38:10 +00:00
committed by GitHub
7 changed files with 119 additions and 2 deletions
+6 -1
View File
@@ -244,8 +244,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}
+22
View File
@@ -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
@@ -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
};
}
@@ -0,0 +1,18 @@
# Obviously wrong typed
{
config.either = {
int = "foo";
};
config.eitherBehindNullor = {
int = "foo";
};
config.oneOf = {
int = "foo";
};
config.number = {
str = "foo";
};
}
@@ -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;
};
}
@@ -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
});
};
}
+6 -1
View File
@@ -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;