Skip to content

message(<cond>) and warning(<cond>) circumvent calling handlers and signal the original class, e.g. an error

5 messages · Henrik Bengtsson, Andreas Kersting, iuke-tier@ey m@iii@g oii uiow@@edu

#
Hi, in help("message", package = "base"), we can read:

Description: 'message' is used for generating 'simple' diagnostic
messages which are neither warnings nor errors, but nevertheless
represented as conditions.
are neither warning nor errors.

However, the following signals a condition of class 'error':
boom!

This can be seen if we do:
<simpleError: boom!

This stems from message(e) using signalCondition(e) internally.

Another problem with this behavior is that message(e) cannot be suppressed:
boom!

or captured with calling handlers, e.g.
boom!
NULL

If we replace e <- simpleError("boom") with e <-
simpleWarning("careful"), we see a similar behavior.  These problems
exist also with warning(e).  The current behaviors prevent functions
from capturing and relaying message(<error>), message(<warning>), and
warning(<error>).

I'm happy to post a bug report to <https://bugs.r-project.org/>.

/Henrik

PS. BTW, it looks like some recent "..." tweaks to the warning() and
stop() code could be applied also to message().
#
Hi,

There is the same issue with stop():
<simpleWarning: careful>

I very recently stumbled upon this, when a warning was re-raised as an error, which was then not caught by an outer try():
+   tryCatch(warning("careful"), warning = function(w) stop(w)),
+   silent = TRUE
+ )
Error in doTryCatch(return(expr), name, parentenv, handler) : careful

I would also like to see this behavior changed. I think that stop() should always signal an error, warning() a warning and message() a message.

Best,
Andreas

2022-03-01 19:38 GMT+01:00 "Henrik Bengtsson" <henrik.bengtsson at gmail.com>:
#
This is behaving as documented and as intended. If you want to
call stop() with a condition argument and you want to have that
condition handled as an error then you need to make sure that your
condition inherits from "error". One way to do this would be to define
something like

warningToError <- function(w)
    errorCondition(conditionMessage(w),
                   warning = w,
 		  class = "warningToError")

and use stop(warningToError(w)).

If you call stop() with a condition argument then that is the
condition stop() will signal, regardless of its class. I can't at the
moment think of a good reason why I would want to call stop() with a
warning condition argument, and I suspect most cases where that
happens would be mistakes. So checking in stop() that a condition
argument inherits from "error" and signaling a warning, or maybe an
error, if it does not might be worth considering (with analogous
changes for warning() and message()).

The condition system separates the signaling protocol from the process
of determining handlers. Signaling itself is done by
signalCondition().  message() and warning() signal a condition with a
muffle restart available, and return if the condition is not handled.
stop() is guaranteed not to return; if the condition is not handled,
then it invokes the default error handler, which will not return. None
of these currently look at the class of the condition.
signalCondition() looks at the condition's class to find out what
handlers are available. It will invoke error handlers for error
conditions and warning handlers for warning conditions.  It does not
know or care about whether it was called from stop(), warning(),
message(), or some other way.

The most common high-level usage of stop(), warning(), or message() is
to call them with a string and possibly some additional arguments used
to create a message. In these cases a condition object of class
"error" for stop(), "warning" for warning(), and "message" for message
is created implicitly and signaled.

Calling these functions with a condition argument is using lower level
functionality, which gives more power but also means users need to
understand what they are doing. In particular, users who want to call
stop() with a condition argument _and_ want handlers for error
conditions to be used need to make sure that the class of the
condition they signal inherits from "error".

Best,

luke
On Tue, 1 Mar 2022, Andreas Kersting wrote:

            

  
    
#
Thank you, Luke.  I discovered this problem last year, where a user
reported that their use of message(<error>) in futures would not work
the same way as without futures. The issue is that the future
framework captures the error condition and relays it, rather than
outputting the message string, which happens if you don't capture the
error condition. Today there was another similar report from another
package using futures. They both had in common that they use

res <- tryCatch({
  some_fcn(x)
}, error = function(e) {
  message(e)
  NA
})

to return a missing value on errors, while outputting the error
message string to inform the user on the error.  I've been informing
them to instead use

  message(conditionMessage(e))

in this case. Your reply confirms this, and I can now confidently say
that using message(e) is incorrect here.

I think the help pages on message, warning, and stop could be more
explicit on this behavior.

My preference would be that it is an error if calling message(cond)
with !inherits(cond, "message"), calling warning(cond) with
!inherits(cond, "warning"), and stop(cond) with !inherits(cond,
"error").  But, maybe there are valid arguments for allowing such use
cases.

Thanks,

Henrik
On Tue, Mar 1, 2022 at 3:12 PM <luke-tierney at uiowa.edu> wrote:
1 day later
#
I'll look into what effect adding stopifnot(inherits(cond, "error"))
and similar to the others has on CRAN/BIOC packages. Probably won't
get there for a while though.

Best,

luke
On Wed, 2 Mar 2022, Henrik Bengtsson wrote: