distinguish between throws & errors during throw

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
This commit is contained in:
Qyriad 2024-06-24 17:26:21 -06:00
parent ca433a9386
commit 022ce87f8a
8 changed files with 85 additions and 6 deletions

View file

@ -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`.

View file

@ -1807,6 +1807,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
try { try {
fn->fun(*this, vCur.determinePos(noPos), args, vCur); 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) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw; throw;

View file

@ -25,7 +25,7 @@ nix eval -E 'assert 1 + 2 == 3; true'
# Top-level eval errors should be printed to stderr with a traceback. # Top-level eval errors should be printed to stderr with a traceback.
topLevelThrow="$(expectStderr 1 nix eval --expr 'throw "a sample throw message"')" topLevelThrow="$(expectStderr 1 nix eval --expr 'throw "a sample throw message"')"
[[ "$topLevelThrow" =~ "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. # But errors inside something should print an elided version, and exit with 0.
outputOfNestedThrow="$(nix eval --expr '{ throws = throw "a sample throw message"; }')" outputOfNestedThrow="$(nix eval --expr '{ throws = throw "a sample throw message"; }')"

View file

@ -41,7 +41,7 @@ error:
| ^ | ^
5| if n > 0 5| if n > 0
while calling the 'throw' builtin caused by explicit throw
at /pwd/lang/eval-fail-duplicate-traces.nix:7:10: at /pwd/lang/eval-fail-duplicate-traces.nix:7:10:
6| then throwAfter (n - 1) 6| then throwAfter (n - 1)
7| else throw "Uh oh!"; 7| else throw "Uh oh!";

View file

@ -27,7 +27,7 @@ error:
| ^ | ^
6| 6|
while calling the 'throw' builtin caused by explicit throw
at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:5:9: at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:5:9:
4| null 4| null
5| [ (_: throw "Not the final value, but is still forced!") (_: 23) ] 5| [ (_: throw "Not the final value, but is still forced!") (_: 23) ]

View file

@ -54,7 +54,7 @@ error:
(21 duplicate frames omitted) (21 duplicate frames omitted)
while calling the 'throw' builtin caused by explicit throw
at /pwd/lang/eval-fail-mutual-recursion.nix:34:10: at /pwd/lang/eval-fail-mutual-recursion.nix:34:10:
33| then throwAfterB true 10 33| then throwAfterB true 10
34| else throw "Uh oh!"; 34| else throw "Uh oh!";

View file

@ -5,7 +5,7 @@ error:
| ^ | ^
2| 2|
while calling the 'throw' builtin caused by explicit throw
at /pwd/lang/eval-fail-not-throws.nix:1:4: at /pwd/lang/eval-fail-not-throws.nix:1:4:
1| ! (throw "uh oh!") 1| ! (throw "uh oh!")
| ^ | ^

View file

@ -40,7 +40,7 @@ error:
| ^ | ^
8| } 8| }
while calling the 'throw' builtin caused by explicit throw
at /pwd/lang/eval-fail-toJSON.nix:7:13: at /pwd/lang/eval-fail-toJSON.nix:7:13:
6| { 6| {
7| c.d = throw "hah no"; 7| c.d = throw "hah no";