Skip to content

Commit 8a322fb

Browse files
committed
maintain separate direct and Make-quoted stanc options
1 parent de41f16 commit 8a322fb

6 files changed

Lines changed: 139 additions & 15 deletions

File tree

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* The `CMDSTANR_NO_VER_CHECK` R option and environment variable are deprecated
44
as of CmdStanR 1.0.0; use the lowercase `cmdstanr_no_ver_check` forms instead.
5+
* `CmdStanModel$compile()` now compiles models with named `stanc_options` values such as `canonicalize`. (#1227)
56
* CmdStanModel methods now correctly handle `#include` directories with spaces
67
in their paths. (#820)
78
* `$include_paths()` now returns absolute paths, and relative include paths are

R/model.R

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -710,28 +710,37 @@ compile <- function(quiet = TRUE,
710710
stanc_options[["name"]] <- paste0(self$model_name(), "_model")
711711
}
712712
stanc_built_options <- c()
713+
stanc_direct_options <- c()
713714
for (i in seq_len(length(stanc_options))) {
714715
option_name <- names(stanc_options)[i]
715716
if (isTRUE(as.logical(stanc_options[[i]]))) {
716-
stanc_built_options <- c(stanc_built_options, paste0("--", option_name))
717+
stanc_direct_option <- paste0("--", option_name)
718+
stanc_built_option <- stanc_direct_option
717719
} else if (is.null(option_name) || !nzchar(option_name)) {
718-
stanc_built_options <- c(stanc_built_options, paste0("--", stanc_options[[i]]))
720+
stanc_direct_option <- paste0("--", stanc_options[[i]])
721+
stanc_built_option <- stanc_direct_option
719722
} else if (option_name == "name") { # Quoting model name mangles generated namespace
720-
stanc_built_options <- c(stanc_built_options, paste0("--", option_name, "=", stanc_options[[i]]))
721-
} else {
722-
stanc_built_options <- c(stanc_built_options, paste0("--", option_name, "=", "'", stanc_options[[i]], "'"))
723+
stanc_direct_option <- paste0("--", option_name, "=", stanc_options[[i]])
724+
stanc_built_option <- stanc_direct_option
725+
} else {
726+
stanc_direct_option <- paste0("--", option_name, "=", stanc_options[[i]])
727+
stanc_built_option <- paste0("--", option_name, "=", "'", stanc_options[[i]], "'")
723728
}
729+
stanc_direct_options <- c(stanc_direct_options, stanc_direct_option)
730+
stanc_built_options <- c(stanc_built_options, stanc_built_option)
724731
}
725732
stancflags_combined <- stanc_built_options
733+
stancflags_direct <- stanc_direct_options
726734
stancflags_local <- get_cmdstan_flags("STANCFLAGS")
727735
if (length(stancflags_local) > 0) {
728736
stancflags_combined <- c(stancflags_combined, stancflags_local)
737+
stancflags_direct <- c(stancflags_direct, stancflags_local)
729738
}
730739
stanc_inc_paths <- include_paths_stanc3_args(include_paths, direct_call = TRUE)
731-
stancflags_standalone <- c("--standalone-functions", stanc_inc_paths, stancflags_combined)
740+
stancflags_standalone <- c("--standalone-functions", stanc_inc_paths, stancflags_direct)
732741
self$functions$hpp_code <- get_standalone_hpp(temp_stan_file, stancflags_standalone)
733742
private$model_methods_env_ <- new.env()
734-
private$model_methods_env_$hpp_code_ <- get_standalone_hpp(temp_stan_file, c(stanc_inc_paths, stancflags_combined))
743+
private$model_methods_env_$hpp_code_ <- get_standalone_hpp(temp_stan_file, c(stanc_inc_paths, stancflags_direct))
735744
self$functions$external <- !is.null(user_header)
736745
self$functions$existing_exe <- FALSE
737746

R/utils.R

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,7 @@ get_standalone_hpp <- function(stan_file, stancflags) {
931931
name <- strip_ext(basename(stan_file))
932932
path <- dirname(stan_file)
933933
hpp_path <- file.path(path, paste0(name, ".hpp"))
934+
on.exit(unlink(hpp_path), add = TRUE)
934935

935936
status <- withr::with_path(
936937
c(
@@ -944,13 +945,22 @@ get_standalone_hpp <- function(stan_file, stancflags) {
944945
error_on_status = FALSE
945946
)
946947
)
947-
if (status$status == 0) {
948-
hpp <- suppressWarnings(readLines(hpp_path, warn = FALSE))
949-
unlink(hpp_path)
950-
hpp
951-
} else {
952-
invisible(NULL)
948+
if (is.null(status$status) || is.na(status$status) || status$status != 0) {
949+
if (length(status$stderr) > 0 && nzchar(status$stderr)) {
950+
message(status$stderr)
951+
}
952+
status_code <- if (is.null(status$status) || is.na(status$status)) {
953+
"missing"
954+
} else {
955+
status$status
956+
}
957+
stop(
958+
"stanc exited with status ", status_code, ".\n",
959+
"Failed to generate the model C++ header.",
960+
call. = FALSE
961+
)
953962
}
963+
suppressWarnings(readLines(hpp_path, warn = FALSE))
954964
}
955965

956966
get_function_name <- function(fun_start, fun_end, model_lines) {

tests/testthat/_snaps/utils.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# get_standalone_hpp() reports stanc failures
2+
3+
Code
4+
get_standalone_hpp(stan_file, "--canonicalize='deprecations'")
5+
Message
6+
stanc: invalid canonicalize value
7+
Condition
8+
Error:
9+
! stanc exited with status 124.
10+
Failed to generate the model C++ header.
11+
112
# copy_temp_files retains sources if any copy fails
213

314
Code

tests/testthat/test-model-compile.R

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ test_that("compile errors are shown", {
284284
stan_file <- testing_stan_file("fail")
285285
expect_error(
286286
cmdstan_model(stan_file),
287-
"An error occured during compilation! See the message above for more information."
287+
"stanc exited with status 1.\nFailed to generate the model C++ header.",
288+
fixed = TRUE
288289
)
289290
})
290291

@@ -522,7 +523,7 @@ test_that("check_syntax() works with pedantic=TRUE", {
522523
mod_dep_warning <- cmdstan_model(stan_file, compile = FALSE)
523524
expect_error(
524525
mod_dep_warning$compile(),
525-
"An error occured during compilation! See the message above for more information.",
526+
"stanc exited with status 1.\nFailed to generate the model C++ header.",
526527
fixed = TRUE
527528
)
528529
expect_error(
@@ -984,6 +985,72 @@ test_that("STANCFLAGS from get_cmdstan_flags() are included in compile output",
984985
expect_output(print(out), out_w_flags)
985986
})
986987

988+
test_that("compile() passes unquoted named stanc options to direct calls", {
989+
stan_file <- testing_stan_file("bernoulli")
990+
model <- cmdstan_model(stan_file, compile = FALSE)
991+
received_stancflags <- list()
992+
local_mocked_bindings(
993+
get_cmdstan_flags = function(flag_name) character(),
994+
get_standalone_hpp = function(stan_file, stancflags) {
995+
received_stancflags <<- append(received_stancflags, list(stancflags))
996+
""
997+
}
998+
)
999+
1000+
model$compile(
1001+
stanc_options = list(
1002+
canonicalize = "deprecations",
1003+
"filename-in-msg" = "model filename with spaces.stan"
1004+
),
1005+
force_recompile = TRUE,
1006+
dry_run = TRUE
1007+
)
1008+
1009+
expected <- c(
1010+
"--canonicalize=deprecations",
1011+
"--filename-in-msg=model filename with spaces.stan"
1012+
)
1013+
direct_options <- lapply(received_stancflags, function(x) {
1014+
grep("^--(canonicalize|filename-in-msg)=", x, value = TRUE)
1015+
})
1016+
expect_length(received_stancflags, 2)
1017+
expect_equal(direct_options, rep(list(expected), 2))
1018+
expect_equal(
1019+
grep("'", unlist(received_stancflags), fixed = TRUE, value = TRUE),
1020+
character()
1021+
)
1022+
})
1023+
1024+
test_that("compile() works with named stanc option values", {
1025+
stan_file <- write_stan_file(
1026+
"
1027+
functions {
1028+
real half(real x) {
1029+
return x / 2;
1030+
}
1031+
}
1032+
parameters {
1033+
real y;
1034+
}
1035+
model {
1036+
y ~ std_normal();
1037+
}
1038+
",
1039+
dir = withr::local_tempdir(),
1040+
basename = "issue1227.stan"
1041+
)
1042+
1043+
expect_call_compilation(
1044+
model <- cmdstan_model(
1045+
stan_file,
1046+
stanc_options = list(
1047+
canonicalize = "deprecations",
1048+
"filename-in-msg" = "model filename with spaces.stan"
1049+
)
1050+
)
1051+
)
1052+
})
1053+
9871054
test_that("compile() detects stan_opencl without case or partial matching", {
9881055
stan_file <- testing_stan_file("bernoulli")
9891056
model <- cmdstan_model(stan_file, compile = FALSE)

tests/testthat/test-utils.R

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,32 @@ test_that("cmdstan_diagnose works if bin/diagnose deleted file", {
135135
expect_output(delete_and_run(), "Checking sampler transitions treedepth")
136136
})
137137

138+
test_that("get_standalone_hpp() reports stanc failures", {
139+
model_dir <- withr::local_tempdir()
140+
stan_file <- file.path(model_dir, "model.stan")
141+
hpp_file <- file.path(model_dir, "model.hpp")
142+
writeLines("parameters { real y; } model { y ~ std_normal(); }", stan_file)
143+
writeLines("// partial output", hpp_file)
144+
local_mocked_bindings(
145+
wsl_compatible_run = function(...) {
146+
list(
147+
status = 124L,
148+
stdout = "",
149+
stderr = "stanc: invalid canonicalize value"
150+
)
151+
}
152+
)
153+
154+
expect_snapshot(
155+
error = TRUE,
156+
get_standalone_hpp(
157+
stan_file,
158+
"--canonicalize='deprecations'"
159+
)
160+
)
161+
expect_false(file.exists(hpp_file))
162+
})
163+
138164

139165
# misc --------------------------------------------------------------------
140166

0 commit comments

Comments
 (0)