diff --git a/DESCRIPTION b/DESCRIPTION index be3ef41bf..d8dae8c05 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,7 +31,7 @@ Imports: pillar, prettyunits, purrr (>= 1.0.0), - rlang (>= 0.3.1), + rlang (>= 1.1.0), stats, tibble (>= 2.1.1), tidyr (>= 1.3.0), diff --git a/NAMESPACE b/NAMESPACE index fa9511099..02d4e92be 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -350,6 +350,7 @@ export(varying) export(varying_args) export(xgb_predict) export(xgb_train) +import(rlang) importFrom(dplyr,arrange) importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) @@ -392,26 +393,6 @@ importFrom(purrr,map) importFrom(purrr,map_chr) importFrom(purrr,map_dbl) importFrom(purrr,map_lgl) -importFrom(rlang,abort) -importFrom(rlang,call2) -importFrom(rlang,caller_env) -importFrom(rlang,current_env) -importFrom(rlang,enquo) -importFrom(rlang,enquos) -importFrom(rlang,eval_tidy) -importFrom(rlang,expr) -importFrom(rlang,get_expr) -importFrom(rlang,is_empty) -importFrom(rlang,is_missing) -importFrom(rlang,is_null) -importFrom(rlang,is_quosure) -importFrom(rlang,is_symbolic) -importFrom(rlang,lgl) -importFrom(rlang,missing_arg) -importFrom(rlang,quo_get_expr) -importFrom(rlang,quos) -importFrom(rlang,sym) -importFrom(rlang,syms) importFrom(stats,.checkMFClasses) importFrom(stats,.getXlevels) importFrom(stats,as.formula) diff --git a/R/aaa_models.R b/R/aaa_models.R index 50919dca6..a5bd0a553 100644 --- a/R/aaa_models.R +++ b/R/aaa_models.R @@ -87,7 +87,10 @@ set_in_env <- function(...) { #' @export set_env_val <- function(name, value) { if (length(name) != 1 || !is.character(name)) { - rlang::abort("`name` should be a single character value.") + cli::cli_abort( + "{.arg name} should be a single character value, \\ + not {.obj_type_friendly {name}}." + ) } mod_env <- get_model_env() x <- list(value) @@ -114,20 +117,29 @@ error_set_object <- function(object, func) { } check_eng_val <- function(eng) { - if (rlang::is_missing(eng) || length(eng) != 1 || !is.character(eng)) - rlang::abort("Please supply a character string for an engine name (e.g. `'lm'`)") + if (rlang::is_missing(eng) || length(eng) != 1 || !is.character(eng)) { + cli::cli_abort( + "Please supply a character string for an engine name (e.g. {.val lm}), \\ + not {.obj_type_friendly {eng}}." + ) + } invisible(NULL) } check_model_exists <- function(model) { if (rlang::is_missing(model) || length(model) != 1 || !is.character(model)) { - rlang::abort("Please supply a character string for a model name (e.g. `'linear_reg'`)") + cli::cli_abort( + "Please supply a character string for a model name \\ + (e.g. {.val linear_reg}), not {.obj_type_friendly {model}}." + ) } current <- get_model_env() if (!any(current$models == model)) { - rlang::abort(glue::glue("Model `{model}` has not been registered.")) + cli::cli_abort( + "Model {.val {model}} has not been registered." + ) } invisible(NULL) @@ -135,13 +147,18 @@ check_model_exists <- function(model) { check_model_doesnt_exist <- function(model) { if (rlang::is_missing(model) || length(model) != 1 || !is.character(model)) { - rlang::abort("Please supply a character string for a model name (e.g. `'linear_reg'`)") + cli::cli_abort( + "Please supply a character string for a model name \\ + (e.g. {.val linear_reg}), not {.obj_type_friendly {model}}." + ) } current <- get_model_env() if (any(current$models == model)) { - rlang::abort(glue::glue("Model `{model}` already exists")) + cli::cli_abort( + "Model {.val {model}} already exists." + ) } invisible(NULL) @@ -149,7 +166,10 @@ check_model_doesnt_exist <- function(model) { check_mode_val <- function(mode) { if (rlang::is_missing(mode) || length(mode) != 1 || !is.character(mode)) { - rlang::abort("Please supply a character string for a mode (e.g. `'regression'`).") + cli::cli_abort( + "Please supply a character string for a mode \\ + (e.g. {.val regression}), not {.obj_type_friendly {mode}}." + ) } invisible(NULL) } @@ -157,31 +177,26 @@ check_mode_val <- function(mode) { stop_incompatible_mode <- function(spec_modes, eng = NULL, cls = NULL, call) { if (is.null(eng) & is.null(cls)) { - msg <- "Available modes are: " + msg <- "Available modes are:" } if (!is.null(eng) & is.null(cls)) { - msg <- glue::glue("Available modes for engine {eng} are: ") + msg <- "Available modes for engine {eng} are:" } if (is.null(eng) & !is.null(cls)) { - msg <- glue::glue("Available modes for model type {cls} are: ") + msg <- "Available modes for model type {cls} are:" } if (!is.null(eng) & !is.null(cls)) { - msg <- glue::glue("Available modes for model type {cls} with engine {eng} are: ") + msg <- "Available modes for model type {cls} with engine {eng} are:" } - msg <- glue::glue( - msg, - glue::glue_collapse(glue::glue("'{spec_modes}'"), sep = ", ") - ) - rlang::abort(msg, call = call) + cli::cli_abort(paste(msg, "{.val {spec_modes}}."), call = call) } stop_incompatible_engine <- function(spec_engs, mode, call) { - msg <- glue::glue( - "Available engines for mode {mode} are: ", - glue::glue_collapse(glue::glue("'{spec_engs}'"), sep = ", ") + cli::cli_abort( + "Available engines for mode {.val {mode}} are: {.val {spec_engs}}.", + call = call ) - rlang::abort(msg, call = call) } stop_missing_engine <- function(cls, call) { @@ -193,17 +208,20 @@ stop_missing_engine <- function(cls, call) { "}"), .groups = "drop") if (nrow(info) == 0) { - rlang::abort(paste0("No known engines for `", cls, "()`."), call = call) + cli::cli_abort("No known engines for {.fn {cls}}.", call = call) } - msg <- paste0(info$msg, collapse = ", ") - msg <- paste("Missing engine. Possible mode/engine combinations are:", msg) - rlang::abort(msg, call = call) + cli::cli_abort( + "Missing engine. Possible mode/engine combinations are: {info$msg}.", + call = call + ) } check_mode_for_new_engine <- function(cls, eng, mode) { all_modes <- get_from_env(paste0(cls, "_modes")) if (!(mode %in% all_modes)) { - rlang::abort(paste0("'", mode, "' is not a known mode for model `", cls, "()`.")) + cli::cli_abort( + "{.val {mode}} is not a known mode for model {.fn {cls}}." + ) } invisible(NULL) } @@ -214,8 +232,10 @@ check_spec_mode_engine_val <- function(cls, eng, mode, call = caller_env()) { all_modes <- get_from_env(paste0(cls, "_modes")) if (!(mode %in% all_modes)) { - rlang::abort(paste0("'", mode, "' is not a known mode for model `", cls, "()`."), - call = call) + cli::cli_abort( + "{.val {mode}} is not a known mode for model {.fn {cls}}.", + call = call + ) } model_info <- rlang::env_get(get_model_env(), cls) @@ -250,10 +270,10 @@ check_spec_mode_engine_val <- function(cls, eng, mode, call = caller_env()) { spec_engs <- model_info$engine # engine is allowed to be NULL if (!is.null(eng) && !(eng %in% spec_engs)) { - rlang::abort( - paste0( - "Engine '", eng, "' is not supported for `", cls, "()`. See ", - "`show_engines('", cls, "')`." + cli::cli_abort( + c( + x = "Engine {.val {eng}} is not supported for {.fn {cls}}", + i = "See {.code show_engines({.val {cls}})}." ), call = call ) @@ -298,37 +318,42 @@ check_mode_with_no_engine <- function(cls, mode, call) { check_arg_val <- function(arg) { if (rlang::is_missing(arg) || length(arg) != 1 || !is.character(arg)) - rlang::abort("Please supply a character string for the argument.") + cli::cli_abort( + "Please supply a character string for the argument, \\ + not {.obj_type_friendly {arg}}." + ) invisible(NULL) } -check_submodels_val <- function(has_submodel) { +check_submodels_val <- function(has_submodel, call = caller_env()) { if (!is.logical(has_submodel) || length(has_submodel) != 1) { - rlang::abort("The `submodels` argument should be a single logical.") + cli::cli_abort( + "The {.arg submodels} argument should be a single logical. \\ + not {.obj_type_friendly {has_submodel}}", + call = call + ) } invisible(NULL) } check_func_val <- function(func) { - msg <- - paste( - "`func` should be a named vector with element 'fun' and the optional ", - "elements 'pkg', 'range', 'trans', and 'values'.", - "`func` and 'pkg' should both be single character strings." - ) + msg <- "{.arg func} should be a named vector with element {.field fun} and \\ + the optional elements {.field pkg}, {.field range}, {.field trans}, \\ + and {.field values}. {.field func} and {.field pkg} should both be \\ + single character strings." if (rlang::is_missing(func) || !is.vector(func)) - rlang::abort(msg) + cli::cli_abort(msg) nms <- sort(names(func)) if (all(is.null(nms))) { - rlang::abort(msg) + cli::cli_abort(msg) } if (length(func) == 1) { if (isTRUE(any(nms != "fun"))) { - rlang::abort(msg) + cli::cli_abort(msg) } } else { # check for extra names: @@ -336,15 +361,15 @@ check_func_val <- function(func) { nm_check <- nms %in% c("fun", "pkg", "range", "trans", "values") not_allowed <- nms[!(nms %in% allow_nms)] if (length(not_allowed) > 0) { - rlang::abort(msg) + cli::cli_abort(msg) } } if (!is.character(func[["fun"]])) { - rlang::abort(msg) + cli::cli_abort(msg) } if (any(nms == "pkg") && !is.character(func[["pkg"]])) { - rlang::abort(msg) + cli::cli_abort(msg) } invisible(NULL) @@ -352,7 +377,7 @@ check_func_val <- function(func) { check_fit_info <- function(fit_obj) { if (is.null(fit_obj)) { - rlang::abort("The `fit` module cannot be NULL.") + cli::cli_abort("The {.arg fit_obj} argument cannot be NULL.") } # check required data elements @@ -360,10 +385,9 @@ check_fit_info <- function(fit_obj) { has_req_nms <- exp_nms %in% names(fit_obj) if (!all(has_req_nms)) { - rlang::abort( - glue::glue("The `fit` module should have elements: ", - glue::glue_collapse(glue::glue("`{exp_nms}`"), sep = ", ")) - ) + cli::cli_abort( + "The {.arg value} argument should have elements: {.field {exp_nms}}." + ) } # check optional data elements @@ -371,15 +395,17 @@ check_fit_info <- function(fit_obj) { other_nms <- setdiff(exp_nms, names(fit_obj)) has_opt_nms <- other_nms %in% opt_nms if (any(!has_opt_nms)) { - msg <- glue::glue("The `fit` module can only have optional elements: ", - glue::glue_collapse(glue::glue("`{exp_nms}`"), sep = ", ")) - - rlang::abort(msg) + cli::cli_abort( + "The {.arg value} argument can only have optional elements: \\ + {.field {exp_nms}}." + ) } if (any(other_nms == "data")) { data_nms <- names(fit_obj$data) if (length(data_nms == 0) || any(data_nms == "")) { - rlang::abort("All elements of the `data` argument vector must be named.") + cli::cli_abort( + "All elements of the {.field data} argument vector must be named." + ) } } @@ -387,7 +413,7 @@ check_fit_info <- function(fit_obj) { check_func_val(fit_obj$func) if (!is.list(fit_obj$defaults)) { - rlang::abort("The `defaults` element should be a list: ") + cli::cli_abort("The {.field defaults} element should be a list.") } invisible(NULL) @@ -395,31 +421,29 @@ check_fit_info <- function(fit_obj) { check_pred_info <- function(pred_obj, type) { if (all(type != pred_types)) { - rlang::abort( - glue::glue("The prediction type should be one of: ", - glue::glue_collapse(glue::glue("'{pred_types}'"), sep = ", ")) - ) + cli::cli_abort( + "The prediction type should be one of: {.val {pred_types}}." + ) } exp_nms <- c("args", "func", "post", "pre") if (!isTRUE(all.equal(sort(names(pred_obj)), exp_nms))) { - rlang::abort( - glue::glue("The `predict` module should have elements: ", - glue::glue_collapse(glue::glue("`{exp_nms}`"), sep = ", ")) - ) + cli::cli_abort( + "The {.field predict} module should have elements: {.val {exp_nms}}." + ) } if (!is.null(pred_obj$pre) & !is.function(pred_obj$pre)) { - rlang::abort("The `pre` module should be null or a function: ") + cli::cli_abort("The {.field pre} module should be null or a function.") } if (!is.null(pred_obj$post) & !is.function(pred_obj$post)) { - rlang::abort("The `post` module should be null or a function: ") + cli::cli_abort("The {.field post} module should be null or a function.") } check_func_val(pred_obj$func) if (!is.list(pred_obj$args)) { - rlang::abort("The `args` element should be a list. ") + cli::cli_abort("The {.field args} element should be a list.") } invisible(NULL) @@ -433,18 +457,19 @@ spec_has_pred_type <- function(object, type) { check_spec_pred_type <- function(object, type) { if (!spec_has_pred_type(object, type)) { possible_preds <- names(object$spec$method$pred) - rlang::abort(c( - glue::glue("No {type} prediction method available for this model."), - glue::glue("Value for `type` should be one of: ", - glue::glue_collapse(glue::glue("'{possible_preds}'"), sep = ", ")) - )) + cli::cli_abort( + "No {.val {type}} prediction method available for this model. \\ + {.arg type} should be one of: {.val {possible_preds}}." + ) } invisible(NULL) } check_pkg_val <- function(pkg) { if (rlang::is_missing(pkg) || length(pkg) != 1 || !is.character(pkg)) { - rlang::abort("Please supply a single character value for the package name.") + cli::cli_abort( + "Please supply a single character value for the package name." + ) } invisible(NULL) } @@ -452,9 +477,8 @@ check_pkg_val <- function(pkg) { check_interface_val <- function(x) { exp_interf <- c("data.frame", "formula", "matrix") if (length(x) != 1 || !(x %in% exp_interf)) { - rlang::abort( - glue::glue("The `interface` element should have a single value of: ", - glue::glue_collapse(glue::glue("`{exp_interf}`"), sep = ", ")) + cli::cli_abort( + "The {.field interface} element should have a single of: {exp_interf}." ) } invisible(NULL) @@ -674,7 +698,7 @@ set_model_arg <- function(model, eng, parsnip, original, func, has_submodel) { updated <- try(dplyr::bind_rows(old_args, new_arg), silent = TRUE) if (inherits(updated, "try-error")) { - rlang::abort("An error occured when adding the new argument.") + cli::cli_abort("An error occurred when adding the new argument.") } updated <- vctrs::vec_unique(updated) @@ -706,8 +730,9 @@ set_dependency <- function(model, eng, pkg = "parsnip", mode = NULL) { dplyr::filter(engine == eng) %>% nrow() if (has_engine != 1) { - rlang::abort( - glue::glue("The engine '{eng}' has not been registered for model '{model}'.") + cli::cli_abort( + "The engine {.val {eng}} has not been registered for model \\ + {.val {model}}." ) } @@ -719,10 +744,12 @@ set_dependency <- function(model, eng, pkg = "parsnip", mode = NULL) { mode <- all_modes } else { if (length(mode) > 1) { - rlang::abort("'mode' should be a single character value or NULL.") + cli::cli_abort("{.arg mode} should be a single character value or NULL.") } if (!any(mode == all_modes)) { - rlang::abort(glue::glue("mode '{mode}' is not a valid mode for '{model}'")) + cli::cli_abort( + "mode {.val {mode}} is not a valid mode for {.val {model}}." + ) } } @@ -762,7 +789,7 @@ get_dependency <- function(model) { check_model_exists(model) pkg_name <- paste0(model, "_pkgs") if (!any(pkg_name != rlang::env_names(get_model_env()))) { - rlang::abort(glue::glue("`{model}` does not have a dependency list in parsnip.")) + cli::cli_abort("{.val {model}} does not have a dependency list in parsnip.") } rlang::env_get(get_model_env(), pkg_name) } @@ -801,12 +828,10 @@ is_discordant_info <- function(model, mode, eng, candidate, same_info <- isTRUE(all.equal(current, candidate, check.environment = FALSE)) if (!same_info) { - rlang::abort( - glue::glue( - "The combination of engine '{eng}' and mode '{mode}' {p_type} already has ", - "{component} data for model '{model}' and the new information being ", - "registered is different." - ) + cli::cli_abort( + "The combination of engine {.var {eng}} and mode {.var {mode}} \\ + {.val {p_type}} already has {component} data for model {.var {model}} \\ + and the new information being registered is different." ) } @@ -822,10 +847,9 @@ check_unregistered <- function(model, mode, eng) { dplyr::filter(engine == eng & mode == !!mode) %>% nrow() if (has_engine != 1) { - rlang::abort( - glue::glue("The combination of engine '{eng}' and mode '{mode}' has not ", - "been registered for model '{model}'.") - ) + cli::cli_abort( + "The combination of engine {.var {eng}} and mode {.var {mode}} has not \\ + been registered for model {.var {model}}.") } invisible(NULL) } @@ -856,7 +880,7 @@ set_fit <- function(model, mode, eng, value) { old_fits <- get_from_env(paste0(model, "_fit")) updated <- try(dplyr::bind_rows(old_fits, new_fit), silent = TRUE) if (inherits(updated, "try-error")) { - rlang::abort("An error occured when adding the new fit module.") + cli::cli_abort("An error occurred when adding the new fit module.") } set_env_val( @@ -874,7 +898,7 @@ get_fit <- function(model) { check_model_exists(model) fit_name <- paste0(model, "_fit") if (!any(fit_name != rlang::env_names(get_model_env()))) { - rlang::abort(glue::glue("`{model}` does not have a `fit` method in parsnip.")) + cli::cli_abort("{.arg {model}} does not have a {.fn fit} method in parsnip.") } rlang::env_get(get_model_env(), fit_name) } @@ -907,7 +931,7 @@ set_pred <- function(model, mode, eng, type, value) { old_pred <- get_from_env(paste0(model, "_predict")) updated <- try(dplyr::bind_rows(old_pred, new_pred), silent = TRUE) if (inherits(updated, "try-error")) { - rlang::abort("An error occured when adding the new fit module.") + cli::cli_abort("An error occurred when adding the new fit module.") } set_env_val(paste0(model, "_predict"), updated) @@ -922,11 +946,13 @@ get_pred_type <- function(model, type) { check_model_exists(model) pred_name <- paste0(model, "_predict") if (!any(pred_name != rlang::env_names(get_model_env()))) { - rlang::abort(glue::glue("`{model}` does not have any `pred` methods in parsnip.")) + cli::cli_abort( + "{.arg {model}} does not have any {.arg pred} methods in parsnip." + ) } all_preds <- rlang::env_get(get_model_env(), pred_name) if (!any(all_preds$type == type)) { - rlang::abort(glue::glue("`{model}` does not have any prediction methods in parsnip.")) + cli::cli_abort("{.arg {model}} does not have any prediction methods in parsnip.") } dplyr::filter(all_preds, type == !!type) } @@ -1047,7 +1073,9 @@ show_model_info <- function(model) { #' @export pred_value_template <- function(pre = NULL, post = NULL, func, ...) { if (rlang::is_missing(func)) { - rlang::abort("Please supply a value to `func`. See `?set_pred`.") + cli::cli_abort( + "Please supply a value to {.arg func}. See {.help [{.fun set_pred}](parsnip::set_pred)}." + ) } list(pre = pre, post = post, func = func, args = list(...)) } @@ -1056,7 +1084,7 @@ pred_value_template <- function(pre = NULL, post = NULL, func, ...) { check_encodings <- function(x) { if (!is.list(x)) { - rlang::abort("`values` should be a list.") + cli::cli_abort("{.arg values} should be a list.") } req_args <- list(predictor_indicators = rlang::na_chr, compute_intercept = rlang::na_lgl, @@ -1065,20 +1093,16 @@ check_encodings <- function(x) { missing_args <- setdiff(names(req_args), names(x)) if (length(missing_args) > 0) { - rlang::abort( - glue::glue( - "The values passed to `set_encoding()` are missing arguments: ", - paste0("'", missing_args, "'", collapse = ", ") - ) + cli::cli_abort( + "The values passed to {.fn set_encoding} are missing arguments: \\ + {.field {missing_args}}." ) } extra_args <- setdiff(names(x), names(req_args)) if (length(extra_args) > 0) { - rlang::abort( - glue::glue( - "The values passed to `set_encoding()` had extra arguments: ", - paste0("'", extra_args, "'", collapse = ", ") - ) + cli::cli_abort( + "The values passed to {.fn set_encoding} had extra arguments: \\ + {.arg {extra_args}}." ) } invisible(x) diff --git a/R/import-standalone-obj-type.R b/R/import-standalone-obj-type.R new file mode 100644 index 000000000..8e3c07df4 --- /dev/null +++ b/R/import-standalone-obj-type.R @@ -0,0 +1,360 @@ +# Standalone file: do not edit by hand +# Source: +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-obj-type.R +# last-updated: 2023-05-01 +# license: https://unlicense.org +# imports: rlang (>= 1.1.0) +# --- +# +# ## Changelog +# +# 2023-05-01: +# - `obj_type_friendly()` now only displays the first class of S3 objects. +# +# 2023-03-30: +# - `stop_input_type()` now handles `I()` input literally in `arg`. +# +# 2022-10-04: +# - `obj_type_friendly(value = TRUE)` now shows numeric scalars +# literally. +# - `stop_friendly_type()` now takes `show_value`, passed to +# `obj_type_friendly()` as the `value` argument. +# +# 2022-10-03: +# - Added `allow_na` and `allow_null` arguments. +# - `NULL` is now backticked. +# - Better friendly type for infinities and `NaN`. +# +# 2022-09-16: +# - Unprefixed usage of rlang functions with `rlang::` to +# avoid onLoad issues when called from rlang (#1482). +# +# 2022-08-11: +# - Prefixed usage of rlang functions with `rlang::`. +# +# 2022-06-22: +# - `friendly_type_of()` is now `obj_type_friendly()`. +# - Added `obj_type_oo()`. +# +# 2021-12-20: +# - Added support for scalar values and empty vectors. +# - Added `stop_input_type()` +# +# 2021-06-30: +# - Added support for missing arguments. +# +# 2021-04-19: +# - Added support for matrices and arrays (#141). +# - Added documentation. +# - Added changelog. +# +# nocov start + +#' Return English-friendly type +#' @param x Any R object. +#' @param value Whether to describe the value of `x`. Special values +#' like `NA` or `""` are always described. +#' @param length Whether to mention the length of vectors and lists. +#' @return A string describing the type. Starts with an indefinite +#' article, e.g. "an integer vector". +#' @noRd +obj_type_friendly <- function(x, value = TRUE) { + if (is_missing(x)) { + return("absent") + } + + if (is.object(x)) { + if (inherits(x, "quosure")) { + type <- "quosure" + } else { + type <- class(x)[[1L]] + } + return(sprintf("a <%s> object", type)) + } + + if (!is_vector(x)) { + return(.rlang_as_friendly_type(typeof(x))) + } + + n_dim <- length(dim(x)) + + if (!n_dim) { + if (!is_list(x) && length(x) == 1) { + if (is_na(x)) { + return(switch( + typeof(x), + logical = "`NA`", + integer = "an integer `NA`", + double = + if (is.nan(x)) { + "`NaN`" + } else { + "a numeric `NA`" + }, + complex = "a complex `NA`", + character = "a character `NA`", + .rlang_stop_unexpected_typeof(x) + )) + } + + show_infinites <- function(x) { + if (x > 0) { + "`Inf`" + } else { + "`-Inf`" + } + } + str_encode <- function(x, width = 30, ...) { + if (nchar(x) > width) { + x <- substr(x, 1, width - 3) + x <- paste0(x, "...") + } + encodeString(x, ...) + } + + if (value) { + if (is.numeric(x) && is.infinite(x)) { + return(show_infinites(x)) + } + + if (is.numeric(x) || is.complex(x)) { + number <- as.character(round(x, 2)) + what <- if (is.complex(x)) "the complex number" else "the number" + return(paste(what, number)) + } + + return(switch( + typeof(x), + logical = if (x) "`TRUE`" else "`FALSE`", + character = { + what <- if (nzchar(x)) "the string" else "the empty string" + paste(what, str_encode(x, quote = "\"")) + }, + raw = paste("the raw value", as.character(x)), + .rlang_stop_unexpected_typeof(x) + )) + } + + return(switch( + typeof(x), + logical = "a logical value", + integer = "an integer", + double = if (is.infinite(x)) show_infinites(x) else "a number", + complex = "a complex number", + character = if (nzchar(x)) "a string" else "\"\"", + raw = "a raw value", + .rlang_stop_unexpected_typeof(x) + )) + } + + if (length(x) == 0) { + return(switch( + typeof(x), + logical = "an empty logical vector", + integer = "an empty integer vector", + double = "an empty numeric vector", + complex = "an empty complex vector", + character = "an empty character vector", + raw = "an empty raw vector", + list = "an empty list", + .rlang_stop_unexpected_typeof(x) + )) + } + } + + vec_type_friendly(x) +} + +vec_type_friendly <- function(x, length = FALSE) { + if (!is_vector(x)) { + abort("`x` must be a vector.") + } + type <- typeof(x) + n_dim <- length(dim(x)) + + add_length <- function(type) { + if (length && !n_dim) { + paste0(type, sprintf(" of length %s", length(x))) + } else { + type + } + } + + if (type == "list") { + if (n_dim < 2) { + return(add_length("a list")) + } else if (is.data.frame(x)) { + return("a data frame") + } else if (n_dim == 2) { + return("a list matrix") + } else { + return("a list array") + } + } + + type <- switch( + type, + logical = "a logical %s", + integer = "an integer %s", + numeric = , + double = "a double %s", + complex = "a complex %s", + character = "a character %s", + raw = "a raw %s", + type = paste0("a ", type, " %s") + ) + + if (n_dim < 2) { + kind <- "vector" + } else if (n_dim == 2) { + kind <- "matrix" + } else { + kind <- "array" + } + out <- sprintf(type, kind) + + if (n_dim >= 2) { + out + } else { + add_length(out) + } +} + +.rlang_as_friendly_type <- function(type) { + switch( + type, + + list = "a list", + + NULL = "`NULL`", + environment = "an environment", + externalptr = "a pointer", + weakref = "a weak reference", + S4 = "an S4 object", + + name = , + symbol = "a symbol", + language = "a call", + pairlist = "a pairlist node", + expression = "an expression vector", + + char = "an internal string", + promise = "an internal promise", + ... = "an internal dots object", + any = "an internal `any` object", + bytecode = "an internal bytecode object", + + primitive = , + builtin = , + special = "a primitive function", + closure = "a function", + + type + ) +} + +.rlang_stop_unexpected_typeof <- function(x, call = caller_env()) { + abort( + sprintf("Unexpected type <%s>.", typeof(x)), + call = call + ) +} + +#' Return OO type +#' @param x Any R object. +#' @return One of `"bare"` (for non-OO objects), `"S3"`, `"S4"`, +#' `"R6"`, or `"R7"`. +#' @noRd +obj_type_oo <- function(x) { + if (!is.object(x)) { + return("bare") + } + + class <- inherits(x, c("R6", "R7_object"), which = TRUE) + + if (class[[1]]) { + "R6" + } else if (class[[2]]) { + "R7" + } else if (isS4(x)) { + "S4" + } else { + "S3" + } +} + +#' @param x The object type which does not conform to `what`. Its +#' `obj_type_friendly()` is taken and mentioned in the error message. +#' @param what The friendly expected type as a string. Can be a +#' character vector of expected types, in which case the error +#' message mentions all of them in an "or" enumeration. +#' @param show_value Passed to `value` argument of `obj_type_friendly()`. +#' @param ... Arguments passed to [abort()]. +#' @inheritParams args_error_context +#' @noRd +stop_input_type <- function(x, + what, + ..., + allow_na = FALSE, + allow_null = FALSE, + show_value = TRUE, + arg = caller_arg(x), + call = caller_env()) { + # From standalone-cli.R + cli <- env_get_list( + nms = c("format_arg", "format_code"), + last = topenv(), + default = function(x) sprintf("`%s`", x), + inherit = TRUE + ) + + if (allow_na) { + what <- c(what, cli$format_code("NA")) + } + if (allow_null) { + what <- c(what, cli$format_code("NULL")) + } + if (length(what)) { + what <- oxford_comma(what) + } + if (inherits(arg, "AsIs")) { + format_arg <- identity + } else { + format_arg <- cli$format_arg + } + + message <- sprintf( + "%s must be %s, not %s.", + format_arg(arg), + what, + obj_type_friendly(x, value = show_value) + ) + + abort(message, ..., call = call, arg = arg) +} + +oxford_comma <- function(chr, sep = ", ", final = "or") { + n <- length(chr) + + if (n < 2) { + return(chr) + } + + head <- chr[seq_len(n - 1)] + last <- chr[n] + + head <- paste(head, collapse = sep) + + # Write a or b. But a, b, or c. + if (n > 2) { + paste0(head, sep, final, " ", last) + } else { + paste0(head, " ", final, " ", last) + } +} + +# nocov end diff --git a/R/import-standalone-types-check.R b/R/import-standalone-types-check.R new file mode 100644 index 000000000..6782d69b1 --- /dev/null +++ b/R/import-standalone-types-check.R @@ -0,0 +1,538 @@ +# Standalone file: do not edit by hand +# Source: +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-types-check.R +# last-updated: 2023-03-13 +# license: https://unlicense.org +# dependencies: standalone-obj-type.R +# imports: rlang (>= 1.1.0) +# --- +# +# ## Changelog +# +# 2023-03-13: +# - Improved error messages of number checkers (@teunbrand) +# - Added `allow_infinite` argument to `check_number_whole()` (@mgirlich). +# - Added `check_data_frame()` (@mgirlich). +# +# 2023-03-07: +# - Added dependency on rlang (>= 1.1.0). +# +# 2023-02-15: +# - Added `check_logical()`. +# +# - `check_bool()`, `check_number_whole()`, and +# `check_number_decimal()` are now implemented in C. +# +# - For efficiency, `check_number_whole()` and +# `check_number_decimal()` now take a `NULL` default for `min` and +# `max`. This makes it possible to bypass unnecessary type-checking +# and comparisons in the default case of no bounds checks. +# +# 2022-10-07: +# - `check_number_whole()` and `_decimal()` no longer treat +# non-numeric types such as factors or dates as numbers. Numeric +# types are detected with `is.numeric()`. +# +# 2022-10-04: +# - Added `check_name()` that forbids the empty string. +# `check_string()` allows the empty string by default. +# +# 2022-09-28: +# - Removed `what` arguments. +# - Added `allow_na` and `allow_null` arguments. +# - Added `allow_decimal` and `allow_infinite` arguments. +# - Improved errors with absent arguments. +# +# +# 2022-09-16: +# - Unprefixed usage of rlang functions with `rlang::` to +# avoid onLoad issues when called from rlang (#1482). +# +# 2022-08-11: +# - Added changelog. +# +# nocov start + +# Scalars ----------------------------------------------------------------- + +.standalone_types_check_dot_call <- .Call + +check_bool <- function(x, + ..., + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x) && .standalone_types_check_dot_call(ffi_standalone_is_bool_1.0.7, x, allow_na, allow_null)) { + return(invisible(NULL)) + } + + stop_input_type( + x, + c("`TRUE`", "`FALSE`"), + ..., + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_string <- function(x, + ..., + allow_empty = TRUE, + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + is_string <- .rlang_check_is_string( + x, + allow_empty = allow_empty, + allow_na = allow_na, + allow_null = allow_null + ) + if (is_string) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a single string", + ..., + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +.rlang_check_is_string <- function(x, + allow_empty, + allow_na, + allow_null) { + if (is_string(x)) { + if (allow_empty || !is_string(x, "")) { + return(TRUE) + } + } + + if (allow_null && is_null(x)) { + return(TRUE) + } + + if (allow_na && (identical(x, NA) || identical(x, na_chr))) { + return(TRUE) + } + + FALSE +} + +check_name <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + is_string <- .rlang_check_is_string( + x, + allow_empty = FALSE, + allow_na = FALSE, + allow_null = allow_null + ) + if (is_string) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a valid name", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +IS_NUMBER_true <- 0 +IS_NUMBER_false <- 1 +IS_NUMBER_oob <- 2 + +check_number_decimal <- function(x, + ..., + min = NULL, + max = NULL, + allow_infinite = TRUE, + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (missing(x)) { + exit_code <- IS_NUMBER_false + } else if (0 == (exit_code <- .standalone_types_check_dot_call( + ffi_standalone_check_number_1.0.7, + x, + allow_decimal = TRUE, + min, + max, + allow_infinite, + allow_na, + allow_null + ))) { + return(invisible(NULL)) + } + + .stop_not_number( + x, + ..., + exit_code = exit_code, + allow_decimal = TRUE, + min = min, + max = max, + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_number_whole <- function(x, + ..., + min = NULL, + max = NULL, + allow_infinite = FALSE, + allow_na = FALSE, + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (missing(x)) { + exit_code <- IS_NUMBER_false + } else if (0 == (exit_code <- .standalone_types_check_dot_call( + ffi_standalone_check_number_1.0.7, + x, + allow_decimal = FALSE, + min, + max, + allow_infinite, + allow_na, + allow_null + ))) { + return(invisible(NULL)) + } + + .stop_not_number( + x, + ..., + exit_code = exit_code, + allow_decimal = FALSE, + min = min, + max = max, + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +.stop_not_number <- function(x, + ..., + exit_code, + allow_decimal, + min, + max, + allow_na, + allow_null, + arg, + call) { + if (allow_decimal) { + what <- "a number" + } else { + what <- "a whole number" + } + + if (exit_code == IS_NUMBER_oob) { + min <- min %||% -Inf + max <- max %||% Inf + + if (min > -Inf && max < Inf) { + what <- sprintf("%s between %s and %s", what, min, max) + } else if (x < min) { + what <- sprintf("%s larger than or equal to %s", what, min) + } else if (x > max) { + what <- sprintf("%s smaller than or equal to %s", what, max) + } else { + abort("Unexpected state in OOB check", .internal = TRUE) + } + } + + stop_input_type( + x, + what, + ..., + allow_na = allow_na, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_symbol <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_symbol(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a symbol", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_arg <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_symbol(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "an argument name", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_call <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_call(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a defused call", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_environment <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_environment(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "an environment", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_function <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_function(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a function", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_closure <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_closure(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "an R function", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_formula <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_formula(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a formula", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + + +# Vectors ----------------------------------------------------------------- + +check_character <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_character(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a character vector", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_logical <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is_logical(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a logical vector", + ..., + allow_na = FALSE, + allow_null = allow_null, + arg = arg, + call = call + ) +} + +check_data_frame <- function(x, + ..., + allow_null = FALSE, + arg = caller_arg(x), + call = caller_env()) { + if (!missing(x)) { + if (is.data.frame(x)) { + return(invisible(NULL)) + } + if (allow_null && is_null(x)) { + return(invisible(NULL)) + } + } + + stop_input_type( + x, + "a data frame", + ..., + allow_null = allow_null, + arg = arg, + call = call + ) +} + +# nocov end diff --git a/R/parsnip-package.R b/R/parsnip-package.R index 5aa6e0848..01f1f42c1 100644 --- a/R/parsnip-package.R +++ b/R/parsnip-package.R @@ -8,6 +8,7 @@ "_PACKAGE" ## usethis namespace: start +#' @import rlang #' @importFrom dplyr arrange bind_cols bind_rows collect full_join group_by #' @importFrom dplyr mutate pull rename select starts_with summarise tally #' @importFrom generics tunable varying_args tune_args @@ -17,9 +18,6 @@ #' @importFrom pillar type_sum #' @importFrom purrr as_vector imap imap_lgl map map_chr map_dbl #' @importFrom purrr map_lgl %||% -#' @importFrom rlang abort call2 caller_env current_env enquo enquos eval_tidy -#' @importFrom rlang expr get_expr is_empty is_missing is_null is_quosure -#' @importFrom rlang is_symbolic lgl missing_arg quo_get_expr quos sym syms #' @importFrom stats .checkMFClasses .getXlevels as.formula binomial coef #' @importFrom stats delete.response model.frame model.matrix model.offset #' @importFrom stats model.response model.weights na.omit na.pass predict qnorm diff --git a/tests/testthat/_snaps/args_and_modes.md b/tests/testthat/_snaps/args_and_modes.md index 598a27092..ea4b2a436 100644 --- a/tests/testthat/_snaps/args_and_modes.md +++ b/tests/testthat/_snaps/args_and_modes.md @@ -4,7 +4,7 @@ set_mode(linear_reg(), "classification") Condition Error in `set_mode()`: - ! 'classification' is not a known mode for model `linear_reg()`. + ! "classification" is not a known mode for model `linear_reg()`. # unavailable modes for an engine and vice-versa @@ -12,7 +12,7 @@ decision_tree() %>% set_mode("regression") %>% set_engine("C5.0") Condition Error in `set_engine()`: - ! Available modes for engine C5.0 are: 'unknown', 'classification' + ! Available modes for engine C5.0 are: "unknown" and "classification". --- @@ -20,7 +20,7 @@ decision_tree(mode = "regression", engine = "C5.0") Condition Error in `decision_tree()`: - ! Available modes for engine C5.0 are: 'unknown', 'classification' + ! Available modes for engine C5.0 are: "unknown" and "classification". --- @@ -28,7 +28,7 @@ decision_tree() %>% set_engine("C5.0") %>% set_mode("regression") Condition Error in `set_mode()`: - ! Available modes for engine C5.0 are: 'unknown', 'classification' + ! Available modes for engine C5.0 are: "unknown" and "classification". --- @@ -36,7 +36,7 @@ decision_tree(engine = NULL) %>% set_engine("C5.0") %>% set_mode("regression") Condition Error in `set_mode()`: - ! Available modes for engine C5.0 are: 'unknown', 'classification' + ! Available modes for engine C5.0 are: "unknown" and "classification". --- @@ -44,7 +44,7 @@ decision_tree(engine = NULL) %>% set_mode("regression") %>% set_engine("C5.0") Condition Error in `set_engine()`: - ! Available modes for engine C5.0 are: 'unknown', 'classification' + ! Available modes for engine C5.0 are: "unknown" and "classification". --- @@ -52,7 +52,7 @@ proportional_hazards() %>% set_mode("regression") Condition Error in `set_mode()`: - ! 'regression' is not a known mode for model `proportional_hazards()`. + ! "regression" is not a known mode for model `proportional_hazards()`. --- @@ -60,7 +60,7 @@ linear_reg() %>% set_mode() Condition Error in `set_mode()`: - ! Available modes for model type linear_reg are: 'unknown', 'regression' + ! Available modes for model type linear_reg are: "unknown" and "regression". --- @@ -68,7 +68,8 @@ linear_reg(engine = "boop") Condition Error in `linear_reg()`: - ! Engine 'boop' is not supported for `linear_reg()`. See `show_engines('linear_reg')`. + x Engine "boop" is not supported for `linear_reg()` + i See `show_engines("linear_reg")`. --- @@ -76,7 +77,7 @@ linear_reg() %>% set_engine() Condition Error in `set_engine()`: - ! Missing engine. Possible mode/engine combinations are: regression {lm, glm, glmnet, stan, spark, keras, brulee} + ! Missing engine. Possible mode/engine combinations are: regression {lm, glm, glmnet, stan, spark, keras, brulee}. --- diff --git a/tests/testthat/_snaps/decision_tree.md b/tests/testthat/_snaps/decision_tree.md index f43180156..0a6aa9dc2 100644 --- a/tests/testthat/_snaps/decision_tree.md +++ b/tests/testthat/_snaps/decision_tree.md @@ -17,7 +17,7 @@ # bad input - 'bogus' is not a known mode for model `decision_tree()`. + "bogus" is not a known mode for model `decision_tree()`. --- diff --git a/tests/testthat/_snaps/poisson_reg.md b/tests/testthat/_snaps/poisson_reg.md index 137af21dc..72bf35b99 100644 --- a/tests/testthat/_snaps/poisson_reg.md +++ b/tests/testthat/_snaps/poisson_reg.md @@ -26,7 +26,7 @@ poisson_reg(mode = "bogus") Condition Error in `poisson_reg()`: - ! 'bogus' is not a known mode for model `poisson_reg()`. + ! "bogus" is not a known mode for model `poisson_reg()`. --- diff --git a/tests/testthat/_snaps/registration.md b/tests/testthat/_snaps/registration.md new file mode 100644 index 000000000..690e25445 --- /dev/null +++ b/tests/testthat/_snaps/registration.md @@ -0,0 +1,8 @@ +# adding a new engine + + Code + set_model_engine("sponge", mode = "regression", eng = "gum") + Condition + Error in `check_mode_for_new_engine()`: + ! "regression" is not a known mode for model `sponge()`. + diff --git a/tests/testthat/_snaps/svm_linear.md b/tests/testthat/_snaps/svm_linear.md index 4d2c50acc..d493980dd 100644 --- a/tests/testthat/_snaps/svm_linear.md +++ b/tests/testthat/_snaps/svm_linear.md @@ -15,3 +15,19 @@ Computational engine: kernlab +# linear svm classification prediction: LiblineaR + + Code + predict(cls_form, hpc_no_m[ind, -5], type = "prob") + Condition + Error in `check_spec_pred_type()`: + ! No "prob" prediction method available for this model. `type` should be one of: "class" and "raw". + +--- + + Code + predict(cls_xy_form, hpc_no_m[ind, -5], type = "prob") + Condition + Error in `check_spec_pred_type()`: + ! No "prob" prediction method available for this model. `type` should be one of: "class" and "raw". + diff --git a/tests/testthat/test_registration.R b/tests/testthat/test_registration.R index 3c1fe5b24..414e59818 100644 --- a/tests/testthat/test_registration.R +++ b/tests/testthat/test_registration.R @@ -71,9 +71,9 @@ test_that('adding a new engine', { expect_error(set_model_engine("sponge", eng = "gum")) expect_error(set_model_engine("sponge", mode = "classification")) - expect_error( - set_model_engine("sponge", mode = "regression", eng = "gum"), - "'regression' is not a known mode" + expect_snapshot( + error = TRUE, + set_model_engine("sponge", mode = "regression", eng = "gum") ) }) diff --git a/tests/testthat/test_svm_linear.R b/tests/testthat/test_svm_linear.R index ef6a736ae..0b875c093 100644 --- a/tests/testthat/test_svm_linear.R +++ b/tests/testthat/test_svm_linear.R @@ -184,14 +184,14 @@ test_that('linear svm classification prediction: LiblineaR', { ) expect_equal(extract_fit_engine(cls_form)$W, extract_fit_engine(cls_xy_form)$W) - expect_error( - predict(cls_form, hpc_no_m[ind, -5], type = "prob"), - "No prob prediction method available for this model" + expect_snapshot( + error = TRUE, + predict(cls_form, hpc_no_m[ind, -5], type = "prob") ) - expect_error( - predict(cls_xy_form, hpc_no_m[ind, -5], type = "prob"), - "No prob prediction method available for this model" + expect_snapshot( + error = TRUE, + predict(cls_xy_form, hpc_no_m[ind, -5], type = "prob") ) })