@@ -358,25 +358,41 @@ rec {
358358 } ;
359359
360360 # A submodule (like typed attribute set). See NixOS manual.
361- submodule = opts :
361+ submodule = modules : submoduleWith {
362+ shorthandOnlyDefinesConfig = true ;
363+ modules = toList modules ;
364+ } ;
365+
366+ submoduleWith =
367+ { modules
368+ , specialArgs ? { }
369+ , shorthandOnlyDefinesConfig ? false
370+ } @attrs :
362371 let
363- opts' = toList opts ;
364372 inherit ( lib . modules ) evalModules ;
373+
374+ coerce = unify : value : if isFunction value
375+ then setFunctionArgs ( args : unify ( value args ) ) ( functionArgs value )
376+ else unify ( if shorthandOnlyDefinesConfig then { config = value ; } else value ) ;
377+
378+ allModules = defs : modules ++ imap1 ( n : { value , file } :
379+ # Annotate the value with the location of its definition for better error messages
380+ coerce ( lib . modules . unifyModuleSyntax file "${ toString file } -${ toString n } " ) value
381+ ) defs ;
382+
365383 in
366384 mkOptionType rec {
367385 name = "submodule" ;
368386 check = x : isAttrs x || isFunction x ;
369387 merge = loc : defs :
370- let
371- coerce = def : if isFunction def then def else { config = def ; } ;
372- modules = opts' ++ map ( def : { _file = def . file ; imports = [ ( coerce def . value ) ] ; } ) defs ;
373- in ( evalModules {
374- inherit modules ;
388+ ( evalModules {
389+ modules = allModules defs ;
390+ inherit specialArgs ;
375391 args . name = last loc ;
376392 prefix = loc ;
377393 } ) . config ;
378394 getSubOptions = prefix : ( evalModules
379- { modules = opts' ; inherit prefix ;
395+ { inherit modules prefix specialArgs ;
380396 # This is a work-around due to the fact that some sub-modules,
381397 # such as the one included in an attribute set, expects a "args"
382398 # attribute to be given to the sub-module. As the option
@@ -394,13 +410,29 @@ rec {
394410 # It shouldn't cause an issue since this is cosmetic for the manual.
395411 args . name = "‹name›" ;
396412 } ) . options ;
397- getSubModules = opts' ;
398- substSubModules = m : submodule m ;
399- functor = ( defaultFunctor name ) // {
400- # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
401- # each submodule with its location.
402- payload = [ ] ;
403- binOp = lhs : rhs : [ ] ;
413+ getSubModules = modules ;
414+ substSubModules = m : submoduleWith ( attrs // {
415+ modules = m ;
416+ } ) ;
417+ functor = defaultFunctor name // {
418+ type = types . submoduleWith ;
419+ payload = {
420+ modules = modules ;
421+ specialArgs = specialArgs ;
422+ shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig ;
423+ } ;
424+ binOp = lhs : rhs : {
425+ modules = lhs . modules ++ rhs . modules ;
426+ specialArgs =
427+ let intersecting = builtins . intersectAttrs lhs . specialArgs rhs . specialArgs ;
428+ in if intersecting == { }
429+ then lhs . specialArgs // rhs . specialArgs
430+ else throw "A submoduleWith option is declared multiple times with the same specialArgs \" ${ toString ( attrNames intersecting ) } \" " ;
431+ shorthandOnlyDefinesConfig =
432+ if lhs . shorthandOnlyDefinesConfig == rhs . shorthandOnlyDefinesConfig
433+ then lhs . shorthandOnlyDefinesConfig
434+ else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values" ;
435+ } ;
404436 } ;
405437 } ;
406438
0 commit comments