···539540 mergeModules' = prefix: options: configs:
541 let
542- /* byName is like foldAttrs, but will look for attributes to merge in the
543- specified attribute name.
544-545- byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"])
546- [
547- {
548- hidden="baz";
549- foo={qux="bar"; gla="flop";};
550- }
551- {
552- hidden="fli";
553- foo={qux="gne"; gli="flip";};
554- }
555- ]
556- ===>
557- {
558- gla = [ "module.hidden=baz,value=flop" ];
559- gli = [ "module.hidden=fli,value=flip" ];
560- qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ];
561- }
562- */
563- byName = attr: f: modules:
564- zipAttrsWith (n: concatLists)
565- (map (module: let subtree = module.${attr}; in
566 if !(builtins.isAttrs subtree) then
567- throw (if attr == "config" then ''
568- You're trying to define a value of type `${builtins.typeOf subtree}'
569- rather than an attribute set for the option
570- `${builtins.concatStringsSep "." prefix}'!
571-572- This usually happens if `${builtins.concatStringsSep "." prefix}' has option
573- definitions inside that are not matched. Please check how to properly define
574- this option by e.g. referring to `man 5 configuration.nix'!
575- '' else ''
576 An option declaration for `${builtins.concatStringsSep "." prefix}' has type
577 `${builtins.typeOf subtree}' rather than an attribute set.
578 Did you mean to define this outside of `options'?
579- '')
580 else
581- mapAttrs (n: f module) subtree
582- ) modules);
583- # an attrset 'name' => list of submodules that declare ‘name’.
584- declsByName = byName "options" (module: option:
585- [{ inherit (module) _file; options = option; }]
586- ) options;
00000000000000000000000587 # an attrset 'name' => list of submodules that define ‘name’.
588- defnsByName = byName "config" (module: value:
589- map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
590- ) configs;
000000000591 # extract the definitions for each loc
592- defnsByName' = byName "config" (module: value:
593- [{ inherit (module) file; inherit value; }]
594- ) configs;
000000000595596 # Convert an option tree decl to a submodule option decl
597 optionTreeToOption = decl:
···613 # We're descending into attribute ‘name’.
614 let
615 loc = prefix ++ [name];
616- defns = defnsByName.${name} or [];
617- defns' = defnsByName'.${name} or [];
618 optionDecls = filter (m: isOption m.options) decls;
619 in
620 if length optionDecls == length decls then
···657 # Propagate all unmatched definitions from nested option sets
658 mapAttrs (n: v: v.unmatchedDefns) resultsByName
659 # Plus the definitions for the current prefix that don't have a matching option
660- // removeAttrs defnsByName' (attrNames matchedOptions);
661 in {
662 inherit matchedOptions;
663
···539540 mergeModules' = prefix: options: configs:
541 let
542+ # an attrset 'name' => list of submodules that declare ‘name’.
543+ declsByName =
544+ zipAttrsWith
545+ (n: concatLists)
546+ (map
547+ (module: let subtree = module.options; in
000000000000000000548 if !(builtins.isAttrs subtree) then
549+ throw ''
00000000550 An option declaration for `${builtins.concatStringsSep "." prefix}' has type
551 `${builtins.typeOf subtree}' rather than an attribute set.
552 Did you mean to define this outside of `options'?
553+ ''
554 else
555+ mapAttrs
556+ (n: option:
557+ [{ inherit (module) _file; options = option; }]
558+ )
559+ subtree
560+ )
561+ options);
562+563+ # The root of any module definition must be an attrset.
564+ checkedConfigs =
565+ assert
566+ lib.all
567+ (c:
568+ # TODO: I have my doubts that this error would occur when option definitions are not matched.
569+ # The implementation of this check used to be tied to a superficially similar check for
570+ # options, so maybe that's why this is here.
571+ isAttrs c.config || throw ''
572+ In module `${c.file}', you're trying to define a value of type `${builtins.typeOf c.config}'
573+ rather than an attribute set for the option
574+ `${builtins.concatStringsSep "." prefix}'!
575+576+ This usually happens if `${builtins.concatStringsSep "." prefix}' has option
577+ definitions inside that are not matched. Please check how to properly define
578+ this option by e.g. referring to `man 5 configuration.nix'!
579+ ''
580+ )
581+ configs;
582+ configs;
583+584 # an attrset 'name' => list of submodules that define ‘name’.
585+ pushedDownDefinitionsByName =
586+ zipAttrsWith
587+ (n: concatLists)
588+ (map
589+ (module:
590+ mapAttrs
591+ (n: value:
592+ map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
593+ )
594+ module.config
595+ )
596+ checkedConfigs);
597 # extract the definitions for each loc
598+ rawDefinitionsByName =
599+ zipAttrsWith
600+ (n: concatLists)
601+ (map
602+ (module:
603+ mapAttrs
604+ (n: value:
605+ [{ inherit (module) file; inherit value; }]
606+ )
607+ module.config
608+ )
609+ checkedConfigs);
610611 # Convert an option tree decl to a submodule option decl
612 optionTreeToOption = decl:
···628 # We're descending into attribute ‘name’.
629 let
630 loc = prefix ++ [name];
631+ defns = pushedDownDefinitionsByName.${name} or [];
632+ defns' = rawDefinitionsByName.${name} or [];
633 optionDecls = filter (m: isOption m.options) decls;
634 in
635 if length optionDecls == length decls then
···672 # Propagate all unmatched definitions from nested option sets
673 mapAttrs (n: v: v.unmatchedDefns) resultsByName
674 # Plus the definitions for the current prefix that don't have a matching option
675+ // removeAttrs rawDefinitionsByName (attrNames matchedOptions);
676 in {
677 inherit matchedOptions;
678
+1-1
lib/tests/modules.sh
···207## shorthandOnlyDefines config behaves as expected
208checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
209checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
210-checkConfigError "You're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
211checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
212213## submoduleWith should merge all modules in one swoop
···207## shorthandOnlyDefines config behaves as expected
208checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
209checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
210+checkConfigError "In module ..*define-submoduleWith-shorthand.nix., you're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
211checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
212213## submoduleWith should merge all modules in one swoop