module system: rework module merging

The asymptotic complexity is now much lower.

Changed files
+46 -22
lib
+1 -1
lib/attrsets.nix
···
foldAttrs = op: nul: list_of_attrs:
fold (n: a:
fold (name: o:
-
o // (listToAttrs [{inherit name; value = op n.${name} (a.${name} or nul); }])
+
o // { ${name} = op n.${name} (a.${name} or nul); }
) a (attrNames n)
) {} list_of_attrs;
+45 -21
lib/modules.nix
···
(concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
mergeModules' = prefix: options: configs:
-
listToAttrs (map (name: {
+
let
+
/* byName is like foldAttrs, but will look for attributes to merge in the
+
specified attribute name.
+
+
byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"])
+
[
+
{
+
hidden="baz";
+
foo={qux="bar"; gla="flop";};
+
}
+
{
+
hidden="fli";
+
foo={qux="gne"; gli="flip";};
+
}
+
]
+
===>
+
{
+
gla = [ "module.hidden=baz,value=flop" ];
+
gli = [ "module.hidden=fli,value=flip" ];
+
qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ];
+
}
+
*/
+
byName = attr: f: modules: foldl' (acc: module:
+
foldl' (inner: name:
+
inner // { ${name} = (acc.${name} or []) ++ (f module module.${attr}.${name}); }
+
) acc (attrNames module.${attr})
+
) {} modules;
+
# an attrset 'name' => list of submodules that declare ‘name’.
+
declsByName = byName "options"
+
(module: option: [{ inherit (module) file; options = option; }])
+
options;
+
# an attrset 'name' => list of submodules that define ‘name’.
+
defnsByName = byName "config" (module: value:
+
map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
+
) configs;
+
# extract the definitions for each loc
+
defnsByName' = byName "config"
+
(module: value: [{ inherit (module) file; inherit value; }])
+
configs;
+
in
+
(flip mapAttrs declsByName (name: decls:
# We're descending into attribute ‘name’.
-
inherit name;
-
value =
let
loc = prefix ++ [name];
-
# Get all submodules that declare ‘name’.
-
decls = concatMap (m:
-
if m.options ? ${name}
-
then [ { inherit (m) file; options = m.options.${name}; } ]
-
else []
-
) options;
-
# Get all submodules that define ‘name’.
-
defns = concatMap (m:
-
if m.config ? ${name}
-
then map (config: { inherit (m) file; inherit config; })
-
(pushDownProperties m.config.${name})
-
else []
-
) configs;
+
defns = defnsByName.${name} or [];
+
defns' = defnsByName'.${name} or [];
nrOptions = count (m: isOption m.options) decls;
-
# Extract the definitions for this loc
-
defns' = map (m: { inherit (m) file; value = m.config.${name}; })
-
(filter (m: m.config ? ${name}) configs);
in
if nrOptions == length decls then
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
···
in
throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
else
-
mergeModules' loc decls defns;
-
}) (concatMap (m: attrNames m.options) options))
+
mergeModules' loc decls defns
+
))
// { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
/* Merge multiple option declarations into a single declaration. In