Skip to content

stanc_options values are single-quoted for make but passed verbatim to stanc, breaking compilation #1227

Description

@jgabry

This bug description was generated with the help of Claude

Describe the bug

compile() builds stanc_built_options with single quotes around named option
values (R/model.R:719):

stanc_built_options <- c(stanc_built_options, paste0("--", option_name, "=", "'", stanc_options[[i]], "'"))

That quoting is correct for the STANCFLAGS += ... string handed to make, but the
same vector (stancflags_combined) is then passed straight to processx via
get_standalone_hpp() at R/model.R:728-731:

stancflags_standalone <- c("--standalone-functions", stanc_inc_paths, stancflags_combined)
self$functions$hpp_code <- get_standalone_hpp(temp_stan_file, stancflags_standalone)
private$model_methods_env_ <- new.env()
private$model_methods_env_$hpp_code_ <- get_standalone_hpp(temp_stan_file, c(stanc_inc_paths, stancflags_combined))

There is no shell in that path, so stanc receives the quotes as literal
characters and rejects the value. get_standalone_hpp() returns NULL
invisibly on failure, and the NULL blows up downstream.

This affects any named, non-logical stanc_option other than name (which is
already special-cased at R/model.R:716). Lines 728-731 run on every compile,
not only when compile_standalone = TRUE.

This is the same class of bug as #820 (fixed in #1226 for include paths), just
for option values rather than include paths.

To Reproduce

library(cmdstanr)

d <- tempdir()
f <- file.path(d, "m.stan")
writeLines(c(
  "functions { real half(real x) { return x/2; } }",
  "parameters { real y; }",
  "model { y ~ std_normal(); }"
), f)

mod <- cmdstan_model(f, stanc_options = list(canonicalize = "deprecations"))
#> Error in writeLines(private$model_methods_env_$hpp_code_, con = wsl_safe_path(private$hpp_file_,  :
#>   can only write character objects

The underlying stanc rejection, confirmed by invoking the binary directly:

stanc <- file.path(cmdstan_path(), "bin", "stanc")
processx::run(stanc, c(f, "--auto-format", "--canonicalize='deprecations'"),
              error_on_status = FALSE)$stderr
#> stanc: option --canonicalize: invalid element in list ('deprecations'): invalid ...

Passing the same option unquoted (--canonicalize=deprecations) succeeds.

Expected behavior

Compilation succeeds and stanc receives --canonicalize=deprecations. Quoting
should be applied only on the make/STANCFLAGS path, matching how include paths
are handled by the standalone_call argument of include_paths_stanc3_args().

Secondarily: a failure inside get_standalone_hpp() should surface as a real
error rather than a silent NULL that produces "can only write character
objects" several frames later.

*** Additional context
Found while reviewing #1226. Not introduced by that PR — present on master.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions