9

I need to convert a warning to an error to be able to handle it further upstream (warnings are swallowed somewhere in the middle, over which I have no control; errors are not). To do this, I’m doing the following:

warning_to_error = function (expr)
    withCallingHandlers(expr, warning = stop)

This works great:

> warning_to_error(warning('foobar'))
Error in withCallingHandlers(expr, warning = stop) : foobar

Unfortunately, this makes the error completely uncatchable:

> try(warning_to_error(warning('foobar')))
Error in withCallingHandlers(expr, warning = stop) : foobar

In my real situation, there are several layers between my warning_to_error and the try (including the logic that muffles warnings). How can I make the error raised by my calling handler catchable? Unfortunately I cannot use restarts as described in another Stack Overflow question because stop doesn’t define a restart, and I once again have no control over the code doing the catching anyway.

Community
  • 1
  • 1
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214

1 Answers1

7

This should tell you what is happening with your definition of warning_to_error:

> tryCatch(warning_to_error(warning('foobar')), condition = print)
<simpleWarning in withCallingHandlers(expr, warning = stop): foobar>```

As the documentation for stop says, when you call stop with a condition, that condition is signalled to look for handlers, which means warning handlers in this case. If you want an error handler to be invoked you need to signal an error condition. That is what happens when you set options(warn = 2) for example. So you need something like

warning_to_error1 <- function (expr)
    withCallingHandlers(expr,
                        warning = function(w)
                            stop("(converted from warning) ",
                                 conditionMessage(w)))

That gives you

> tryCatch(warning_to_error1(warning('foobar')),
+          error = function(e) print("Got it"))
[1] "Got it"

Ideally we should provide a condition class and constructor for warnings converted to errors and use that internally for warn = 2

Luke Tierney
  • 1,025
  • 5
  • 5
  • Thanks, this makes perfect sense. I actually attempted to do just that (= printing/returning the condition to inspect it) but my attempts failed because it simply didn’t occur to me that the class would be anything other than `error`. Duh. – Konrad Rudolph Feb 28 '17 at 15:48
  • It would be even greater if I could somehow spoof the call stack information. Unfortunately I’m unable to achieve this even when using `eval`, executed in the `parent.frame()` of `warning_to_error`: the stack trace invariably ends in the `eval` itself. I’m guessing this isn’t possible? – Konrad Rudolph Feb 28 '17 at 16:06
  • 1
    Create an error condition object and store the result of `sys.calls` in it before passing it to `stop`. – Luke Tierney Feb 28 '17 at 16:49
  • Awesome. That only fixes the display and not the actual stack trace but I can live with that (and it’s arguably the desired result anyway). – Konrad Rudolph Feb 28 '17 at 17:00