Merge pull request #98155 from Infinisil/improved-module-errors
Improved module errors
This commit is contained in:
commit
ecc00bd8f6
4 changed files with 55 additions and 33 deletions
|
@ -117,7 +117,7 @@ rec {
|
||||||
if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
|
if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
|
||||||
let
|
let
|
||||||
firstDef = head merged.unmatchedDefns;
|
firstDef = head merged.unmatchedDefns;
|
||||||
baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' defined in `${firstDef.file}' does not exist.";
|
baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}";
|
||||||
in
|
in
|
||||||
if attrNames options == [ "_module" ]
|
if attrNames options == [ "_module" ]
|
||||||
then throw ''
|
then throw ''
|
||||||
|
@ -449,7 +449,13 @@ rec {
|
||||||
# Handle properties, check types, and merge everything together.
|
# Handle properties, check types, and merge everything together.
|
||||||
res =
|
res =
|
||||||
if opt.readOnly or false && length defs' > 1 then
|
if opt.readOnly or false && length defs' > 1 then
|
||||||
throw "The option `${showOption loc}' is read-only, but it's set multiple times."
|
let
|
||||||
|
# For a better error message, evaluate all readOnly definitions as
|
||||||
|
# if they were the only definition.
|
||||||
|
separateDefs = map (def: def // {
|
||||||
|
value = (mergeDefinitions loc opt.type [ def ]).mergedValue;
|
||||||
|
}) defs';
|
||||||
|
in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
|
||||||
else
|
else
|
||||||
mergeDefinitions loc opt.type defs';
|
mergeDefinitions loc opt.type defs';
|
||||||
|
|
||||||
|
@ -497,8 +503,8 @@ rec {
|
||||||
mergedValue =
|
mergedValue =
|
||||||
if isDefined then
|
if isDefined then
|
||||||
if all (def: type.check def.value) defsFinal then type.merge loc defsFinal
|
if all (def: type.check def.value) defsFinal then type.merge loc defsFinal
|
||||||
else let firstInvalid = findFirst (def: ! type.check def.value) null defsFinal;
|
else let allInvalid = filter (def: ! type.check def.value) defsFinal;
|
||||||
in throw "The option value `${showOption loc}' in `${firstInvalid.file}' is not of type `${type.description}'."
|
in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
|
||||||
else
|
else
|
||||||
# (nixos-option detects this specific error message and gives it special
|
# (nixos-option detects this specific error message and gives it special
|
||||||
# handling. If changed here, please change it there too.)
|
# handling. If changed here, please change it there too.)
|
||||||
|
|
|
@ -96,12 +96,12 @@ rec {
|
||||||
else if all isBool list then foldl' lib.or false list
|
else if all isBool list then foldl' lib.or false list
|
||||||
else if all isString list then lib.concatStrings list
|
else if all isString list then lib.concatStrings list
|
||||||
else if all isInt list && all (x: x == head list) list then head list
|
else if all isInt list && all (x: x == head list) list then head list
|
||||||
else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
|
else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
|
||||||
|
|
||||||
mergeOneOption = loc: defs:
|
mergeOneOption = loc: defs:
|
||||||
if defs == [] then abort "This case should never happen."
|
if defs == [] then abort "This case should never happen."
|
||||||
else if length defs != 1 then
|
else if length defs != 1 then
|
||||||
throw "The unique option `${showOption loc}' is defined multiple times, in:\n - ${concatStringsSep "\n - " (getFiles defs)}."
|
throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}"
|
||||||
else (head defs).value;
|
else (head defs).value;
|
||||||
|
|
||||||
/* "Merge" option definitions by checking that they all have the same value. */
|
/* "Merge" option definitions by checking that they all have the same value. */
|
||||||
|
@ -111,11 +111,11 @@ rec {
|
||||||
# This also makes it work for functions, because the foldl' below would try
|
# This also makes it work for functions, because the foldl' below would try
|
||||||
# to compare the first element with itself, which is false for functions
|
# to compare the first element with itself, which is false for functions
|
||||||
else if length defs == 1 then (elemAt defs 0).value
|
else if length defs == 1 then (elemAt defs 0).value
|
||||||
else foldl' (val: def:
|
else (foldl' (first: def:
|
||||||
if def.value != val then
|
if def.value != first.value then
|
||||||
throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
|
throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}"
|
||||||
else
|
else
|
||||||
val) (head defs).value defs;
|
first) (head defs) defs).value;
|
||||||
|
|
||||||
/* Extracts values of all "value" keys of the given list.
|
/* Extracts values of all "value" keys of the given list.
|
||||||
|
|
||||||
|
@ -213,6 +213,24 @@ rec {
|
||||||
else escaped;
|
else escaped;
|
||||||
in (concatStringsSep ".") (map escapeOptionPart parts);
|
in (concatStringsSep ".") (map escapeOptionPart parts);
|
||||||
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
|
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
|
||||||
|
|
||||||
|
showDefs = defs: concatMapStrings (def:
|
||||||
|
let
|
||||||
|
# Pretty print the value for display, if successful
|
||||||
|
prettyEval = builtins.tryEval (lib.generators.toPretty {} def.value);
|
||||||
|
# Split it into its lines
|
||||||
|
lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value);
|
||||||
|
# Only display the first 5 lines, and indent them for better visibility
|
||||||
|
value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
|
||||||
|
result =
|
||||||
|
# Don't print any value if evaluating the value strictly fails
|
||||||
|
if ! prettyEval.success then ""
|
||||||
|
# Put it on a new line if it consists of multiple
|
||||||
|
else if length lines > 1 then ":\n " + value
|
||||||
|
else ": " + value;
|
||||||
|
in "\n- In `${def.file}'${result}"
|
||||||
|
) defs;
|
||||||
|
|
||||||
unknownModule = "<unknown-file>";
|
unknownModule = "<unknown-file>";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ checkConfigError() {
|
||||||
reportFailure "$@"
|
reportFailure "$@"
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
if echo "$err" | grep --silent "$errorContains" ; then
|
if echo "$err" | grep -zP --silent "$errorContains" ; then
|
||||||
pass=$((pass + 1))
|
pass=$((pass + 1))
|
||||||
return 0;
|
return 0;
|
||||||
else
|
else
|
||||||
|
@ -62,17 +62,17 @@ checkConfigError() {
|
||||||
|
|
||||||
# Check boolean option.
|
# Check boolean option.
|
||||||
checkConfigOutput "false" config.enable ./declare-enable.nix
|
checkConfigOutput "false" config.enable ./declare-enable.nix
|
||||||
checkConfigError 'The option .* defined in .* does not exist.' config.enable ./define-enable.nix
|
checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./define-enable.nix
|
||||||
|
|
||||||
# Check integer types.
|
# Check integer types.
|
||||||
# unsigned
|
# unsigned
|
||||||
checkConfigOutput "42" config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
|
checkConfigOutput "42" config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
|
||||||
checkConfigError 'The option value .* in .* is not of type.*unsigned integer.*' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
|
checkConfigError 'A definition for option .* is not of type.*unsigned integer.*. Definition values:\n- In .*: -23' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
|
||||||
# positive
|
# positive
|
||||||
checkConfigError 'The option value .* in .* is not of type.*positive integer.*' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
|
checkConfigError 'A definition for option .* is not of type.*positive integer.*. Definition values:\n- In .*: 0' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
|
||||||
# between
|
# between
|
||||||
checkConfigOutput "42" config.value ./declare-int-between-value.nix ./define-value-int-positive.nix
|
checkConfigOutput "42" config.value ./declare-int-between-value.nix ./define-value-int-positive.nix
|
||||||
checkConfigError 'The option value .* in .* is not of type.*between.*-21 and 43.*inclusive.*' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
|
checkConfigError 'A definition for option .* is not of type.*between.*-21 and 43.*inclusive.*. Definition values:\n- In .*: -23' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
|
||||||
|
|
||||||
# Check either types
|
# Check either types
|
||||||
# types.either
|
# types.either
|
||||||
|
@ -125,7 +125,7 @@ checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-foo-enable
|
||||||
set -- config.enable ./define-enable.nix ./declare-enable.nix
|
set -- config.enable ./define-enable.nix ./declare-enable.nix
|
||||||
checkConfigOutput "true" "$@"
|
checkConfigOutput "true" "$@"
|
||||||
checkConfigOutput "false" "$@" ./disable-define-enable.nix
|
checkConfigOutput "false" "$@" ./disable-define-enable.nix
|
||||||
checkConfigError "The option .*enable.* defined in .* does not exist" "$@" ./disable-declare-enable.nix
|
checkConfigError "The option .*enable.* does not exist. Definition values:\n- In .*: true" "$@" ./disable-declare-enable.nix
|
||||||
checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix
|
checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix
|
||||||
checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix
|
checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix
|
||||||
|
|
||||||
|
@ -142,17 +142,17 @@ checkConfigError 'infinite recursion encountered' "$@"
|
||||||
|
|
||||||
# Check _module.check.
|
# Check _module.check.
|
||||||
set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix
|
set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix
|
||||||
checkConfigError 'The option .* defined in .* does not exist.' "$@"
|
checkConfigError 'The option .* does not exist. Definition values:\n- In .*' "$@"
|
||||||
checkConfigOutput "true" "$@" ./define-module-check.nix
|
checkConfigOutput "true" "$@" ./define-module-check.nix
|
||||||
|
|
||||||
# Check coerced value.
|
# Check coerced value.
|
||||||
checkConfigOutput "\"42\"" config.value ./declare-coerced-value.nix
|
checkConfigOutput "\"42\"" config.value ./declare-coerced-value.nix
|
||||||
checkConfigOutput "\"24\"" config.value ./declare-coerced-value.nix ./define-value-string.nix
|
checkConfigOutput "\"24\"" config.value ./declare-coerced-value.nix ./define-value-string.nix
|
||||||
checkConfigError 'The option value .* in .* is not.*string or signed integer convertible to it' config.value ./declare-coerced-value.nix ./define-value-list.nix
|
checkConfigError 'A definition for option .* is not.*string or signed integer convertible to it.*. Definition values:\n- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix
|
||||||
|
|
||||||
# Check coerced value with unsound coercion
|
# Check coerced value with unsound coercion
|
||||||
checkConfigOutput "12" config.value ./declare-coerced-value-unsound.nix
|
checkConfigOutput "12" config.value ./declare-coerced-value-unsound.nix
|
||||||
checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
|
checkConfigError 'A definition for option .* is not of type .*. Definition values:\n- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
|
||||||
checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
|
checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
|
||||||
|
|
||||||
# Check mkAliasOptionModule.
|
# Check mkAliasOptionModule.
|
||||||
|
@ -183,7 +183,7 @@ checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.ni
|
||||||
checkConfigOutput "true" config.enable ./disable-recursive/main.nix
|
checkConfigOutput "true" config.enable ./disable-recursive/main.nix
|
||||||
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo.nix}
|
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo.nix}
|
||||||
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix}
|
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix}
|
||||||
checkConfigError 'The option .* defined in .* does not exist' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
|
checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
|
||||||
|
|
||||||
# Check that imports can depend on derivations
|
# Check that imports can depend on derivations
|
||||||
checkConfigOutput "true" config.enable ./import-from-store.nix
|
checkConfigOutput "true" config.enable ./import-from-store.nix
|
||||||
|
@ -207,7 +207,7 @@ checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-c
|
||||||
|
|
||||||
|
|
||||||
# Even with multiple assignments, a type error should be thrown if any of them aren't valid
|
# Even with multiple assignments, a type error should be thrown if any of them aren't valid
|
||||||
checkConfigError 'The option value .* in .* is not of type .*' \
|
checkConfigError 'A definition for option .* is not of type .*' \
|
||||||
config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
|
config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
|
||||||
|
|
||||||
## Freeform modules
|
## Freeform modules
|
||||||
|
@ -216,7 +216,7 @@ checkConfigOutput 24 config.value ./freeform-attrsOf.nix ./define-value-string.n
|
||||||
# No freeform assigments shouldn't make it error
|
# No freeform assigments shouldn't make it error
|
||||||
checkConfigOutput '{ }' config ./freeform-attrsOf.nix
|
checkConfigOutput '{ }' config ./freeform-attrsOf.nix
|
||||||
# but only if the type matches
|
# but only if the type matches
|
||||||
checkConfigError 'The option value .* in .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix
|
checkConfigError 'A definition for option .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix
|
||||||
# and properties should be applied
|
# and properties should be applied
|
||||||
checkConfigOutput yes config.value ./freeform-attrsOf.nix ./define-value-string-properties.nix
|
checkConfigOutput yes config.value ./freeform-attrsOf.nix ./define-value-string-properties.nix
|
||||||
# Options should still be declarable, and be able to have a type that doesn't match the freeform type
|
# Options should still be declarable, and be able to have a type that doesn't match the freeform type
|
||||||
|
@ -251,7 +251,7 @@ checkConfigOutput / config.value.path ./types-anything/equal-atoms.nix
|
||||||
checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix
|
checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix
|
||||||
checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix
|
checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix
|
||||||
# Functions can't be merged together
|
# Functions can't be merged together
|
||||||
checkConfigError "The option .* has conflicting definitions" config.value.multiple-lambdas ./types-anything/functions.nix
|
checkConfigError "The option .* has conflicting definition values" config.value.multiple-lambdas ./types-anything/functions.nix
|
||||||
checkConfigOutput '<LAMBDA>' config.value.single-lambda ./types-anything/functions.nix
|
checkConfigOutput '<LAMBDA>' config.value.single-lambda ./types-anything/functions.nix
|
||||||
# Check that all mk* modifiers are applied
|
# Check that all mk* modifiers are applied
|
||||||
checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix
|
checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix
|
||||||
|
|
|
@ -299,16 +299,14 @@ rec {
|
||||||
check = isList;
|
check = isList;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
|
map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
|
||||||
if isList def.value then
|
imap1 (m: def':
|
||||||
imap1 (m: def':
|
(mergeDefinitions
|
||||||
(mergeDefinitions
|
(loc ++ ["[definition ${toString n}-entry ${toString m}]"])
|
||||||
(loc ++ ["[definition ${toString n}-entry ${toString m}]"])
|
elemType
|
||||||
elemType
|
[{ inherit (def) file; value = def'; }]
|
||||||
[{ inherit (def) file; value = def'; }]
|
).optionalValue
|
||||||
).optionalValue
|
) def.value
|
||||||
) def.value
|
) defs)));
|
||||||
else
|
|
||||||
throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
|
|
||||||
emptyValue = { value = {}; };
|
emptyValue = { value = {}; };
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
|
|
Loading…
Reference in a new issue