lol

lib.modules: Let module declare options directly in bare submodule

... where a bare submodule is an option that has a type like
`submoduleWith x`, as opposed to `attrsOf (submoduleWith x)`.

This makes migration unnecessary when introducing a freeform type
in an existing option tree.

Closes #146882

+75 -1
+21 -1
lib/modules.nix
··· 474 474 [{ inherit (module) file; inherit value; }] 475 475 ) configs; 476 476 477 + # Convert an option tree decl to a submodule option decl 478 + optionTreeToOption = decl: 479 + if isOption decl.options 480 + then decl 481 + else decl // { 482 + options = mkOption { 483 + type = types.submoduleWith { 484 + modules = [ { options = decl.options; } ]; 485 + }; 486 + }; 487 + }; 488 + 477 489 resultsByName = mapAttrs (name: decls: 478 490 # We're descending into attribute ‘name’. 479 491 let ··· 493 505 firstOption = findFirst (m: isOption m.options) "" decls; 494 506 firstNonOption = findFirst (m: !isOption m.options) "" decls; 495 507 in 496 - throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'." 508 + if firstOption.options.type?isSubmodule && firstOption.options.type.isSubmodule 509 + then 510 + let opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls)); 511 + in { 512 + matchedOptions = evalOptionValue loc opt defns'; 513 + unmatchedDefns = []; 514 + } 515 + else 516 + throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'." 497 517 else 498 518 mergeModules' loc decls defns) declsByName; 499 519
+5
lib/tests/modules.sh
··· 62 62 checkConfigOutput '^false$' config.enable ./declare-enable.nix 63 63 checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix 64 64 65 + checkConfigOutput '^1$' config.bare-submodule.nested ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix 66 + checkConfigOutput '^2$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix 67 + checkConfigOutput '^42$' config.bare-submodule.nested ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix ./declare-bare-submodule-deep-option.nix ./define-bare-submodule-values.nix 68 + checkConfigOutput '^420$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix ./declare-bare-submodule-deep-option.nix ./define-bare-submodule-values.nix 69 + 65 70 # Check integer types. 66 71 # unsigned 67 72 checkConfigOutput '^42$' config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
+10
lib/tests/modules/declare-bare-submodule-deep-option.nix
··· 1 + { lib, ... }: 2 + let 3 + inherit (lib) mkOption types; 4 + in 5 + { 6 + options.bare-submodule.deep = mkOption { 7 + type = types.int; 8 + default = 2; 9 + }; 10 + }
+18
lib/tests/modules/declare-bare-submodule-nested-option.nix
··· 1 + { lib, ... }: 2 + let 3 + inherit (lib) mkOption types; 4 + in 5 + { 6 + options.bare-submodule = mkOption { 7 + type = types.submoduleWith { 8 + modules = [ 9 + { 10 + options.nested = mkOption { 11 + type = types.int; 12 + default = 1; 13 + }; 14 + } 15 + ]; 16 + }; 17 + }; 18 + }
+12
lib/tests/modules/declare-bare-submodule.nix
··· 1 + { lib, ... }: 2 + let 3 + inherit (lib) mkOption types; 4 + in 5 + { 6 + options.bare-submodule = mkOption { 7 + type = types.submoduleWith { 8 + modules = [ ]; 9 + }; 10 + default = {}; 11 + }; 12 + }
+4
lib/tests/modules/define-bare-submodule-values.nix
··· 1 + { 2 + bare-submodule.nested = 42; 3 + bare-submodule.deep = 420; 4 + }
+5
lib/types.nix
··· 642 642 else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values"; 643 643 }; 644 644 }; 645 + } // { 646 + # Submodule-typed options get special treatment in order to facilitate 647 + # certain migrations, such as the addition of freeformTypes onto 648 + # existing option trees. 649 + isSubmodule = true; 645 650 }; 646 651 647 652 # A value from a set of allowed ones.