Page MenuHomePhabricator

Make ErrorList class default constructible and add simple push_back() method
Needs ReviewPublic

Authored by sgraenitz on Jan 3 2020, 12:30 PM.

Details

Summary

ErrorList appears to have some unused potential. It could be a bit easier to use I think. This change proposes the following idiomatic usage. (My actual use-case is still in progress.) Thoughts?

Error idiomaticUse() {
  ErrorList Errs;
  Errs.push_back(...);
  return make_error<ErrorList>(std::move(Errs));
}

Event Timeline

sgraenitz created this revision.Jan 3 2020, 12:30 PM
Herald added a project: Restricted Project. · View Herald TranscriptJan 3 2020, 12:30 PM

This seems like a nice change for ErrorList.

What are you using it for though? I actually keep thinking I would like to remove ErrorList, as it has some awkward properties: it doesn't interact sensibly with isA tests (isA should really be changed to a pair of utilities: containsA, and containsOnly), and it means that Expected values have a weird asymmetry in the success and failure case: They contain exactly one success value, or (potentially) many failure values. This makes it tricky, if not impossible, to safely write a utility function that maps failures to recovery values:

int attemptWithRecovery(Expected<int> Val) {
  if (Val)
    return *Val;
  int Fallback = 0;
  handleAllErrors(Val,
               [&](StringError &SE) { Fallback = 1; },      // What should Fallback's value be if Val contains
               [&](ErrorInfoBase &EIB) { Fallback = 0; });  // both a StringError and another Error?
  return Fallback;
}

In this case the answer is "the fallback value will be based on the last error in the list", but that means you've ignored the recovery mappings for all the other errors, which probably isn't what you wanted.

In my experience, whenever I have used ErrorList it was really because I wanted to be able to report multiple errors, never because I wanted to be able to recover from them. However I think this kind of reporting is better handled by a logging idiom (like ExecutionSession::reportError).

Actually, since I'm pontificating about Error on this thread anyway, the other big change that I would like to see (which is also impacted by ErrorList) would be to ditch handleErrors entirely. If we had the "one Error/Expected == one failure" invariant, we could switch to an "err_cast" idiom:

Instead of:

Error foo();

handleErrors(foo(),
             [](ErrType1 &E1) {
               ...
             },
             [](ErrType2 &E2) {
               ...
             },
             ...);

We could have:

auto Err = foo();
if (auto E1 = err_cast<ErrType1>(Err)) {
  ..
} else if (auto E2 = err_cast<ErrType2>(Err)) {
  ...
}

There are pros and cons to each scheme:
handleErrors -- Pros: Failure value names (E1, E2) scoped to handler only. Cons: Not intuitive -- you need to read the docs to figure out how to use it. Can't be used in C API.
err_cast -- Pros: Intuitive, and C API implementation would be a straightforward mapping. Cons: Failure value names leak into else-if scopes.

On balance I think err_cast is a better answer. But we need to remove (or at least demote) ErrorList before we could switch.

This seems like a nice change for ErrorList.

What are you using it for though?
[...]
In my experience, whenever I have used ErrorList it was really because I wanted to be able to report multiple errors, never because I wanted to be able to recover from them. However I think this kind of reporting is better handled by a logging idiom (like ExecutionSession::reportError).

Yes, basically I'd use it for reporting errors and indeed it's better done like logging. At first, I like how ErrorList packed more errors in the Payload to make a list, that seemed very elegant to me. But you are right, if it adds unnecessary complexity to the primary usage, it probably not the right way to go.

I would like to see (which is also impacted by ErrorList) would be to ditch handleErrors entirely. If we had the "one Error/Expected == one failure" invariant, we could switch to an "err_cast" idiom

That seems like a good idea. Not sure if I ever saw a handleErrors with more than one handler in production code. I agree that if we have to drop ErrorList for it, we should do so and not make it more convenient to use.