summaryrefslogtreecommitdiff
path: root/lib/customisation.nix
diff options
context:
space:
mode:
authorSilvan Mosberger <contact@infinisil.com>2020-07-25 19:54:37 +0200
committerProfpatsch <mail@profpatsch.de>2022-04-01 22:03:05 +0200
commit1c00bf394867b07ed7a908408d8bc1d0afd9fa49 (patch)
treed7917805b5fdc34b7db02095d30792f26e9f122d /lib/customisation.nix
parentlib/tests: Add tests for levenshtein functions (diff)
downloadnixpkgs-1c00bf394867b07ed7a908408d8bc1d0afd9fa49.tar.gz
lib/customization: Improve callPackage error message for missing args
This uses the levenshtein distance to look through all possible arguments to find ones that are close to what was requested: error: Function in /home/infinisil/src/nixpkgs/pkgs/tools/text/ripgrep/default.nix called without required argument "fetchFromGithub", did you mean "fetchFromGitHub" or "fetchFromGitLab"? With https://github.com/NixOS/nix/pull/3468 (in current nixUnstable) the error message becomes even better, adding line location info
Diffstat (limited to 'lib/customisation.nix')
-rw-r--r--lib/customisation.nix51
1 files changed, 49 insertions, 2 deletions
diff --git a/lib/customisation.nix b/lib/customisation.nix
index 234a528527d3..cc9a9b1c55d0 100644
--- a/lib/customisation.nix
+++ b/lib/customisation.nix
@@ -117,8 +117,55 @@ rec {
callPackageWith = autoArgs: fn: args:
let
f = if lib.isFunction fn then fn else import fn;
- auto = builtins.intersectAttrs (lib.functionArgs f) autoArgs;
- in makeOverridable f (auto // args);
+ fargs = lib.functionArgs f;
+
+ # All arguments that will be passed to the function
+ # This includes automatic ones and ones passed explicitly
+ allArgs = builtins.intersectAttrs fargs autoArgs // args;
+
+ # A list of argument names that the function requires, but
+ # wouldn't be passed to it
+ missingArgs = lib.attrNames
+ # Filter out arguments that have a default value
+ (lib.filterAttrs (name: value: ! value)
+ # Filter out arguments that would be passed
+ (removeAttrs fargs (lib.attrNames allArgs)));
+
+ # Get a list of suggested argument names for a given missing one
+ getSuggestions = arg: lib.pipe (autoArgs // args) [
+ lib.attrNames
+ # Only use ones that are at most 2 edits away. While mork would work,
+ # levenshteinAtMost is only fast for 2 or less.
+ (lib.filter (lib.strings.levenshteinAtMost 2 arg))
+ # Put strings with shorter distance first
+ (lib.sort (x: y: lib.strings.levenshtein x arg < lib.strings.levenshtein y arg))
+ # Only take the first couple results
+ (lib.take 3)
+ # Quote all entries
+ (map (x: "\"" + x + "\""))
+ ];
+
+ prettySuggestions = suggestions:
+ if suggestions == [] then ""
+ else if lib.length suggestions == 1 then ", did you mean ${lib.elemAt suggestions 0}?"
+ else ", did you mean ${lib.concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?";
+
+ errorForArg = arg:
+ let
+ loc = builtins.unsafeGetAttrPos arg fargs;
+ # loc' can be removed once lib/minver.nix is >2.3.4, since that includes
+ # https://github.com/NixOS/nix/pull/3468 which makes loc be non-null
+ loc' = if loc != null then loc.file + ":" + toString loc.line
+ else if ! lib.isFunction fn then
+ toString fn + lib.optionalString (lib.sources.pathIsDirectory fn) "/default.nix"
+ else "<unknown location>";
+ in "Function called without required argument \"${arg}\" at "
+ + "${loc'}${prettySuggestions (getSuggestions arg)}";
+
+ # Only show the error for the first missing argument
+ error = errorForArg (lib.head missingArgs);
+
+ in if missingArgs == [] then makeOverridable f allArgs else throw error;
/* Like callPackage, but for a function that returns an attribute