Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# vctrs (development version)

* `obj_check_vector()` now throws a clearer error message. In particular, special info bullets have been added that link out to FAQ pages and explain common issues around incompatible S3 lists and data frames (#2061).

* R >=4.0.0 is now required. This is still more permissive than the general tidyverse policy of supporting the [5 most recent versions of R](https://www.tidyverse.org/blog/2019/04/r-version-support/).

* `vec_interleave()` gains new `.size` and `.error_call` arguments.
Expand Down
96 changes: 94 additions & 2 deletions R/conditions.R
Original file line number Diff line number Diff line change
Expand Up @@ -678,15 +678,107 @@ stop_scalar_type <- function(x, arg = NULL, call = caller_env()) {
} else {
arg <- glue::backtick(arg)
}
msg <- glue::glue("{arg} must be a vector, not {obj_type_friendly(x)}.")

message <- glue::glue("{arg} must be a vector, not {obj_type_friendly(x)}.")

# Use the first detected issue, with a fallthrough to point to our scalar FAQ
message <-
with_incompatible_s3_list_bullets(message, x) %||%
with_incompatible_data_frame_bullets(message, x) %||%
with_scalar_faq_bullet(message)

stop_vctrs(
msg,
message,
"vctrs_error_scalar_type",
actual = x,
call = call
)
}

with_incompatible_s3_list_bullets <- function(message, x) {
is_list_typeof <- typeof(x) == "list"

classes <- class(x)
doesnt_contain_explicit_list_class <- !any(classes == "list")
doesnt_contain_data_frame_class <- !any(classes == "data.frame")

# We also assume no `vec_proxy()` method exists, otherwise one would have
# been invoked, avoiding the error
is_incompatible_s3_list <-
is_list_typeof &&
doesnt_contain_explicit_list_class &&
doesnt_contain_data_frame_class

if (!is_incompatible_s3_list) {
return(NULL)
}

c(
message,
x = cli::format_inline(paste(
"Detected incompatible scalar S3 list.",
"To be treated as a vector, the object must explicitly inherit from {.cls list}",
"or should implement a {.fn vec_proxy} method.",
"Class: {.cls {classes}}."
)),
i = "If this object comes from a package, please report this error to the package author.",
i = cli::format_inline(paste(
"Read our FAQ about",
"{.topic [creating vector types](howto_faq_fix_scalar_type_error)}",
"to learn more."
))
)
}

with_incompatible_data_frame_bullets <- function(message, x) {
classes <- class(x)
n_classes <- length(classes)

contains_data_frame_class <- any(classes == "data.frame")

if (n_classes == 0L) {
# Edge case of `NULL` or `character()` classes
last_class_is_not_data_frame <- TRUE
} else {
last_class_is_not_data_frame <- classes[n_classes] != "data.frame"
}

is_incompatible_data_frame <-
contains_data_frame_class && last_class_is_not_data_frame

if (!is_incompatible_data_frame) {
return(NULL)
}

subclasses <- setdiff(classes, "data.frame")

c(
message,
x = cli::format_inline(paste(
"Detected incompatible data frame structure.",
"A data frame is normally treated as a vector, but an incompatible class ordering was detected.",
"To be compatible, the subclass {.cls {subclasses}} must come before {.cls data.frame}, not after.",
"Class: {.cls {classes}}."
)),
i = "If this object comes from a package, please report this error to the package author.",
i = cli::format_inline(paste(
"Read our FAQ about",
"{.topic [creating vector types](howto_faq_fix_scalar_type_error)}",
"to learn more."
))
)
}

with_scalar_faq_bullet <- function(message) {
c(
message,
i = cli::format_inline(paste(
"Read our FAQ about {.topic [scalar types](faq_error_scalar_type)}",
"to learn more."
))
)
}

stop_corrupt_factor_levels <- function(x, arg = "x", call = caller_env()) {
msg <- glue::glue("`{arg}` is a corrupt factor with non-character levels")
abort(msg, call = call)
Expand Down
1 change: 1 addition & 0 deletions R/faq-developer.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ NULL
#' @includeRmd man/faq/developer/howto-faq-fix-scalar-type-error.Rmd description
#'
#' @name howto-faq-fix-scalar-type-error
#' @aliases howto_faq_fix_scalar_type_error
Comment on lines 44 to +45
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created underscored aliases for these for use in the cli bullet. In cli's fallback case it shows ?faq_error_scalar_type which doesn't work well with - if you try and copy and paste it in an R console. We've also learned this in the past with ?rlang::args_dots_empty

NULL
1 change: 1 addition & 0 deletions R/faq.R
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ NULL
#' @includeRmd man/faq/user/faq-error-scalar-type.Rmd description
#'
#' @name faq-error-scalar-type
#' @aliases faq_error_scalar_type
NULL
5 changes: 5 additions & 0 deletions man/faq-error-scalar-type.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions man/howto-faq-fix-scalar-type-error.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions tests/testthat/_snaps/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Condition
Error:
! `quote(foo)` must be a vector, not a symbol.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

---

Expand All @@ -13,6 +14,9 @@
Condition
Error:
! `foobar()` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# obj_check_vector() error respects `arg` and `call`

Expand All @@ -21,6 +25,40 @@
Condition
Error in `my_check_vector()`:
! `foo` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# obj_check_vector() error contains FAQ links and correct bullets

Code
obj_check_vector(x)
Condition
Error:
! `x` must be a vector, not an expression vector.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

---

Code
obj_check_vector(x)
Condition
Error:
! `x` must be a vector, not a <my_list> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <my_list>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

---

Code
obj_check_vector(x)
Condition
Error:
! `x` must be a vector, not a <data.frame> object.
x Detected incompatible data frame structure. A data frame is normally treated as a vector, but an incompatible class ordering was detected. To be compatible, the subclass <my_df> must come before <data.frame>, not after. Class: <data.frame/my_df>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# assertion failures are explained

Expand Down Expand Up @@ -235,6 +273,7 @@
Condition
Error:
! `quote(foo)` must be a vector, not a symbol.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

---

Expand All @@ -243,6 +282,9 @@
Condition
Error:
! `foobar()` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# vec_check_size() error respects `arg` and `call`

Expand All @@ -259,6 +301,9 @@
Condition
Error in `my_check_size()`:
! `foo` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# vec_check_size() validates `size`

Expand Down Expand Up @@ -307,6 +352,7 @@
Condition
Error:
! `quote(foo)` must be a vector, not a symbol.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

---

Expand All @@ -315,6 +361,9 @@
Condition
Error:
! `foobar()` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# vec_check_recyclable() error respects `arg` and `call`

Expand All @@ -331,6 +380,9 @@
Condition
Error in `my_check_recyclable()`:
! `foo` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.

# vec_check_recyclable() validates `size`

Expand Down Expand Up @@ -398,18 +450,21 @@
<error/vctrs_error_scalar_type>
Error in `my_function()`:
! `my_arg[[2]]` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.
Code
(expect_error(my_function(list(1, name = env()))))
Output
<error/vctrs_error_scalar_type>
Error in `my_function()`:
! `my_arg$name` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.
Code
(expect_error(my_function(list(1, foo = env()))))
Output
<error/vctrs_error_scalar_type>
Error in `my_function()`:
! `my_arg$foo` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

# list_check_all_size() works

Expand Down Expand Up @@ -465,13 +520,15 @@
<error/vctrs_error_scalar_type>
Error in `list_all_size()`:
! `x[[1]]` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.
Code
my_function <- (function(my_arg, size) list_check_all_size(my_arg, size))
(expect_error(my_function(x, 2)))
Output
<error/vctrs_error_scalar_type>
Error in `my_function()`:
! `my_arg[[1]]` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

# list_all_recyclable() and list_check_all_recyclable() error on scalars

Expand All @@ -481,6 +538,7 @@
<error/vctrs_error_scalar_type>
Error in `list_all_recyclable()`:
! `x[[1]]` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.
Code
my_function <- (function(my_arg, size) {
list_check_all_recyclable(my_arg, size)
Expand All @@ -490,6 +548,7 @@
<error/vctrs_error_scalar_type>
Error in `my_function()`:
! `my_arg[[1]]` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

# list_all_size() and list_check_all_size() throw error using internal call on non-list input

Expand Down Expand Up @@ -567,6 +626,7 @@
Condition
Error:
! `x[[2]]` must be a vector, not `NULL`.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

---

Expand All @@ -575,6 +635,7 @@
Condition
Error:
! `x[[2]]` must be a vector, not `NULL`.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

---

Expand All @@ -583,6 +644,7 @@
Condition
Error:
! `x[[3]]` must be a vector, not an environment.
i Read our FAQ about scalar types (`?faq_error_scalar_type`) to learn more.

# list_check_all_recyclable() works with `allow_null`

Expand Down
6 changes: 6 additions & 0 deletions tests/testthat/_snaps/bind.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,18 @@
<error/vctrs_error_scalar_type>
Error in `vec_cbind()`:
! `..1` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.
Code
(expect_error(vec_cbind(foobar(list()), .error_call = call("foo"))))
Output
<error/vctrs_error_scalar_type>
Error in `foo()`:
! `..1` must be a vector, not a <vctrs_foobar> object.
x Detected incompatible scalar S3 list. To be treated as a vector, the object must explicitly inherit from <list> or should implement a `vec_proxy()` method. Class: <vctrs_foobar>.
i If this object comes from a package, please report this error to the package author.
i Read our FAQ about creating vector types (`?howto_faq_fix_scalar_type_error`) to learn more.
Code
(expect_error(vec_cbind(a = 1:2, b = int())))
Output
Expand Down
Loading