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-select-err.err.exp b/tests/functional/lang/eval-fail-select-err.err.exp new file mode 100644 index 000000000..70ee50737 --- /dev/null +++ b/tests/functional/lang/eval-fail-select-err.err.exp @@ -0,0 +1,23 @@ +error: + … while evaluating the attribute 'src.meta' + at /pwd/lang/eval-fail-select-err.nix:2:3: + 1| let + 2| somepkg.src = throw "invalid foo bar"; + | ^ + 3| in somepkg.src.meta + + … while evaluating 'somepkg.src' to select 'meta' on it + at /pwd/lang/eval-fail-select-err.nix:3:4: + 2| somepkg.src = throw "invalid foo bar"; + 3| in somepkg.src.meta + | ^ + 4| + + … caused by explicit throw + at /pwd/lang/eval-fail-select-err.nix:2:17: + 1| let + 2| somepkg.src = throw "invalid foo bar"; + | ^ + 3| in somepkg.src.meta + + error: invalid foo bar diff --git a/tests/functional/lang/eval-fail-select-err.nix b/tests/functional/lang/eval-fail-select-err.nix new file mode 100644 index 000000000..a7add0077 --- /dev/null +++ b/tests/functional/lang/eval-fail-select-err.nix @@ -0,0 +1,3 @@ +let + somepkg.src = throw "invalid foo bar"; +in somepkg.src.meta 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";