From 49e586e8234b8dbee3e8c473823f638091a8fe9c Mon Sep 17 00:00:00 2001 From: Emil Hvitfeldt Date: Sun, 23 Aug 2026 15:14:10 -0700 Subject: [PATCH] Fix cforest models under partykit 1.3-0 partykit 1.3-0 added a shim to its S3 methods that warns when a method is called directly rather than through the generic. It identifies the caller with as.name(as.list(sys.call(-1))[[1L]]) which errors on any call whose first element is not a symbol. The generic's own frame is the method's caller, so calling `partykit::gettree(model, tree_no)` makes that first element the `::` call, and every cforest model fails with 'language' object cannot be coerced to type 'symbol' This breaks tidypredict_fit(), parse_model() and the partykit vignette, so R CMD check fails at vignette building. Bind the generic to a local name and call it through that symbol. The name has to be `gettree`, since the shim compares the symbol it recovers against the generic's own name and only stays quiet when they match; any other name works but emits a spurious deprecation warning per call. Only gettree is affected. nodeids, nodeapply, as.party, id_node, is.terminal and kids_node were each checked and are fine with the `::` prefix. --- NEWS.md | 2 ++ R/model-cforest.R | 24 ++++++++++++++++++++++-- tests/testthat/test-model-cforest.R | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index b0d47ae2..43d0f006 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # tidypredict (development version) +- Fixed `cforest()` models failing with "'language' object cannot be coerced to type 'symbol'" under partykit 1.3-0. That release added a shim to partykit's methods that identifies the caller with `as.name()`, which errors when the generic is reached as `partykit::gettree()`. (#434) + - The error raised when no method knows how to handle a model at all now carries the condition class `tidypredict_unsupported_model`. Many other errors also say "are not supported", but they report an unsupported *configuration* of a model that is otherwise handled, so the wording alone could not distinguish the two. Packages that wrap `tidypredict_fit()`, such as orbital, need that distinction to decide whether to fall back or to report the model as unsupported. (#432) - New articles for `kernlab::ksvm()`, `mboost::blackboost()` and `xrf::xrf()`, and the model list menu now links to the `LiblineaR` and `quantreg` sections directly. (#317) diff --git a/R/model-cforest.R b/R/model-cforest.R index c8747803..1e232624 100644 --- a/R/model-cforest.R +++ b/R/model-cforest.R @@ -3,6 +3,26 @@ # weighted terminal-node mean, so it can be expressed as the mean of the # individual party-tree expressions. +# partykit 1.3-0 added a shim to its methods that warns when a method is called +# directly instead of through the generic. It identifies the caller with +# `as.name(as.list(sys.call(-1))[[1L]])`, which errors on a call whose first +# element is not a symbol. The generic's own frame is the method's caller, so +# writing `partykit::gettree(...)` makes that first element the `::` call and +# every `gettree()` on a cforest model fails with "'language' object cannot be +# coerced to type 'symbol'". +# +# Binding the generic to a local name calls it through a symbol instead. The +# name has to be `gettree`, because the shim then compares that symbol against +# the generic's own name and stays quiet only if they match. +# +# Only `gettree` is affected. The other partykit functions used here +# (`nodeids`, `nodeapply`, `as.party`, `id_node`, `is.terminal`, `kids_node`) +# were checked and are fine with the `::` prefix. +cforest_gettree <- function(model, tree_no) { + gettree <- partykit::gettree + gettree(model, tree_no) +} + cforest_check_regression <- function(model) { response_col <- model$fitted[["(response)"]] if (!is.numeric(response_col)) { @@ -24,7 +44,7 @@ parse_model.cforest <- function(model) { n_trees <- length(model$nodes) pm$tree_info_list <- map( seq_len(n_trees), - function(tree_no) partykit_tree_info_full(partykit::gettree(model, tree_no)) + function(tree_no) partykit_tree_info_full(cforest_gettree(model, tree_no)) ) as_parsed_model(pm) } @@ -39,7 +59,7 @@ tidypredict_fit.cforest <- function(model, ...) { tree_exprs <- map( seq_len(n_trees), function(tree_no) { - tree_info <- partykit_tree_info_full(partykit::gettree(model, tree_no)) + tree_info <- partykit_tree_info_full(cforest_gettree(model, tree_no)) generate_nested_case_when_tree(tree_info, missing = "na") } ) diff --git a/tests/testthat/test-model-cforest.R b/tests/testthat/test-model-cforest.R index 2b40b155..3fe308b6 100644 --- a/tests/testthat/test-model-cforest.R +++ b/tests/testthat/test-model-cforest.R @@ -8,6 +8,23 @@ test_that("cforest regression predictions match", { expect_false(tidypredict_test(model, df = mtcars)$alert) }) +test_that("gettree() is reached without tripping partykit's method shim", { + skip_if_not_installed("partykit") + + set.seed(1) + model <- partykit::cforest(mpg ~ wt + cyl, data = mtcars, ntree = 2) + + # partykit 1.3-0's shim errors if the generic is called as + # `partykit::gettree()`, and warns if it is reached under any name other than + # `gettree`. Assert both: no condition of either kind. + expect_no_error(cforest_gettree(model, 1)) + expect_no_warning(cforest_gettree(model, 1)) + expect_s3_class(cforest_gettree(model, 1), "party") + + expect_no_warning(tidypredict_fit(model)) + expect_no_warning(parse_model(model)) +}) + test_that("terminal nodes use in-bag weighted means, not unweighted means", { skip_if_not_installed("partykit")