From 022ce87f8ab7b579265e42a8af39a72982e8714b Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 24 Jun 2024 17:26:21 -0600 Subject: [PATCH] distinguish between throws & errors during throw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns errors like this: let throwMsg = a: throw (a + " invalid bar"); in throwMsg "bullshit" error: … from call site at «string»:3:4: 2| throwMsg = a: throw (a + " invalid bar"); 3| in throwMsg "bullshit" | ^ … while calling 'throwMsg' at «string»:2:14: 1| let 2| throwMsg = a: throw (a + " invalid bar"); | ^ 3| in throwMsg "bullshit" … while calling the 'throw' builtin at «string»:2:17: 1| let 2| throwMsg = a: throw (a + " invalid bar"); | ^ 3| in throwMsg "bullshit" error: bullshit invalid bar into errors like this: let throwMsg = a: throw (a + " invalid bar"); in throwMsg "bullshit" error: … from call site at «string»:3:4: 2| throwMsg = a: throw (a + " invalid bar"); 3| in throwMsg "bullshit" | ^ … while calling 'throwMsg' at «string»:2:14: 1| let 2| throwMsg = a: throw (a + " invalid bar"); | ^ 3| in throwMsg "bullshit" … caused by explicit throw at «string»:2:17: 1| let 2| throwMsg = a: throw (a + " invalid bar"); | ^ 3| in throwMsg "bullshit" error: bullshit invalid bar Change-Id: I593688928ece20f97999d1bf03b2b46d9ac338cb --- .../rl-next/distinguish-throw-errors.md | 70 +++++++++++++++++++ src/libexpr/eval.cc | 9 +++ tests/functional/eval.sh | 2 +- .../lang/eval-fail-duplicate-traces.err.exp | 2 +- ...-foldlStrict-strict-op-application.err.exp | 2 +- .../lang/eval-fail-mutual-recursion.err.exp | 2 +- .../lang/eval-fail-not-throws.err.exp | 2 +- .../functional/lang/eval-fail-toJSON.err.exp | 2 +- 8 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 doc/manual/rl-next/distinguish-throw-errors.md diff --git a/doc/manual/rl-next/distinguish-throw-errors.md b/doc/manual/rl-next/distinguish-throw-errors.md new file mode 100644 index 000000000..243e33d61 --- /dev/null +++ b/doc/manual/rl-next/distinguish-throw-errors.md @@ -0,0 +1,70 @@ +--- +synopsis: "Distinguish between explicit throws and errors that happened while evaluating a throw" +cls: 1511 +credits: Qyriad +category: Improvements +--- + +Previously, errors caused by an expression like `throw "invalid argument"` were treated like an error that happened simply while some builtin function was being called: + +``` +let + throwMsg = p: throw "${p} isn't the right package"; +in throwMsg "linuz" + +error: + … while calling the 'throw' builtin + at «string»:2:17: + 1| let + 2| throwMsg = p: throw "${p} isn't the right package"; + | ^ + 3| in throwMsg "linuz" + + error: linuz isn't the right package +``` + +But the error didn't just happen "while" calling the `throw` builtin — it's a throw error! +Now it looks like this: + +``` +let + throwMsg = p: throw "${p} isn't the right package"; +in throwMsg "linuz" + +error: + … caused by explicit throw + at «string»:2:17: + 1| let + 2| throwMsg = p: throw "${p} isn't the right package"; + | ^ + 3| in throwMsg "linuz" + + error: linuz isn't the right package +``` + +This also means that incorrect usage of `throw` or errors evaluating its arguments are easily distinguishable from explicit throws: + +``` +let + throwMsg = p: throw "${p} isn't the right package"; +in throwMsg { attrs = "error when coerced in string interpolation"; } + +error: + … while calling the 'throw' builtin + at «string»:2:17: + 1| let + 2| throwMsg = p: throw "${p} isn't the right package"; + | ^ + 3| in throwMsg { attrs = "error when coerced in string interpolation"; } + + … while evaluating a path segment + at «string»:2:24: + 1| let + 2| throwMsg = p: throw "${p} isn't the right package"; + | ^ + 3| in throwMsg { attrs = "error when coerced in string interpolation"; } + + error: cannot coerce a set to a string: { attrs = "error when coerced in string interpolation"; } +``` + +Here, instead of an actual thrown error, a type error happens first (trying to coerce an attribute set to a string), but that type error happened *while* calling `throw`. diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 56581cc19..d9bdb0d2c 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1807,6 +1807,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & try { fn->fun(*this, vCur.determinePos(noPos), args, vCur); + } catch (ThrownError & e) { + // Distinguish between an error that simply happened while "throw" + // was being evaluated and an explicit thrown error. + if (fn->name == "throw") { + addErrorTrace(e, pos, "caused by explicit %s", "throw"); + } else { + addErrorTrace(e, pos, "while calling the '%s' builtin", fn->name); + } + throw; } catch (Error & e) { addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); throw; diff --git a/tests/functional/eval.sh b/tests/functional/eval.sh index 9c125b569..ae6fcec63 100644 --- a/tests/functional/eval.sh +++ b/tests/functional/eval.sh @@ -25,7 +25,7 @@ nix eval -E 'assert 1 + 2 == 3; true' # Top-level eval errors should be printed to stderr with a traceback. topLevelThrow="$(expectStderr 1 nix eval --expr 'throw "a sample throw message"')" [[ "$topLevelThrow" =~ "a sample throw message" ]] -[[ "$topLevelThrow" =~ "while calling the 'throw' builtin" ]] +[[ "$topLevelThrow" =~ "caused by explicit throw" ]] # But errors inside something should print an elided version, and exit with 0. outputOfNestedThrow="$(nix eval --expr '{ throws = throw "a sample throw message"; }')" diff --git a/tests/functional/lang/eval-fail-duplicate-traces.err.exp b/tests/functional/lang/eval-fail-duplicate-traces.err.exp index cedaebd3b..d9e2ec945 100644 --- a/tests/functional/lang/eval-fail-duplicate-traces.err.exp +++ b/tests/functional/lang/eval-fail-duplicate-traces.err.exp @@ -41,7 +41,7 @@ error: | ^ 5| if n > 0 - … while calling the 'throw' builtin + … caused by explicit throw at /pwd/lang/eval-fail-duplicate-traces.nix:7:10: 6| then throwAfter (n - 1) 7| else throw "Uh oh!"; diff --git a/tests/functional/lang/eval-fail-foldlStrict-strict-op-application.err.exp b/tests/functional/lang/eval-fail-foldlStrict-strict-op-application.err.exp index 4903bc82d..6955fad13 100644 --- a/tests/functional/lang/eval-fail-foldlStrict-strict-op-application.err.exp +++ b/tests/functional/lang/eval-fail-foldlStrict-strict-op-application.err.exp @@ -27,7 +27,7 @@ error: | ^ 6| - … while calling the 'throw' builtin + … caused by explicit throw at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:5:9: 4| null 5| [ (_: throw "Not the final value, but is still forced!") (_: 23) ] diff --git a/tests/functional/lang/eval-fail-mutual-recursion.err.exp b/tests/functional/lang/eval-fail-mutual-recursion.err.exp index c034afcd5..c03d2e840 100644 --- a/tests/functional/lang/eval-fail-mutual-recursion.err.exp +++ b/tests/functional/lang/eval-fail-mutual-recursion.err.exp @@ -54,7 +54,7 @@ error: (21 duplicate frames omitted) - … while calling the 'throw' builtin + … caused by explicit throw at /pwd/lang/eval-fail-mutual-recursion.nix:34:10: 33| then throwAfterB true 10 34| else throw "Uh oh!"; diff --git a/tests/functional/lang/eval-fail-not-throws.err.exp b/tests/functional/lang/eval-fail-not-throws.err.exp index fc81f7277..5882a260a 100644 --- a/tests/functional/lang/eval-fail-not-throws.err.exp +++ b/tests/functional/lang/eval-fail-not-throws.err.exp @@ -5,7 +5,7 @@ error: | ^ 2| - … while calling the 'throw' builtin + … caused by explicit throw at /pwd/lang/eval-fail-not-throws.nix:1:4: 1| ! (throw "uh oh!") | ^ diff --git a/tests/functional/lang/eval-fail-toJSON.err.exp b/tests/functional/lang/eval-fail-toJSON.err.exp index ad267711b..18c334923 100644 --- a/tests/functional/lang/eval-fail-toJSON.err.exp +++ b/tests/functional/lang/eval-fail-toJSON.err.exp @@ -40,7 +40,7 @@ error: | ^ 8| } - … while calling the 'throw' builtin + … caused by explicit throw at /pwd/lang/eval-fail-toJSON.nix:7:13: 6| { 7| c.d = throw "hah no";