Skip to content

Commit 528b0ce

Browse files
committed
Better error messages for bad geom, stat, and position
Fixes #2401
1 parent 3c9c504 commit 528b0ce

File tree

5 files changed

+57
-35
lines changed

5 files changed

+57
-35
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ up correct aspect ratio, and draws a graticule.
191191

192192
* In most cases, using `%>%` instead of `+` should generate an informative
193193
error (#2400).
194+
195+
* `layer()` gives considerably better error messages for incorrectly specified
196+
`geom`, `stat`, or `position` (#2401).
194197

195198
* In all layers that use it, `linemitre` now defaults to 10 (instead of 1)
196199
to better match base R.

R/fortify.r

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fortify.function <- function(model, data, ...) model
2323
fortify.default <- function(model, data, ...) {
2424
msg <- paste0(
2525
"`data` must be a data frame, or other object coercible by `fortify()`, ",
26-
"not an object of class ", paste(class(model), collapse = "/")
26+
"not ", obj_desc(x)
2727
)
2828
if (inherits(model, "uneval")) {
2929
msg <- paste0(

R/geom-defaults.r

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,17 @@
1212
#' update_geom_defaults("point", list(colour = "black"))
1313
#' @rdname update_defaults
1414
update_geom_defaults <- function(geom, new) {
15-
if (is.character(geom)) {
16-
g <- find_subclass("Geom", geom, parent.frame())
17-
} else if (inherits(geom, "Geom")) {
18-
g <- geom
19-
} else {
20-
stop('`geom` must be a string (like "point") or a Geom object (like GeomPoint).',
21-
call. = FALSE)
22-
}
23-
15+
g <- check_subclass(geom, "Geom", env = parent.frame())
2416
old <- g$default_aes
2517
g$default_aes <- defaults(rename_aes(new), old)
18+
invisible()
2619
}
2720

2821
#' @rdname update_defaults
2922
#' @export
3023
update_stat_defaults <- function(stat, new) {
31-
if (is.character(stat)) {
32-
g <- find_subclass("Stat", stat, parent.frame())
33-
} else if (inherits(stat, "Stat")) {
34-
g <- stat
35-
} else {
36-
stop('`stat` must be a string (like "point") or a Stat object (like StatBin).',
37-
call. = FALSE)
38-
}
39-
24+
g <- check_subclass(stat, "Stat", env = parent.frame())
4025
old <- g$default_aes
4126
g$default_aes <- defaults(rename_aes(new), old)
27+
invisible()
4228
}

R/layer.r

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,9 @@ layer <- function(geom = NULL, stat = NULL,
8989
mapping <- validate_mapping(mapping)
9090
}
9191

92-
if (is.character(geom))
93-
geom <- find_subclass("Geom", geom, parent.frame())
94-
if (is.character(stat))
95-
stat <- find_subclass("Stat", stat, parent.frame())
96-
if (is.character(position))
97-
position <- find_subclass("Position", position, parent.frame())
92+
geom <- check_subclass(geom, "Geom", env = parent.frame())
93+
stat <- check_subclass(stat, "Stat", env = parent.frame())
94+
position <- check_subclass(position, "Position", env = parent.frame())
9895

9996
# Special case for na.rm parameter needed by all layers
10097
if (is.null(params$na.rm)) {
@@ -344,15 +341,51 @@ Layer <- ggproto("Layer", NULL,
344341
is.layer <- function(x) inherits(x, "Layer")
345342

346343

347-
find_subclass <- function(super, class, env) {
348-
name <- paste0(super, camelize(class, first = TRUE))
349-
obj <- find_global(name, env = env)
350344

351-
if (is.null(obj)) {
352-
stop("No ", tolower(super), " called '", class, "'.", call. = FALSE)
353-
} else if (!inherits(obj, super)) {
354-
stop("Found object is not a ", tolower(super), ".", call. = FALSE)
345+
check_subclass <- function(x, subclass,
346+
argname = tolower(subclass),
347+
env = parent.frame()) {
348+
if (inherits(x, subclass)) {
349+
x
350+
} else if (is.character(x) && length(x) == 1) {
351+
name <- paste0(subclass, camelize(x, first = TRUE))
352+
obj <- find_global(name, env = env)
353+
354+
if (is.null(obj) || !inherits(obj, subclass)) {
355+
stop("Can't find `", argname, "` called \"", x, "\"", call. = FALSE)
356+
} else {
357+
obj
358+
}
359+
} else {
360+
stop(
361+
"`", argname, "` must be either a string or a ", subclass, " object, ",
362+
"not ", obj_desc(x),
363+
call. = FALSE
364+
)
355365
}
366+
}
356367

357-
obj
368+
obj_desc <- function(x) {
369+
if (isS4(x)) {
370+
paste0("an S4 object with class ", class(x)[[1]])
371+
} else if (is.object(x)) {
372+
if (is.data.frame(x)) {
373+
"a data frame"
374+
} else if (is.factor(x)) {
375+
"a factor"
376+
} else {
377+
paste0("an S3 object with class ", paste(class(x), collapse = "/"))
378+
}
379+
} else {
380+
switch(typeof(x),
381+
"NULL" = "a NULL",
382+
character = "a character vector",
383+
integer = "an integer vector",
384+
logical = "a logical vector",
385+
double = "a numeric vector",
386+
list = "a list",
387+
closure = "a function",
388+
paste0("a base object of type", typeof(x))
389+
)
390+
}
358391
}

R/utilities-help.r

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11

22
rd_aesthetics <- function(type, name) {
33
obj <- switch(type,
4-
geom = find_subclass("Geom", name, globalenv()),
5-
stat = find_subclass("Stat", name, globalenv())
4+
geom = check_subclass(name, "Geom", env = globalenv()),
5+
stat = check_subclass(name, "Stat", env = globalenv())
66
)
77
aes <- rd_aesthetics_item(obj)
88

0 commit comments

Comments
 (0)