Skip to content

Improve error messages for missing required input arguments - #4389

Open
samuelbharti wants to merge 4 commits into
rstudio:mainfrom
samuelbharti:samuelbharti/review-shiny-issue-1423
Open

Improve error messages for missing required input arguments#4389
samuelbharti wants to merge 4 commits into
rstudio:mainfrom
samuelbharti:samuelbharti/review-shiny-issue-1423

Conversation

@samuelbharti

@samuelbharti samuelbharti commented May 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #1423.

Summary

Input constructors (textInput(), sliderInput(), selectInput(), actionButton(), etc.) currently surface cryptic downstream R errors like argument "label" is missing, with no default when a required argument is omitted, without naming the function or the argument that was actually missing.

This PR adds a small internal check_required() helper in R/input-utils.R that uses eval(call("missing", ...), envir = caller_env()) to detect unsupplied promises without forcing them, then raises a classed error via cli::cli_abort() that names both the calling function and the missing argument(s). The helper is wired into the top of every *Input() constructor plus actionButton() / actionLink() for arguments without defaults (inputId, label, plus per-function specifics such as choices, min/max/value, data).

The error carries class shiny_missing_arg_error so downstream code can catch it specifically. Explicit NULL and NA are treated as "supplied" so documented opt-outs like label = NULL continue to work. No public API changes; only the error wording changes for the previously-broken cases.

Scope

In scope: every standalone input constructor and actionButton() / actionLink() — 15 functions in total.

Out of scope (intentionally):

  • Argument type validation (e.g., rejecting inputId = c("a", "b")). The issue is about clearer missing-arg messages, not a general validator.
  • Positional-shift heuristics. sliderInput("forgot my id!", 1, 10, 5) will still report value as missing because we can't infer the user's intent.
  • The update*Input() family. Same problem exists there but the surface area is larger; tracked as a follow-up.

Follow-ups

  • Apply the same check_required() treatment to updateTextInput(), updateSliderInput(), updateSelectInput(), etc.
  • Consider exporting check_required() (or replacing with rlang::check_required()) if other parts of shiny adopt the pattern.

Verification

Before:

sliderInput("Whoops forgot my id!", 1, 10, 5)
#> Error in force(default) : argument "value" is missing, with no default
textInput("text")
#> Error in label %AND% tags$label(label, `for` = inputId) :
#>   argument "label" is missing, with no default

After:

sliderInput("Whoops forgot my id!", 1, 10, 5)
#> Error in `sliderInput()`:
#> ! `sliderInput()` is missing required argument: `value`.
textInput("text")
#> Error in `textInput()`:
#> ! `textInput()` is missing required argument: `label`.
textInput()
#> Error in `textInput()`:
#> ! `textInput()` is missing required arguments: `inputId` and `label`.

Test plan

  • tests/testthat/test-input-required-args.R adds 7 test_that() blocks (48 expectations) covering: the helper's own behavior, the new error class, behavior under do.call(), regex-based regression assertions, explicit-NULL opt-out, happy-path shiny.tag returns for all 15 patched constructors, and full-message snapshots.
  • devtools::test(filter = "input"): 231 PASS, 0 FAIL.
  • Full devtools::test() suite earlier: 2018 PASS, 0 FAIL (12 environmental skips, unrelated).
@samuelbharti
samuelbharti marked this pull request as ready for review May 28, 2026 18:35
Input constructors (textInput(), sliderInput(), selectInput(),
actionButton(), etc.) now report which function and which argument were
missing instead of surfacing a downstream R error like
'argument "label" is missing, with no default'.

Adds an internal check_required() helper in R/input-utils.R that uses
rlang::is_missing() against the caller's frame and cli::cli_abort() to
name both the function and the missing argument(s). The helper is wired
into the top of every *Input() constructor and actionButton() /
actionLink() for arguments without defaults.

Fixes rstudio#1423.
- Replace nested rlang call construction with eval(call("missing", ...))
  for readability; behavior unchanged.
- Classed error: cli_abort() now raises with class
  "shiny_missing_arg_error" so callers can catch it specifically.
- Test additions: verify the classed error, verify behavior under
  do.call(), and tighten happy-path assertions to confirm each
  constructor returns a shiny.tag.
Match the existing cadence for issue-reporter credits, e.g.
"(thanks @ismirsehregal, rstudio#4318)" on NEWS.md:177.
@samuelbharti
samuelbharti force-pushed the samuelbharti/review-shiny-issue-1423 branch from 1193f11 to 81af13b Compare May 28, 2026 18:37
Addresses code-review feedback:

- Fix wrong-function attribution: selectizeInput()/varSelectizeInput()
  delegate to selectInput()/varSelectInput(), so a missing required
  argument was reported as an error in the inner constructor. They now
  pass their own environment via a new internal `error_call` argument so
  the error names the function the user actually called. Adds tests for
  both wrappers.
- check_required(): separate the frame used to evaluate missing() from
  the call blamed in the error (`frame` vs `call`), enabling the above.
- Remove the base::call() shadowing on the `call` argument by building
  the missing() call with as.call(); capture argument names with
  rlang::ensyms() instead of enquos().
- Tighten happy-path tests to assert the exact "shiny.tag" class.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant