about summary refs log tree commit diff
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
parentf8c1aee5dac656dac5bab6de54b06ea2ccf3242a (diff)
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
-rw-r--r--lib/customisation.nix51
1 files changed, 49 insertions, 2 deletions
diff --git a/lib/customisation.nix b/lib/customisation.nix
index 234a528527d3c..cc9a9b1c55d0a 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