Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error handling (tryEval?) #356

Open
jameshfisher opened this issue Sep 27, 2014 · 12 comments
Open

Error handling (tryEval?) #356

jameshfisher opened this issue Sep 27, 2014 · 12 comments

Comments

@jameshfisher
Copy link

@jameshfisher jameshfisher commented Sep 27, 2014

What documentation is there for error handling in the Nix expression language? I think that evaluation can abort with an error message for many different reasons. I also see a concept of throwing an error message, and this appears to be a separate concept to abort. I also see brief mentions of a builtin called tryEval, but its behavior is not documented anywhere.

Is the error/exception/throw/abort/tryEval semantics defined more precisely anywhere?

@jameshfisher
Copy link
Author

@jameshfisher jameshfisher commented Sep 27, 2014

The distinction between uncatchable errors and catchable errors ("exceptions"?) is not very clear. I would like to know roughly what kinds of thing evaluation can yield; at the moment I have the idea that evaluation of an expression either yields:

  • a value, e.g. 2 + 3 evaluates to 5, or
  • an uncatchable error, like abort "foo" evaluates to an uncatchable error with message foo
  • an exception, like throw "foo" evaluates to an exception with message foo

The distinction is important for tryEval, which appears to only catch some forms of error:

> nix-instantiate --eval --expr 'builtins.tryEval (abort "foo")'
error: evaluation aborted with the following error message: `foo'
> nix-instantiate --eval --expr 'builtins.tryEval (throw "foo")'
{ success = false; value = false; }
@jameshfisher
Copy link
Author

@jameshfisher jameshfisher commented Sep 27, 2014

Is it the case that, in the set that builtins.tryEval evaluates to, if the success key is false, then the value key is always false too?

@jameshfisher
Copy link
Author

@jameshfisher jameshfisher commented Sep 27, 2014

At the moment, I want to give builtins.tryEval the following rough semantics:

  • if Expr evaluates to error Error(Msg), the expression builtins.tryEval Expr evaluates to error Error(Msg)
  • if Expr evaluates to exception Exception(Msg), the expression builtins.tryEval Expr evaluates to value { success = false; value = false; }
  • if Expr evaluates to value Val, the expression builtins.tryEval Expr evaluates to value { success = true; value = Val; }

Does this sound accurate?

@jameshfisher
Copy link
Author

@jameshfisher jameshfisher commented Sep 27, 2014

The documentation for assert says:

assert e1; e2

where e1 is an expression that should evaluate to a Boolean value. If it evaluates to true, e2 is returned; otherwise expression evaluation is aborted and a backtrace is printed.

IMO this implies that failed assertions generate an uncatchable error in my above terminology, as with the abort expression. But experimentation shows that assertions actually generate a catchable exception:

> nix-instantiate --eval --expr 'builtins.tryEval (assert false; "foo")'
{ success = false; value = false; }
@jameshfisher
Copy link
Author

@jameshfisher jameshfisher commented Sep 27, 2014

So I looked through the source; my C++ is very rusty but based on the Error class hierarchy and this definition for tryEval:

/* Try evaluating the argument. Success => {success=true; value=something;},
 * else => {success=false; value=false;} */
static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
    state.mkAttrs(v, 2);
    try {
        state.forceValue(*args[0]);
        v.attrs->push_back(Attr(state.sValue, args[0]));
        mkBool(*state.allocAttr(v, state.symbols.create("success")), true);
    } catch (AssertionError & e) {
        mkBool(*state.allocAttr(v, state.sValue), false);
        mkBool(*state.allocAttr(v, state.symbols.create("success")), false);
    }
    v.attrs->sort();
}

I generated this class hierarchy diagram, where the pink error kinds are the catchable ones:

errors

In other words, the only errors that can be caught with tryEval are those that are generated by assert and throw.

I would suggest that the documentation be changed to call these exceptions: we can throw exceptions and catch exceptions, and exceptions are also thrown by assertion failures. Everything else is a fatal error.

@shlevy
Copy link
Member

@shlevy shlevy commented Sep 27, 2014

Your interpretation of the tryEval code is correct. These are the errors that can be generated by end-user code (Abort can also, but having the result of abort be catchable seems dubious). In particular, I believe they are used in evaluating the nixpkgs jobset per-system: various packages have assert/throws in order to limit the package to only being built on the systems they support, and the evaluation simply skips those using in-language constructs rather than having to handle errors in C++.

👍 for documentation.

@jameshfisher
Copy link
Author

@jameshfisher jameshfisher commented Sep 28, 2014

Great, thanks Shea. I mainly ask because I'm writing what's turned out to be a rather epic blog post on the Nix expression language. Hopefully I can get someone to fact-check it when I'm done soon. :-)

On an unrelated note, it would be nice if the thrown message wasn't "thrown away" when caught; i.e. if the semantics were instead:

  • ...
  • if Expr evaluates to exception Exception(Msg), the expression builtins.tryEval Expr evaluates to value { success = false; message = Msg; }
  • ...
@sjmackenzie
Copy link

@sjmackenzie sjmackenzie commented Feb 15, 2016

@jameshfisher great work on this thread, very considerate of you!

@jb55
Copy link

@jb55 jb55 commented Apr 18, 2017

(triage) some good stuff in here, what's the status of this issue? can it be closed?

@deliciouslytyped
Copy link

@deliciouslytyped deliciouslytyped commented May 21, 2019

I'm trying to use tryEval to run tests for some complicated nix expressions I was dealing with. I ran into it not being able to catch a lot of thing things.

@FaustXVI
Copy link

@FaustXVI FaustXVI commented Nov 22, 2019

I am having the same issue as @deliciouslytyped . The behavior described by @jameshfisher would solve it so 👍 😄
Looking at the code it seems to me it could be solved like this (please let me know what I am missing) :

- mkBool(*state.allocAttr(v, state.sValue), false);
+ mkBool(*state.allocAttr(v, state.sValue), e);
@stale
Copy link

@stale stale bot commented Feb 15, 2021

I marked this as stale due to inactivity. → More info

@stale stale bot added the stale label Feb 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants