Search code examples
rwarnings

Show the name of the parent function in the warning message


I have a helper function for displaying warnings:

mywarn <- function() {
  warning("I warn you")
}

If I call the function directly, I get the name of the function where the error occurred:

mywarn()
> Warning message:
  In mywarn() : I warn you

However I want to use the warning function from within several other functions:

myfun <- function(x) {
  x <- x^2
  mywarn()
  x
}

But then it still shows the name of my warning function when warning is displayed:

myfun(10)
> Warning message:
  In mywarn() : I warn you

What is the best way to make the warning message display myfun instead of mywarn ?


Solution

  • In comments you pointed out that other solutions to your question fail on cases like

    sapply(10, myfun) 
    

    where they display something like

    Warning message:
    In FUN(X[[i]], ...) : I warn you
    

    I think this is nearly unavoidable without debug information. In the sapply() call, myfun is an object; sapply doesn't care about its name. So to put "myfun" into the message instead of the local variable in the sapply implementation, your mywarn would need to recognize that it was being called from sapply, and look further up the call stack to find the name of the function being called. And there are lots of other functions besides sapply that it would have to handle, so this isn't a feasible approach.

    If you happen to know that this is going to be a problem, you can work around it by making myfun do more work. Instead of calling mywarn with simply the error message, it could pass its own name as part of the message. This could be as simple as

    mywarn("myfun")
    

    or more elaborate like

    mywarn(call = substitute(myfun(x),list(x = substitute(x))))
    

    (where some of the ugliness could probably be moved into mywarn, but the presence of myfun(x) is essential).

    You can do a lot more if you have enabled debug information in your source. This is the default if you're using source(), but it's optional for package source code.

    For example, if you put the code below in a file named test.R and call source("test.R"), you'll get the output as shown:

    myfun <- function(x) {
      x <- x^2
      mywarn()
      x
    }
    
    mywarn <- function (msg = "I warn you", call = sys.call(sys.parent()))
    {   
      calls <- sys.calls()
      for (i in rev(seq_along(calls))) {
        location <- getSrcref(calls[[i]])
        if (!is.null(location)) {
          call <- NULL
          filename <- getSrcFilename(location)
          linenum  <- getSrcLocation(location)
          functionname <- findLineNum(attr(location, "srcfile"), linenum)
          if (length(functionname) < 1) prefix <- "At "
          else prefix <- paste0("In ", functionname[[1]]$name, "() at ")
          msg <- sprintf("%s%s#%d: %s", prefix, basename(filename), linenum, msg)
        }
      }
      warning(simpleWarning(msg, call))
    }
    
    sapply(10, myfun)
    
    Warning message:
    At test.R#25: In myfun() at test.R#3: I warn you  
    

    which indicates that your source file line 25 called something that eventually got to myfun() in your source file line 3 and issued the warning. You can make the prefix more or less informative according to your taste; I'd probably only include the closest location (In myfun() at test.R#3:) for example.

    If you are working in a situation where the debug info isn't present, it will fall back to @KonradRudolph's solution.