lib.types.submoduleWith: Avoid _key collisions after extendModules

+7 -1
lib/modules.nix
···
args ? {}
, # This would be remove in the future, Prefer _module.check option instead.
check ? true
+
# Internal variable to avoid `_key` collisions regardless
+
# of `extendModules`. Used in `submoduleWith`.
+
# Test case: lib/tests/modules, "168767"
+
, extensionOffset ? 0
}:
let
withWarnings = x:
···
modules ? [],
specialArgs ? {},
prefix ? [],
+
extensionOffset ? length modules,
}:
evalModules (evalModulesArgs // {
modules = regularModules ++ modules;
specialArgs = evalModulesArgs.specialArgs or {} // specialArgs;
prefix = extendArgs.prefix or evalModulesArgs.prefix;
+
inherit extensionOffset;
});
type = lib.types.submoduleWith {
-
inherit modules specialArgs;
+
inherit modules specialArgs extensionOffset;
};
result = withWarnings {
+5 -1
lib/tests/modules.sh
···
# moduleType
checkConfigOutput '^"a b"$' config.resultFoo ./declare-variants.nix ./define-variant.nix
-
checkConfigOutput '^"a y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix
+
checkConfigOutput '^"a b y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix
checkConfigOutput '^"a b c"$' config.resultFooFoo ./declare-variants.nix ./define-variant.nix
## emptyValue's
···
# Test that types.optionType leaves types untouched as long as they don't need to be merged
checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survives-type-merge.nix
+
+
# Anonymous submodules don't get nixed by import resolution/deduplication
+
# because of an `extendModules` bug, issue 168767.
+
checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix
cat <<EOF
====== module tests ======
+41
lib/tests/modules/extendModules-168767-imports.nix
···
+
{ lib
+
, extendModules
+
, ...
+
}:
+
with lib;
+
{
+
imports = [
+
+
{
+
options.sub = mkOption {
+
default = { };
+
type = types.submodule (
+
{ config
+
, extendModules
+
, ...
+
}:
+
{
+
options.value = mkOption {
+
type = types.int;
+
};
+
+
options.specialisation = mkOption {
+
default = { };
+
inherit
+
(extendModules {
+
modules = [{
+
specialisation = mkOverride 0 { };
+
}];
+
})
+
type;
+
};
+
}
+
);
+
};
+
}
+
+
{ config.sub.value = 1; }
+
+
+
];
+
}
+8 -2
lib/types.nix
···
{ modules
, specialArgs ? {}
, shorthandOnlyDefinesConfig ? false
+
+
# Internal variable to avoid `_key` collisions regardless
+
# of `extendModules`. Wired through by `evalModules`.
+
# Test case: lib/tests/modules, "168767"
+
, extensionOffset ? 0
}@attrs:
let
inherit (lib.modules) evalModules;
···
allModules = defs: imap1 (n: { value, file }:
if isFunction value
then setFunctionArgs
-
(args: lib.modules.unifyModuleSyntax file "${toString file}-${toString n}" (value args))
+
(args: lib.modules.unifyModuleSyntax file "${toString file}-${toString (n + extensionOffset)}" (value args))
(functionArgs value)
else if isAttrs value
then
-
lib.modules.unifyModuleSyntax file "${toString file}-${toString n}" (shorthandToModule value)
+
lib.modules.unifyModuleSyntax file "${toString file}-${toString (n + extensionOffset)}" (shorthandToModule value)
else value
) defs;
···
(base.extendModules {
modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
prefix = loc;
+
extensionOffset = extensionOffset + length defs;
}).config;
emptyValue = { value = {}; };
getSubOptions = prefix: (base.extendModules