Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion doc/release-notes/rl-2511.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
22 changes: 22 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions lib/tests/modules/freeform-attrsof-either.nix
Original file line number Diff line number Diff line change
@@ -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
};
}
18 changes: 18 additions & 0 deletions lib/tests/modules/freeform-deprecated-malicous-wrong.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Obviously wrong typed
{
config.either = {
int = "foo";
};

config.eitherBehindNullor = {
int = "foo";
};

config.oneOf = {
int = "foo";
};

config.number = {
str = "foo";
};
}
19 changes: 19 additions & 0 deletions lib/tests/modules/freeform-deprecated-malicous-wrong2.nix
Original file line number Diff line number Diff line change
@@ -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;
};
}
34 changes: 34 additions & 0 deletions lib/tests/modules/freeform-deprecated-malicous.nix
Original file line number Diff line number Diff line change
@@ -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
});
};
}
7 changes: 6 additions & 1 deletion lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading