···105105 # when resolving module structure (like in imports). For everything else,
106106 # there's _module.args. If specialArgs.modulesPath is defined it will be
107107 # used as the base path for disabledModules.
108108+ #
109109+ # `specialArgs.class`:
110110+ # A nominal type for modules. When set and non-null, this adds a check to
111111+ # make sure that only compatible modules are imported.
108112 specialArgs ? {}
109113 , # This would be remove in the future, Prefer _module.args option instead.
110114 args ? {}
···256260257261 merged =
258262 let collected = collectModules
263263+ (specialArgs.class or null)
259264 (specialArgs.modulesPath or "")
260265 (regularModules ++ [ internalModule ])
261266 ({ inherit lib options config specialArgs; } // specialArgs);
···349354 };
350355 in result;
351356352352- # collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
357357+ # collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
353358 #
354359 # Collects all modules recursively through `import` statements, filtering out
355360 # all modules in disabledModules.
356356- collectModules = let
361361+ collectModules = class: let
357362358363 # Like unifyModuleSyntax, but also imports paths and calls functions if necessary
359364 loadModule = args: fallbackFile: fallbackKey: m:
···364369 throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
365370 else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
366371372372+ checkModule =
373373+ if class != null
374374+ then
375375+ m:
376376+ if m.class != null -> m.class == class
377377+ then m
378378+ else
379379+ throw "The module ${m._file or m.key} was imported into ${class} instead of ${m.class}."
380380+ else
381381+ m: m;
382382+367383 /*
368384 Collects all modules recursively into the form
369385···397413 };
398414 in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x:
399415 let
400400- module = loadModule args parentFile "${parentKey}:anon-${toString n}" x;
416416+ module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
401417 collectedImports = collectStructuredModules module._file module.key module.imports args;
402418 in {
403419 key = module.key;
···461477 else config;
462478 in
463479 if m ? config || m ? options then
464464- let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in
480480+ let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType" "class"]; in
465481 if badAttrs != {} then
466482 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
467483 else
···471487 imports = m.imports or [];
472488 options = m.options or {};
473489 config = addFreeformType (addMeta (m.config or {}));
490490+ class = m.class or null;
474491 }
475492 else
476493 # shorthand syntax
···481498 imports = m.require or [] ++ m.imports or [];
482499 options = {};
483500 config = addFreeformType (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"]);
501501+ class = m.class or null;
484502 };
485503486504 applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
+5
lib/tests/modules.sh
···360360# because of an `extendModules` bug, issue 168767.
361361checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix
362362363363+# Class checks
364364+checkConfigOutput '^{ }$' config.ok.config ./class-check.nix
365365+checkConfigError 'The module .*/module-class-is-darwin.nix was imported into nixos instead of darwin.' config.fail.config ./class-check.nix
366366+checkConfigError 'The module foo.nix#darwinModules.default was imported into nixos instead of darwin.' config.fail-anon.config ./class-check.nix
367367+363368# doRename works when `warnings` does not exist.
364369checkConfigOutput '^1234$' config.c.d.e ./doRename-basic.nix
365370# doRename adds a warning.