From d00edfb28d0a52d9acd392c582a43f98e773cf4c Mon Sep 17 00:00:00 2001 From: Qyriad Date: Sat, 22 Jun 2024 21:22:29 -0600 Subject: [PATCH] trace when the `foo` part of `foo.bar.baz` errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns errors like: let errpkg = throw "invalid foobar"; in errpkg.meta error: … while calling the 'throw' builtin at «string»:2:12: 1| let 2| errpkg = throw "invalid foobar"; | ^ 3| in errpkg.meta error: invalid foobar into errors like: let errpkg = throw "invalid foobar"; in errpkg.meta error: … while evaluating 'errpkg' to select 'meta' on it at «string»:3:4: 2| errpkg = throw "invalid foobar"; 3| in errpkg.meta | ^ … while calling the 'throw' builtin at «string»:2:12: 1| let 2| errpkg = throw "invalid foobar"; | ^ 3| in errpkg.meta error: invalid foobar For the low price of one try/catch, you too can have the incorrect line of code actually show up in the trace! Change-Id: If8d6200ec1567706669d405c34adcd7e2d2cd29d --- doc/manual/rl-next/better-attrpath-errors.md | 64 +++++++++++++++++++ src/libexpr/eval.cc | 15 ++++- .../lang/eval-fail-recursion.err.exp | 6 ++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 doc/manual/rl-next/better-attrpath-errors.md diff --git a/doc/manual/rl-next/better-attrpath-errors.md b/doc/manual/rl-next/better-attrpath-errors.md new file mode 100644 index 000000000..d06bfc43b --- /dev/null +++ b/doc/manual/rl-next/better-attrpath-errors.md @@ -0,0 +1,64 @@ +--- +synopsis: "Trace when the `foo` part of a `foo.bar.baz` expression errors" +cls: 1505 +credits: Qyriad +category: Improvements +--- + +Previously, if an expression like `linux_4_9.meta.description` errored in the `linux_4_9` part, it wouldn't show you that that's the part of the expression that failed to evaluate, or even that that line of code is what caused evaluation of the failing expression. +The previous error looks like this: + +``` +let + inherit (pkgs.linuxKernel.kernels) linux_4_9; +in linux_4_9.meta.description + +error: + … while evaluating the attribute 'linux_4_9' + at /nix/store/dk2rpyb6ndvfbf19bkb2plcz5y3k8i5v-source/pkgs/top-level/linux-kernels.nix:278:5: + 277| } // lib.optionalAttrs config.allowAliases { + 278| linux_4_9 = throw "linux 4.9 was removed because it will reach its end of life within 22.11"; + | ^ + 279| linux_4_14 = throw "linux 4.14 was removed because it will reach its end of life within 23.11"; + + … while calling the 'throw' builtin + at /nix/store/dk2rpyb6ndvfbf19bkb2plcz5y3k8i5v-source/pkgs/top-level/linux-kernels.nix:278:17: + 277| } // lib.optionalAttrs config.allowAliases { + 278| linux_4_9 = throw "linux 4.9 was removed because it will reach its end of life within 22.11"; + | ^ + 279| linux_4_14 = throw "linux 4.14 was removed because it will reach its end of life within 23.11"; + + error: linux 4.9 was removed because it will reach its end of life within 22.11 +``` + +Now, the error will look like this: + +``` +let + inherit (pkgs.linuxKernel.kernels) linux_4_9; +in linux_4_9.meta.description +error: + … while evaluating 'linux_4_9' to select 'meta.description' on it + at «string»:3:4: + 2| inherit (pkgs.linuxKernel.kernels) linux_4_9; + 3| in linux_4_9.meta.description + | ^ + + … while evaluating the attribute 'linux_4_9' + at /nix/store/dk2rpyb6ndvfbf19bkb2plcz5y3k8i5v-source/pkgs/top-level/linux-kernels.nix:278:5: + 277| } // lib.optionalAttrs config.allowAliases { + 278| linux_4_9 = throw "linux 4.9 was removed because it will reach its end of life within 22.11"; + | ^ + 279| linux_4_14 = throw "linux 4.14 was removed because it will reach its end of life within 23.11"; + + … caused by explicit throw + at /nix/store/dk2rpyb6ndvfbf19bkb2plcz5y3k8i5v-source/pkgs/top-level/linux-kernels.nix:278:17: + 277| } // lib.optionalAttrs config.allowAliases { + 278| linux_4_9 = throw "linux 4.9 was removed because it will reach its end of life within 22.11"; + | ^ + 279| linux_4_14 = throw "linux 4.14 was removed because it will reach its end of life within 23.11"; + + error: linux 4.9 was removed because it will reach its end of life within 22.11 +``` + +Not only does the line of code that referenced the failing binding show up in the trace, it also tells you that it was specifically the `linux_4_9` part that failed. diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index a6a64a43c..b01867a09 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1432,7 +1432,20 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) Value * vCurrent = &vFirst; // Position for the current attrset Value in this select chain. PosIdx posCurrent; - e->eval(state, env, vFirst); + + try { + e->eval(state, env, vFirst); + } catch (Error & e) { + assert(this->e != nullptr); + state.addErrorTrace( + e, + getPos(), + "while evaluating '%s' to select '%s' on it", + ExprPrinter(state, *this->e), + showAttrPath(state.symbols, this->attrPath) + ); + throw; + } try { auto dts = state.debugRepl diff --git a/tests/functional/lang/eval-fail-recursion.err.exp b/tests/functional/lang/eval-fail-recursion.err.exp index 19380dc65..f0057b2d5 100644 --- a/tests/functional/lang/eval-fail-recursion.err.exp +++ b/tests/functional/lang/eval-fail-recursion.err.exp @@ -1,4 +1,10 @@ error: + … while evaluating 'a' to select 'foo' on it + at /pwd/lang/eval-fail-recursion.nix:1:21: + 1| let a = {} // a; in a.foo + | ^ + 2| + … in the right operand of the update (//) operator at /pwd/lang/eval-fail-recursion.nix:1:12: 1| let a = {} // a; in a.foo