Skip to content

Refactor specify_*(): remove duplication, split into per-class files - #328

Merged
jonthegeek merged 2 commits into
mainfrom
fix-325-specify-breakdown
Aug 17, 2026
Merged

Refactor specify_*(): remove duplication, split into per-class files#328
jonthegeek merged 2 commits into
mainfrom
fix-325-specify-breakdown

Conversation

@jonthegeek

Copy link
Copy Markdown
Collaborator

Closes #325.

Part 1: abstraction

Each specify_*()/specify_*_scalar() function repeated the same 6-line loop to build factory_args from its non-missing arguments. Extracted this into a shared internal helper, .capture_factory_args(), which introspects the calling function's formals and frame to determine which arguments were explicitly supplied.

.specify_cls()'s own signature and behavior are unchanged, so its existing tests needed no modification beyond adding the #325 issue tag. Since specify_*() argument lists were kept fully explicit (not collapsed to ...), no user-facing/NEWS change is needed.

Part 2: file split

Split the class-specific sections of R/specify_cls.R into their own files (R/specify_chr.R, R/specify_date.R, R/specify_dttm.R, R/specify_dbl.R, R/specify_dur.R, R/specify_fct.R, R/specify_int.R, R/specify_lgl.R, R/specify_time.R), matching the existing per-class convention used by stabilize_*.R and to_*.R. R/specify_cls.R now holds only the shared internal helpers (.specify_cls(), .maybe_check_dupes(), .construct_stabilizer_symbol(), .construct_specification_fn(), the `!!` injection operator, and the new .capture_factory_args()).

Split tests/testthat/test-specify_cls.R into corresponding per-class test files. All moved/kept tests have #325 added to their issue tags per the issue's instructions.

Verification

  • devtools::test(reporter = "check"): 2425 passed, 0 failed.
  • devtools::check(error_on = "warning"): 0 errors, 0 warnings, 0 notes.
  • Confirmed 100% file-level coverage for all new/split R/specify_*.R files via covr::package_coverage().

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

🌐 PR pkgdown deployed: https://wranglezone.github.io/stbl/pr/328/dev

Generated by this GitHub Action. Updated: 2026-08-17T11:19:28Z

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

{qcthat} Report: User Acceptance

No issues are awaiting UAT.

This report was generated by this GitHub Actions job.

Last updated: 2026-08-17 11:19:53 UTC

Session Info

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

{qcthat} Reports

PR-Associated Issues

✅ A qcthat issue test matrix with 1 milestone, 1 issue, and 71 tests
└─█─Milestone: v0.5.0 (1 issue, 71 tests)
  └─📥─Task 325: chore: Refactor `specify_*()`
    ├─✅─specify_chr can build a regex checker (#147, #310, #325)
    ├─✅─specify_chr can enforce unique elements (#280, #325)
    ├─✅─specify_chr_scalar can build a regex checker (#147, #310, #325)
    ├─✅─specify_chr_scalar defaults to allow_null = FALSE (#197, #325)
    ├─✅─specify_chr_scalar defaults to allow_zero_length = FALSE (#197, #325)
    ├─✅─specify_character() exists (#164, #325)
    ├─✅─stabilize_character_scalar() exists (#164, #325)
    ├─✅─specify_chr can enforce allowed_values (#282, #325)
    ├─✅─.capture_factory_args only includes non-missing arguments (#325)
    ├─✅─specify_date can build a value checker (#104, #325)
    ├─✅─specify_date can enforce unique elements (#104, #325)
    ├─✅─specify_date_scalar can build a value checker (#104, #325)
    ├─✅─specify_date_scalar defaults to allow_null = FALSE (#104, #325)
    ├─✅─specify_date_scalar defaults to allow_zero_length = FALSE (#104, #325)
    ├─✅─specify_date can enforce allowed_values (#104, #325)
    ├─✅─specify_date() creates a working stabilizer (#104, #325)
    ├─✅─specify_date_scalar() creates a working scalar stabilizer (#104, #325)
    ├─✅─specify_dbl can build a value checker (#148, #325)
    ├─✅─specify_dbl can enforce unique elements (#280, #325)
    ├─✅─specify_dbl_scalar can build a value checker (#148, #325)
    ├─✅─specify_dbl_scalar defaults to allow_null = FALSE (#197, #325)
    ├─✅─specify_dbl_scalar defaults to allow_zero_length = FALSE (#197, #325)
    ├─✅─specify_double() exists (#164, #325)
    ├─✅─stabilize_double_scalar() exists (#164, #325)
    ├─✅─specify_dbl can enforce allowed_values (#282, #325)
    ├─✅─specify_dttm can build a value checker (#105, #325)
    ├─✅─specify_dttm can enforce unique elements (#105, #325)
    ├─✅─specify_dttm respects tz (#105, #325)
    ├─✅─specify_dttm_scalar can build a value checker (#105, #325)
    ├─✅─specify_dttm_scalar defaults to allow_null = FALSE (#105, #325)
    ├─✅─specify_dttm_scalar defaults to allow_zero_length = FALSE (#105, #325)
    ├─✅─specify_dttm can enforce allowed_values (#105, #325)
    ├─✅─specify_dttm() creates a working stabilizer (#105, #325)
    ├─✅─specify_dttm_scalar() creates a working scalar stabilizer (#105, #325)
    ├─✅─specify_dur can build a value checker (#295, #325)
    ├─✅─specify_dur can enforce unique elements (#295, #325)
    ├─✅─specify_dur_scalar can build a value checker (#295, #325)
    ├─✅─specify_dur_scalar defaults to allow_null = FALSE (#295, #325)
    ├─✅─specify_dur_scalar defaults to allow_zero_length = FALSE (#295, #325)
    ├─✅─specify_dur can enforce allowed_values (#295, #325)
    ├─✅─specify_dur() creates a working stabilizer (#295, #325)
    ├─✅─specify_dur_scalar() creates a working scalar stabilizer (#295, #325)
    ├─✅─specify_fct can build a level checker (#150, #325)
    ├─✅─specify_fct_scalar can build a level checker (#150, #325)
    ├─✅─specify_fct_scalar defaults to allow_null = FALSE (#197, #325)
    ├─✅─specify_fct_scalar defaults to allow_zero_length = FALSE (#197, #325)
    ├─✅─specify_factor() exists (#164, #325)
    ├─✅─stabilize_factor_scalar() exists (#164, #325)
    ├─✅─specify_int can build a value checker (#149, #325)
    ├─✅─specify_int can enforce unique elements (#280, #325)
    ├─✅─specify_int_scalar can build a value checker (#149, #325)
    ├─✅─specify_int_scalar defaults to allow_null = FALSE (#197, #325)
    ├─✅─specify_int_scalar defaults to allow_zero_length = FALSE (#197, #325)
    ├─✅─specify_integer() exists (#164, #325)
    ├─✅─stabilize_integer_scalar() exists (#164, #325)
    ├─✅─specify_int can enforce allowed_values (#282, #325)
    ├─✅─specify_lgl can build a checker (#151, #325)
    ├─✅─specify_lgl_scalar can build a value checker (#151, #325)
    ├─✅─specify_lgl_scalar defaults to allow_null = FALSE (#197, #325)
    ├─✅─specify_lgl_scalar defaults to allow_zero_length = FALSE (#197, #325)
    ├─✅─specify_logical() exists (#164, #325)
    ├─✅─stabilize_logical_scalar() exists (#164, #325)
    ├─✅─specify_lgl can enforce allowed_values (#282, #325)
    ├─✅─specify_time can build a value checker (#294, #325)
    ├─✅─specify_time can enforce unique elements (#294, #325)
    ├─✅─specify_time_scalar can build a value checker (#294, #325)
    ├─✅─specify_time_scalar defaults to allow_null = FALSE (#294, #325)
    ├─✅─specify_time_scalar defaults to allow_zero_length = FALSE (#294, #325)
    ├─✅─specify_time can enforce allowed_values (#294, #325)
    ├─✅─specify_time() creates a working stabilizer (#294, #325)
    └─✅─specify_time_scalar() creates a working scalar stabilizer (#294, #325)
# Issue state: 📥 = open, ☑️ = closed (completed), ⛔ = closed (won't fix)
# Test disposition: ✅ = passed, ❌ = failed, 🚫 = skipped
✅ All tests passed

🟢 All issues have at least one test


Completed Issues

✅ A qcthat issue test matrix with 5 milestones, 85 issues, and 934 tests
├─█─Milestone: v0.1 (17 issues, 93 tests)
│ ├─☑️─Feature 64: Explicitly error to_fct for list and data.frame
│ │ └─✅─to_fct() works for lists (#64, #273, #310)
│ ├─☑️─Feature 21: to_lgl()
│ │ ├─✅─to_lgl() works for lgls (#21)
│ │ ├─✅─to_lgl() works for NULL (#21)
│ │ ├─✅─to_lgl() respects allow_null (#21)
│ │ ├─✅─to_lgl() works for integers (#21)
│ │ ├─✅─to_lgl() works for doubles (#21)
│ │ ├─✅─to_lgl works for characters (#21)
│ │ ├─✅─to_lgl() errors for bad characters (#21, #310)
│ │ ├─✅─to_lgl works for factors (#21)
│ │ ├─✅─to_lgl errors for bad factors (#21, #310)
│ │ ├─✅─to_lgl() works for lists (#21, #273, #310)
│ │ └─✅─to_lgl() errors for other types (#21, #273, #310)
│ ├─☑️─Feature 14: coerce argument(s)
│ │ ├─✅─to_int() respects coerce_character (#14)
│ │ └─✅─to_int() respects coerce_factor (#14)
│ ├─☑️─Feature 12: Scalar
│ │ ├─✅─stabilize_arg_scalar() allows length-1 args through (#12)
│ │ ├─✅─stabilize_arg_scalar() errors for non-scalars (#12, #58)
│ │ ├─✅─stabilize_arg_scalar() respects allow_null (#12, #58)
│ │ ├─✅─stabilize_arg_scalar() errors on weird internal arg values (#12, #58)
│ │ ├─✅─stabilize_int_scalar() allows length-1 ints through (#12, #189)
│ │ ├─✅─stabilize_int_scalar() respects allow_null (#12, #189)
│ │ ├─✅─stabilize_int_scalar() errors on non-scalars (#12)
│ │ ├─✅─to_int_scalar() allows length-1 ints through (#12)
│ │ ├─✅─to_int_scalar() provides informative error messages (#12)
│ │ ├─✅─to_int_scalar() respects allow_null (#12, #189)
│ │ └─✅─to_int_scalar respects allow_zero_length (#12, #43, #45, #189)
│ ├─☑️─Feature 11: Rename then export .return_if_clear()
│ │ ├─✅─stabilize_arg() returns its inputs for default settings (#11)
│ │ ├─✅─stabilize_arg() complains about weird args (#11, #58, #99)
│ │ ├─✅─stabilize_arg() rejects NULLs when asked (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks NAs (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks size args (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks min_size (#11, #58, #99)
│ │ └─✅─stabilize_arg() checks max_size (#11, #58)
│ ├─☑️─Feature 6: to_int_simple() (or something)
│ │ ├─✅─stabilize_int() checks min_value (#2, #6, #176)
│ │ └─✅─to_int() works for ints (#2, #6)
│ ├─☑️─Feature 5: Implement allow_empty
│ │ └─✅─stabilize_int() checks max_value (#5, #176)
│ ├─☑️─Feature 4: Factors to int
│ │ ├─✅─to_int() works for factors (#4)
│ │ └─✅─to_int() errors informatively for bad factors (#4, #310)
│ ├─☑️─Feature 2: Implement to_int()
│ │ ├─✅─stabilize_int() checks min_value (#2, #6, #176)
│ │ ├─✅─to_int() works for ints (#2, #6)
│ │ ├─✅─to_int() works for NULL (#2)
│ │ ├─✅─to_int() respects allow_null (#2)
│ │ ├─✅─to_int() works for lgls (#2)
│ │ ├─✅─to_int() works for dbls (#2)
│ │ ├─✅─to_int() errors for dbls that would lose precision (#2, #217, #310)
│ │ ├─✅─to_int() works for chrs (#2)
│ │ ├─✅─to_int() errors informatively for bad chrs (#2, #310)
│ │ ├─✅─to_int() works for complexes (#2)
│ │ ├─✅─to_int() errors informatively for bad complexes (#2, #310)
│ │ ├─✅─to_int() works for lists (#2, #273, #310)
│ │ └─✅─to_int() errors properly for other types (#2)
│ ├─☑️─Feature 62: stabilize_fct
│ │ ├─✅─stabilize_fct() works (#62)
│ │ ├─✅─stabilize_fct() throws errors for bad levels (#62, #67)
│ │ ├─✅─stabilize_fct_scalar() works (#62, #189)
│ │ ├─✅─stabilize_fct_scalar() respects allow_null (#62, #189)
│ │ ├─✅─stabilize_fct_scalar() errors for non-scalars (#62)
│ │ ├─✅─to_fct() works for fcts (#62)
│ │ ├─✅─to_fct() deals with levels of fcts (#62)
│ │ ├─✅─to_fct() throws errors for bad levels (#62, #67, #177)
│ │ ├─✅─to_fct() works for chrs (#62)
│ │ ├─✅─to_fct() works for NULL (#62)
│ │ ├─✅─to_fct() respects allow_null (#62)
│ │ ├─✅─to_fct() errors for things that can't be coerced (#62, #273, #310)
│ │ ├─✅─to_fct() treats numbers as text (#62)
│ │ ├─✅─to_fct_scalar() allows length-1 fcts through (#62)
│ │ ├─✅─to_fct_scalar() provides informative error messages (#62)
│ │ └─✅─to_fct_scalar respects allow_zero_length (#62, #43, #45, #189)
│ ├─☑️─Task 58: Error classes
│ │ ├─✅─stabilize_arg() complains about weird args (#11, #58, #99)
│ │ ├─✅─stabilize_arg() rejects NULLs when asked (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks NAs (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks size args (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks min_size (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks max_size (#11, #58)
│ │ ├─✅─stabilize_arg_scalar() errors for non-scalars (#12, #58)
│ │ ├─✅─stabilize_arg_scalar() respects allow_null (#12, #58)
│ │ └─✅─stabilize_arg_scalar() errors on weird internal arg values (#12, #58)
│ ├─☑️─Feature 45: empty vs NULL
│ │ ├─✅─to_chr_scalar respects allow_zero_length (#22, #43, #45, #189)
│ │ ├─✅─to_dbl_scalar respects allow_zero_length (#23, #43, #45, #189)
│ │ ├─✅─to_fct_scalar respects allow_zero_length (#62, #43, #45, #189)
│ │ └─✅─to_int_scalar respects allow_zero_length (#12, #43, #45, #189)
│ ├─☑️─Feature 43: Empty input
│ │ ├─✅─to_chr_scalar respects allow_zero_length (#22, #43, #45, #189)
│ │ ├─✅─to_dbl_scalar respects allow_zero_length (#23, #43, #45, #189)
│ │ ├─✅─to_fct_scalar respects allow_zero_length (#62, #43, #45, #189)
│ │ └─✅─to_int_scalar respects allow_zero_length (#12, #43, #45, #189)
│ ├─☑️─Feature 32: to_lgl_scalar()
│ │ ├─✅─to_lgl_scalar() allows length-1 lgls through (#32, #189)
│ │ ├─✅─to_lgl_scalar() errors for non-scalars (#32)
│ │ ├─✅─to_lgl_scalar() errors for bad characters (#32, #310)
│ │ └─✅─to_lgl_scalar() respects allow_null (#32, #189)
│ ├─☑️─Feature 28: stabilize_lgl()
│ │ ├─✅─stabilize_lgl() works on happy path (#28)
│ │ ├─✅─stabilize_lgl() checks NAs (#28)
│ │ ├─✅─stabilize_lgl() checks min_size (#28)
│ │ ├─✅─stabilize_lgl() checks max_size (#28)
│ │ ├─✅─stabilize_lgl_scalar() allows length-1 lgls through (#28, #189)
│ │ ├─✅─stabilize_lgl_scalar() respects allow_null (#28, #189)
│ │ └─✅─stabilize_lgl_scalar() errors on non-scalars (#28)
│ ├─☑️─Feature 27: regex arg
│ │ ├─✅─stabilize_chr() works on happy path (#22, #27, #52)
│ │ ├─✅─stabilize_chr() errors for bad regex matches (#27, #52, #310)
│ │ └─✅─stabilize_chr works with NA and regex pattern (#27, #52)
│ └─☑️─Feature 22: *_chr*()
│   ├─✅─stabilize_chr() works on happy path (#22, #27, #52)
│   ├─✅─stabilize_chr_scalar() allows length-1 chrs through (#22, #189)
│   ├─✅─stabilize_chr_scalar() respects allow_null (#22, #189)
│   ├─✅─stabilize_chr_scalar() errors for non-scalars (#22)
│   ├─✅─to_chr() works for chrs (#22)
│   ├─✅─to_chr() works for NULL (#22)
│   ├─✅─to_chr() respects allow_null (#22)
│   ├─✅─to_chr() works for other things (#22)
│   ├─✅─to_chr() tries to flatten lists (#22)
│   ├─✅─to_chr() fails gracefully for weird cases (#22, #273, #310)
│   ├─✅─to_chr_scalar() allows length-1 chrs through (#22, #189)
│   ├─✅─to_chr_scalar() errors for non-scalars (#22)
│   ├─✅─to_chr_scalar() errors for uncoerceable types (#22, #273, #310)
│   ├─✅─to_chr_scalar() respects allow_null (#22, #189)
│   └─✅─to_chr_scalar respects allow_zero_length (#22, #43, #45, #189)
├─█─Milestone: v0.2 (13 issues, 124 tests)
│ ├─☑️─Task 129: Rename `to_null()` to `.to_null()`
│ │ ├─✅─.to_null() works on the happy path (#129)
│ │ ├─✅─.to_null() errors when NULL isn't allowed (#129)
│ │ ├─✅─.to_null() coerces anything to NULL (#129)
│ │ ├─✅─.to_null() errors for bad allow_null (#129, #310)
│ │ └─✅─.to_null() errors informatively for missing value (#129)
│ ├─☑️─Feature 67: to_fct: Enumerate options
│ │ ├─✅─stabilize_fct() throws errors for bad levels (#62, #67)
│ │ └─✅─to_fct() throws errors for bad levels (#62, #67, #177)
│ ├─☑️─Feature 52: regex error is useless
│ │ ├─✅─regex_must_match() works as expected (#52, #89)
│ │ ├─✅─regex_must_match() deals with characters for glue (#52, #89)
│ │ ├─✅─stabilize_chr() works on happy path (#22, #27, #52)
│ │ ├─✅─stabilize_chr() errors for bad regex matches (#27, #52, #310)
│ │ ├─✅─stabilize_chr() works with complex url regex (#52, #310)
│ │ ├─✅─stabilize_chr() allows for customized error messages (#52, #310)
│ │ ├─✅─stabilize_chr() works with regex that contains braces (#52, #310)
│ │ ├─✅─stabilize_chr works with NA and regex pattern (#27, #52)
│ │ └─✅─stabilize_chr_scalar() works with regex that contains braces (#52, #310)
│ ├─☑️─Feature 30: to_lgl.character(): "0" and "1"
│ │ ├─✅─are_lgl_ish() works for characters (#93, #30)
│ │ ├─✅─are_lgl_ish() works for factors (#93, #30)
│ │ └─✅─are_lgl_ish() works for lists (#93, #30)
│ ├─☑️─Feature 23: dbl
│ │ ├─✅─are_dbl_ish() works for dbls (#23)
│ │ ├─✅─are_dbl_ish() works for ints (#23)
│ │ ├─✅─are_dbl_ish() works for NULL (#23)
│ │ ├─✅─are_dbl_ish() works for logicals (#23)
│ │ ├─✅─are_dbl_ish() works for characters (#23)
│ │ ├─✅─are_dbl_ish() respects coerce_character (#23)
│ │ ├─✅─are_dbl_ish() works for factors (#23)
│ │ ├─✅─are_dbl_ish() respects coerce_factor (#23)
│ │ ├─✅─are_dbl_ish() works for complex (#23)
│ │ ├─✅─are_dbl_ish() works for lists (#23)
│ │ ├─✅─are_dbl_ish() returns FALSE for non-vectors (#23)
│ │ ├─✅─is_dbl_ish() works (#23)
│ │ ├─✅─.to_cls_from_fct() works (#23)
│ │ ├─✅─.to_num_from_complex() works (#23, #310)
│ │ ├─✅─stabilize_dbl() checks min_value (#23, #176)
│ │ ├─✅─stabilize_dbl() checks max_value (#23, #176)
│ │ ├─✅─stabilize_dbl_scalar() allows length-1 dbls through (#23, #189)
│ │ ├─✅─stabilize_dbl_scalar() respects allow_null (#23, #189)
│ │ ├─✅─stabilize_dbl_scalar() errors on non-scalars (#23)
│ │ ├─✅─to_dbl() works for dbls (#23)
│ │ ├─✅─to_dbl() works for ints (#23)
│ │ ├─✅─to_dbl() works for NULL (#23)
│ │ ├─✅─to_dbl() respects allow_null (#23)
│ │ ├─✅─to_dbl() works for lgls (#23)
│ │ ├─✅─to_dbl() works for chrs (#23)
│ │ ├─✅─to_dbl() respects coerce_character (#23)
│ │ ├─✅─to_dbl() errors informatively for bad chrs (#23, #310)
│ │ ├─✅─to_dbl() works for complexes (#23)
│ │ ├─✅─to_dbl() errors informatively for bad complexes (#23, #310)
│ │ ├─✅─to_dbl() works for factors (#23)
│ │ ├─✅─to_dbl() respects coerce_factor (#23)
│ │ ├─✅─to_dbl() errors informatively for bad factors (#23, #310)
│ │ ├─✅─to_dbl() errors properly for other types (#23)
│ │ ├─✅─to_dbl_scalar() allows length-1 dbls through (#23)
│ │ ├─✅─to_dbl_scalar() provides informative error messages (#23)
│ │ ├─✅─to_dbl_scalar() respects allow_null (#23, #189)
│ │ └─✅─to_dbl_scalar respects allow_zero_length (#23, #43, #45, #189)
│ ├─☑️─Bug 128: Ensure `are_*_ish()` and `to_*()` are consistent
│ │ ├─✅─are_chr_ish() works for lists and data.frames (#93, #128)
│ │ ├─✅─are_int_ish() works for lists (#93, #128)
│ │ └─✅─to_dbl() works for lists (#128, #273, #310)
│ ├─☑️─Task 99: Standardize error class checks in all test files
│ │ ├─✅─stabilize_arg() complains about weird args (#11, #58, #99)
│ │ ├─✅─stabilize_arg() rejects NULLs when asked (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks NAs (#11, #58, #99)
│ │ ├─✅─stabilize_arg() checks size args (#11, #58, #99)
│ │ └─✅─stabilize_arg() checks min_size (#11, #58, #99)
│ ├─☑️─Task 95: Standardize Error Messages
│ │ ├─✅─.stbl_abort() throws the expected error (#95)
│ │ ├─✅─.stop_cant_coerce() throws the expected error (#95)
│ │ ├─✅─.stop_cant_coerce() uses additional_msg when provided (#95)
│ │ ├─✅─.stop_must() throws the expected error (#95)
│ │ ├─✅─.stop_must() handles subclasses (#95)
│ │ ├─✅─.stop_must() uses additional_msg when provided (#95)
│ │ ├─✅─.define_main_msg() works (#95)
│ │ ├─✅─.stop_null() throws the expected error (#95)
│ │ ├─✅─.stop_null() passes dots (#95)
│ │ ├─✅─.stop_incompatible() throws the expected error (#95, #310)
│ │ ├─✅─.stop_incompatible() passes dots (#95, #310)
│ │ ├─✅─.check_na() works (#95)
│ │ ├─✅─.check_size() works (#95)
│ │ ├─✅─.check_scalar() works (#95)
│ │ ├─✅─.check_x_no_more_than_y() works (#95)
│ │ └─✅─object_type returns expected types (#95)
│ ├─☑️─Feature 93: `are_*_ish()` functions
│ │ ├─✅─are_chr_ish() returns TRUE for every element of a chr (#93)
│ │ ├─✅─are_chr_ish() works for NULL (#93)
│ │ ├─✅─are_chr_ish() returns TRUE for every element of other atomics (#93)
│ │ ├─✅─are_chr_ish() works for lists and data.frames (#93, #128)
│ │ ├─✅─are_chr_ish() returns unnamed vectors (#93)
│ │ ├─✅─are_chr_ish() returns FALSE for non-vectors (#93)
│ │ ├─✅─is_chr_ish() returns a single TRUE for coercible objects (#93)
│ │ ├─✅─is_chr_ish() works for NULL (#93)
│ │ ├─✅─is_chr_ish() returns FALSE for uncoercibles (#93)
│ │ ├─✅─are_fct_ish() is TRUE for atomics when levels is NULL (#93)
│ │ ├─✅─are_fct_ish() works for NULL (#93)
│ │ ├─✅─are_fct_ish() works when levels are provided (#93)
│ │ ├─✅─are_fct_ish() works with to_na (#93)
│ │ ├─✅─.are_not_fct_ish_chr() works (#93)
│ │ ├─✅─are_fct_ish() works for lists (#93)
│ │ ├─✅─are_fct_ish() returns FALSE for non-vectors (#93)
│ │ ├─✅─are_fct_ish() deals with factor-ish S3 objects (#93)
│ │ ├─✅─is_fct_ish() works (#93)
│ │ ├─✅─are_int_ish() works for ints (#93)
│ │ ├─✅─are_int_ish() works for NULL (#93)
│ │ ├─✅─are_int_ish() works for logicals (#93)
│ │ ├─✅─are_int_ish() works for doubles (#93, #217)
│ │ ├─✅─are_int_ish() works for characters (#93)
│ │ ├─✅─are_int_ish() respects coerce_character (#93)
│ │ ├─✅─are_int_ish() works for factors (#93)
│ │ ├─✅─are_int_ish() respects coerce_factor (#93)
│ │ ├─✅─are_int_ish() works for complex (#93)
│ │ ├─✅─are_int_ish() works for lists (#93, #128)
│ │ ├─✅─are_int_ish() returns FALSE for non-vectors (#93)
│ │ ├─✅─are_int_ish() returns FALSE for unhandled S3 objects (#93)
│ │ ├─✅─is_int_ish() works (#93)
│ │ ├─✅─are_lgl_ish() works for logicals (#93)
│ │ ├─✅─are_lgl_ish() works for NULL (#93)
│ │ ├─✅─are_lgl_ish() works for numerics (#93)
│ │ ├─✅─are_lgl_ish() works for characters (#93, #30)
│ │ ├─✅─are_lgl_ish() works for factors (#93, #30)
│ │ ├─✅─are_lgl_ish() works for lists (#93, #30)
│ │ ├─✅─are_lgl_ish() returns FALSE for non-vectors (#93)
│ │ ├─✅─are_lgl_ish() returns FALSE for unhandled S3 objects (#93)
│ │ ├─✅─is_lgl_ish() works (#93)
│ │ └─✅─.elements_are_cls_ish() works (#93)
│ ├─☑️─Feature 89: cli formatting of regex patterns in errors
│ │ ├─✅─regex_must_match() works as expected (#52, #89)
│ │ ├─✅─regex_must_match() deals with characters for glue (#52, #89)
│ │ ├─✅─regex_must_match() handles negation (#85, #89)
│ │ ├─✅─regex_must_not_match() works as expected (#85, #89)
│ │ └─✅─regex_must_not_match() doesn't freak out about pre-set negation (#85, #89)
│ ├─☑️─Feature 87: Support {stringr} pattern-modifier functions
│ │ ├─✅─stabilize_chr() works with stringr::fixed() (#87, #310)
│ │ ├─✅─stabilize_chr() works with stringr::coll() (#87, #310)
│ │ └─✅─stabilize_chr() works with stringr::regex() (#87, #310)
│ ├─☑️─Feature 86: Allow `stabilize_chr()` to accept multiple regex rules
│ │ └─✅─stabilize_chr() accepts multiple regex rules (#86, #85, #310)
│ └─☑️─Feature 85: regex_must_not_match
│   ├─✅─regex_must_match() handles negation (#85, #89)
│   ├─✅─regex_must_not_match() works as expected (#85, #89)
│   ├─✅─regex_must_not_match() doesn't freak out about pre-set negation (#85, #89)
│   ├─✅─stabilize_chr() accepts negated regex args (#85, #310)
│   └─✅─stabilize_chr() accepts multiple regex rules (#86, #85, #310)
├─█─Milestone: v0.3.0 (22 issues, 211 tests)
│ ├─☑️─Bug 204: bug: `stabilize_lst()` with nested list
│ │ ├─✅─specify_df() accepts columns named 'x_arg', 'call', 'x_class' (#204)
│ │ ├─✅─specify_lst() accepts elements named 'x_arg', 'call', 'x_class' (#204)
│ │ └─✅─stabilize_lst() with nested specify_lst() propagates errors correctly (#204, #310)
│ ├─☑️─Task 166: Make to_lst() the primary spelling
│ │ ├─✅─to_lst() works for lists (#157, #166)
│ │ └─✅─to_list() exists (#157, #166)
│ ├─☑️─Feature 164: Add spelled-out aliases for all functions
│ │ ├─✅─are_character_ish() exists (#164)
│ │ ├─✅─is_character_ish() exists (#164)
│ │ ├─✅─are_double_ish() exists (#164)
│ │ ├─✅─is_double_ish() exists (#164)
│ │ ├─✅─are_factor_ish() exists (#164)
│ │ ├─✅─is_factor_ish() exists (#164)
│ │ ├─✅─are_integer_ish() exists (#164)
│ │ ├─✅─is_integer_ish() exists (#164)
│ │ ├─✅─are_logical_ish() exists (#164)
│ │ ├─✅─is_logical_ish() exists (#164)
│ │ ├─✅─specify_character() exists (#164, #325)
│ │ ├─✅─stabilize_character_scalar() exists (#164, #325)
│ │ ├─✅─specify_double() exists (#164, #325)
│ │ ├─✅─stabilize_double_scalar() exists (#164, #325)
│ │ ├─✅─specify_factor() exists (#164, #325)
│ │ ├─✅─stabilize_factor_scalar() exists (#164, #325)
│ │ ├─✅─specify_integer() exists (#164, #325)
│ │ ├─✅─stabilize_integer_scalar() exists (#164, #325)
│ │ ├─✅─specify_logical() exists (#164, #325)
│ │ ├─✅─stabilize_logical_scalar() exists (#164, #325)
│ │ ├─✅─stabilize_character() exists (#164)
│ │ ├─✅─stabilize_character_scalar() exists (#164)
│ │ ├─✅─stabilize_double() exists (#164)
│ │ ├─✅─stabilize_double_scalar() exists (#164)
│ │ ├─✅─stabilize_factor() exists (#164)
│ │ ├─✅─stabilize_factor_scalar() exists (#164)
│ │ ├─✅─stabilize_integer() exists (#164)
│ │ ├─✅─stabilize_integer_scalar() exists (#164)
│ │ ├─✅─stabilize_logical() exists (#164)
│ │ ├─✅─stabilize_logical_scalar() exists (#164)
│ │ ├─✅─to_character() exists (#164)
│ │ ├─✅─to_character_scalar() exists (#164)
│ │ ├─✅─to_double() exists (#164)
│ │ ├─✅─to_double_scalar() exists (#164)
│ │ ├─✅─to_factor() exists (#164)
│ │ ├─✅─to_factor_scalar() exists (#164)
│ │ ├─✅─to_integer() exists (#164)
│ │ ├─✅─to_integer_scalar() exists (#164)
│ │ ├─✅─to_logical() exists (#164)
│ │ └─✅─to_logical_scalar() exists (#164)
│ ├─☑️─Feature 161: Show actual args in `specify_*()`
│ │ ├─✅─.specify_cls builds the expected function with at least one arg (#150, #161)
│ │ ├─✅─.specify_cls builds the expected function snapshot with at least one arg (#150, #161)
│ │ └─✅─The function built via .specify_cls errors informatively for duplicated args (#150, #153, #161)
│ ├─☑️─Feature 157: Implement `to_list()`
│ │ ├─✅─to_lst() works for lists (#157, #166)
│ │ ├─✅─to_lst() works for NULL (#157)
│ │ ├─✅─to_lst() respects allow_null (#157)
│ │ ├─✅─to_lst() works for character vectors (#157)
│ │ ├─✅─to_lst() errors by default for functions (#157)
│ │ ├─✅─to_lst() works for functions with coerce_function = TRUE (#157)
│ │ ├─✅─to_lst() errors informatively for primitives (#157)
│ │ └─✅─to_list() exists (#157, #166)
│ ├─☑️─Feature 153: Better handling of `...` in `specify_*()`
│ │ └─✅─The function built via .specify_cls errors informatively for duplicated args (#150, #153, #161)
│ ├─☑️─Feature 151: Implement `specify_lgl()`
│ │ ├─✅─specify_lgl can build a checker (#151, #325)
│ │ └─✅─specify_lgl_scalar can build a value checker (#151, #325)
│ ├─☑️─Feature 150: Implement `specify_fct()`
│ │ ├─✅─.specify_cls builds the expected function with no args (#150)
│ │ ├─✅─.specify_cls builds the expected function snapshot with no args (#150)
│ │ ├─✅─.specify_cls builds the expected function with at least one arg (#150, #161)
│ │ ├─✅─.specify_cls builds the expected function snapshot with at least one arg (#150, #161)
│ │ ├─✅─The function built via .specify_cls errors informatively for duplicated args (#150, #153, #161)
│ │ ├─✅─.specify_cls can build a scalar specifier (#150)
│ │ ├─✅─.specify_cls builds the expected scalar function snapshot (#150)
│ │ ├─✅─specify_fct can build a level checker (#150, #325)
│ │ └─✅─specify_fct_scalar can build a level checker (#150, #325)
│ ├─☑️─Feature 149: Implement `specify_int()`
│ │ ├─✅─specify_int can build a value checker (#149, #325)
│ │ └─✅─specify_int_scalar can build a value checker (#149, #325)
│ ├─☑️─Feature 148: Implement `specify_dbl()`
│ │ ├─✅─specify_dbl can build a value checker (#148, #325)
│ │ └─✅─specify_dbl_scalar can build a value checker (#148, #325)
│ ├─☑️─Feature 147: Implement `specify_chr()`
│ │ ├─✅─specify_chr can build a regex checker (#147, #310, #325)
│ │ └─✅─specify_chr_scalar can build a regex checker (#147, #310, #325)
│ ├─☑️─Feature 203: feat: better `to_df()`
│ │ ├─✅─.check_all_named() works (#203)
│ │ ├─✅─.check_not_jagged() works (#203)
│ │ ├─✅─to_df() errors for an unnamed list (#203)
│ │ ├─✅─to_df() coerces named vector types to a data frame (#203)
│ │ └─✅─to_df() errors for inline vector expressions (#203)
│ ├─☑️─Feature 142: Implement `stabilize_df()`
│ │ ├─✅─specify_df() creates a working validator (#142)
│ │ ├─✅─specify_df() errors when required column is missing (#142)
│ │ ├─✅─specify_df() passes through .min_rows, .max_rows (#142)
│ │ ├─✅─specify_df() passes through .col_names (#142)
│ │ ├─✅─specify_df() allows additional specs via ... (#142)
│ │ ├─✅─specify_data_frame() exists (#142)
│ │ ├─✅─stabilize_df() returns NULL for NULL input by default (#142)
│ │ ├─✅─stabilize_df() respects .allow_null (#142)
│ │ ├─✅─stabilize_df() errors for non-coercible input (#142)
│ │ ├─✅─stabilize_df() coerces a named list to a data frame (#142)
│ │ ├─✅─stabilize_df() returns a valid data frame unchanged (#142)
│ │ ├─✅─stabilize_df() coerces compatible column types (#142)
│ │ ├─✅─stabilize_df() errors when required column is missing (#142)
│ │ ├─✅─stabilize_df() errors informatively when column fails validation (#142, #310)
│ │ ├─✅─stabilize_df() errors on extra columns by default (#142)
│ │ ├─✅─stabilize_df() allows extra columns with .extra_cols (#142)
│ │ ├─✅─stabilize_df() validates extra columns with .extra_cols (#142, #310)
│ │ ├─✅─stabilize_df() enforces .min_rows (#142)
│ │ ├─✅─stabilize_df() enforces .min_rows (snapshot) (#142)
│ │ ├─✅─stabilize_df() enforces .max_rows (#142)
│ │ ├─✅─stabilize_df() enforces .max_rows (snapshot) (#142)
│ │ ├─✅─stabilize_df() passes with valid row counts (#142)
│ │ ├─✅─stabilize_df() enforces .col_names (#142)
│ │ ├─✅─stabilize_df() enforces .col_names (snapshot) (#142)
│ │ ├─✅─stabilize_df() allows .col_names alongside ... specs (#142)
│ │ ├─✅─stabilize_df() preserves data frame class after column coercion (#142)
│ │ ├─✅─stabilize_df() works with no column specs and .extra_cols (#142)
│ │ ├─✅─stabilize_df() with unnamed specs errors informatively (#142)
│ │ ├─✅─stabilise_df() exists (#142)
│ │ ├─✅─stabilize_data_frame() exists (#142)
│ │ └─✅─stabilise_data_frame() exists (#142)
│ ├─☑️─Feature 136: Export error functions
│ │ ├─✅─.compile_pkg_condition_classes() compiles condition class chains (#136)
│ │ ├─✅─.compile_pkg_error_classes() compiles error class chains (#136)
│ │ ├─✅─pkg_abort() throws the expected error (#136)
│ │ ├─✅─pkg_abort() uses parent when provided (#136)
│ │ ├─✅─pkg_abort() passes dots to cli_abort() (#136)
│ │ ├─✅─pkg_abort() uses message_env when provided (#136)
│ │ └─✅─expect_pkg_error_classes() tests expressions for classes (#136)
│ ├─☑️─Feature 110: Implement `stabilize_lst()`
│ │ ├─✅─assert_present() returns any non-NULL value unchanged (#110, #299)
│ │ ├─✅─assert_present() errors for NULL (#110, #299)
│ │ ├─✅─assert_present() works as an element spec in stabilize_lst() (#110, #299)
│ │ ├─✅─specify_lst() creates a working specifier (#110)
│ │ ├─✅─specify_lst() respects pre-configured element specs (#110, #310)
│ │ ├─✅─specify_lst() supports element specs named 'x' and 'y' (#110)
│ │ ├─✅─specify_lst() passes through additional element specs from ... (#110)
│ │ ├─✅─specify_lst() respects .min_size (#110)
│ │ ├─✅─specify_lst() respects .allow_null (#110)
│ │ ├─✅─specify_list() exists (#110)
│ │ ├─✅─stabilize_lst() returns NULL for NULL input by default (#110)
│ │ ├─✅─stabilize_lst() respects .allow_null (#110)
│ │ ├─✅─stabilize_lst() returns a valid list unchanged (#110)
│ │ ├─✅─stabilize_lst() validates required named elements (#110)
│ │ ├─✅─stabilize_lst() errors when required named element is missing (#110)
│ │ ├─✅─stabilize_lst() errors informatively when element fails validation (#110, #310)
│ │ ├─✅─stabilize_lst() errors on extra named elements by default (#110)
│ │ ├─✅─stabilize_lst() validates extra named elements with .named (#110, #310)
│ │ ├─✅─stabilize_lst() errors on unnamed elements by default (#110)
│ │ ├─✅─stabilize_lst() validates unnamed elements with .unnamed (#110, #310)
│ │ ├─✅─stabilize_lst() handles mixed named/unnamed lists (#110)
│ │ ├─✅─stabilize_lst() enforces .min_size (#110)
│ │ ├─✅─stabilize_lst() enforces .max_size (#110)
│ │ ├─✅─stabilize_lst() validates nested lists (#110)
│ │ ├─✅─stabilize_lst() with unnamed specs errors informatively (#110)
│ │ ├─✅─.check_duplicate_names(): errors on duplicate names by default (#110)
│ │ ├─✅─.check_duplicate_names(): allows duplicates with .allow_duplicate_names = TRUE (#110)
│ │ ├─✅─.check_duplicate_names(): reports all duplicate name groups (#110)
│ │ ├─✅─.check_duplicate_names(): unnamed elements do not count as duplicates (#110)
│ │ ├─✅─stabilize_list() exists (#110)
│ │ ├─✅─stabilise_lst() exists (#110)
│ │ └─✅─stabilise_list() exists (#110)
│ ├─☑️─Feature 201: feat: `to_df()`
│ │ ├─✅─to_df() returns a data frame unchanged (#201)
│ │ ├─✅─to_df() returns NULL for NULL input by default (#201)
│ │ ├─✅─to_df() respects allow_null (#201)
│ │ ├─✅─to_df() coerces a named list to a data frame (#201)
│ │ ├─✅─to_df() coerces a list of equal-length vectors to a data frame (#201)
│ │ ├─✅─to_df() errors for a list with incompatible column lengths (#201)
│ │ ├─✅─to_df() errors for non-coercible types (#201)
│ │ ├─✅─to_df.default() errors for non-coercible types (#201)
│ │ └─✅─to_data_frame() exists (#201)
│ ├─☑️─Bug 197: Update `specify_*_scalar()` defaults to match `stabilize_*_scalar()`
│ │ ├─✅─specify_chr_scalar defaults to allow_null = FALSE (#197, #325)
│ │ ├─✅─specify_chr_scalar defaults to allow_zero_length = FALSE (#197, #325)
│ │ ├─✅─specify_dbl_scalar defaults to allow_null = FALSE (#197, #325)
│ │ ├─✅─specify_dbl_scalar defaults to allow_zero_length = FALSE (#197, #325)
│ │ ├─✅─specify_fct_scalar defaults to allow_null = FALSE (#197, #325)
│ │ ├─✅─specify_fct_scalar defaults to allow_zero_length = FALSE (#197, #325)
│ │ ├─✅─specify_int_scalar defaults to allow_null = FALSE (#197, #325)
│ │ ├─✅─specify_int_scalar defaults to allow_zero_length = FALSE (#197, #325)
│ │ ├─✅─specify_lgl_scalar defaults to allow_null = FALSE (#197, #325)
│ │ └─✅─specify_lgl_scalar defaults to allow_zero_length = FALSE (#197, #325)
│ ├─☑️─Issue 189: feat: `to_*_scalar()` default arguments
│ │ ├─✅─stabilize_chr_scalar() allows length-1 chrs through (#22, #189)
│ │ ├─✅─stabilize_chr_scalar() respects allow_null (#22, #189)
│ │ ├─✅─stabilize_dbl_scalar() allows length-1 dbls through (#23, #189)
│ │ ├─✅─stabilize_dbl_scalar() respects allow_null (#23, #189)
│ │ ├─✅─stabilize_fct_scalar() works (#62, #189)
│ │ ├─✅─stabilize_fct_scalar() respects allow_null (#62, #189)
│ │ ├─✅─stabilize_int_scalar() allows length-1 ints through (#12, #189)
│ │ ├─✅─stabilize_int_scalar() respects allow_null (#12, #189)
│ │ ├─✅─stabilize_lgl_scalar() allows length-1 lgls through (#28, #189)
│ │ ├─✅─stabilize_lgl_scalar() respects allow_null (#28, #189)
│ │ ├─✅─to_chr_scalar() allows length-1 chrs through (#22, #189)
│ │ ├─✅─to_chr_scalar() respects allow_null (#22, #189)
│ │ ├─✅─to_chr_scalar respects allow_zero_length (#22, #43, #45, #189)
│ │ ├─✅─to_dbl_scalar() respects allow_null (#23, #189)
│ │ ├─✅─to_dbl_scalar respects allow_zero_length (#23, #43, #45, #189)
│ │ ├─✅─to_fct_scalar respects allow_zero_length (#62, #43, #45, #189)
│ │ ├─✅─to_int_scalar() respects allow_null (#12, #189)
│ │ ├─✅─to_int_scalar respects allow_zero_length (#12, #43, #45, #189)
│ │ ├─✅─to_lgl_scalar() allows length-1 lgls through (#32, #189)
│ │ └─✅─to_lgl_scalar() respects allow_null (#32, #189)
│ ├─☑️─Feature 188: feat: expect_pkg_error_snapshot()
│ │ ├─✅─expect_pkg_error_snapshot() snapshots error class and message (#188, #301)
│ │ ├─✅─expect_pkg_error_snapshot() works with multiple class components (#188, #301)
│ │ ├─✅─expect_pkg_error_snapshot() works from an env without stbl attached (#188, #301)
│ │ └─✅─expect_pkg_error_classes() works from an env without stbl attached (#188)
│ ├─☑️─Feature 177: Don't mention levels/to_na in factor message?
│ │ └─✅─to_fct() throws errors for bad levels (#62, #67, #177)
│ ├─☑️─Feature 176: Better error messages
│ │ ├─✅─stabilize_dbl() checks min_value (#23, #176)
│ │ ├─✅─stabilize_dbl() checks max_value (#23, #176)
│ │ ├─✅─stabilize_int() checks min_value (#2, #6, #176)
│ │ └─✅─stabilize_int() checks max_value (#5, #176)
│ └─☑️─Feature 167: Add stabilise_*() aliases
│   ├─✅─stabilise_chr() exists (#167)
│   ├─✅─stabilise_character() exists (#167)
│   ├─✅─stabilise_chr_scalar() exists (#167)
│   ├─✅─stabilise_character_scalar() exists (#167)
│   ├─✅─stabilise_dbl() exists (#167)
│   ├─✅─stabilise_double() exists (#167)
│   ├─✅─stabilise_dbl_scalar() exists (#167)
│   ├─✅─stabilise_double_scalar() exists (#167)
│   ├─✅─stabilise_fct() exists (#167)
│   ├─✅─stabilise_factor() exists (#167)
│   ├─✅─stabilise_fct_scalar() exists (#167)
│   ├─✅─stabilise_factor_scalar() exists (#167)
│   ├─✅─stabilise_int() exists (#167)
│   ├─✅─stabilise_integer() exists (#167)
│   ├─✅─stabilise_int_scalar() exists (#167)
│   ├─✅─stabilise_integer_scalar() exists (#167)
│   ├─✅─stabilise_lgl() exists (#167)
│   ├─✅─stabilise_logical() exists (#167)
│   ├─✅─stabilise_lgl_scalar() exists (#167)
│   └─✅─stabilise_logical_scalar() exists (#167)
├─█─Milestone: v0.4.0 (18 issues, 262 tests)
│ ├─☑️─Bug 253: `expect_pkg_error_snapshot()` vs coverage
│ │ ├─✅─.is_covr_count_call() identifies covr counter calls (#253)
│ │ └─✅─.strip_covr_from_expr() removes covr counter wrappers (#253)
│ ├─☑️─Task 226: chore: complete C optimization for are_*_ish() and to_*() methods
│ │ ├─✅─.dbl_to_lgl() converts 0 to FALSE with valid TRUE (#226, #237)
│ │ ├─✅─.dbl_to_lgl() converts non-zero to TRUE with valid TRUE (#226, #237)
│ │ ├─✅─.dbl_to_lgl() passes NA and NaN through; valid always TRUE (#226, #237)
│ │ ├─✅─.dbl_to_lgl() handles integer input; valid always TRUE (#226, #237)
│ │ ├─✅─.int_to_dbl() converts integers to doubles; valid always TRUE (#226, #237)
│ │ ├─✅─.int_to_dbl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.int_are_dblish() returns all TRUE (#226, #237)
│ │ ├─✅─.lgl_to_dbl() converts TRUE to 1.0; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_dbl() converts FALSE to 0.0; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_dbl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_are_dblish() returns all TRUE (#226, #237)
│ │ ├─✅─.lgl_to_int() converts TRUE to 1L; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_int() converts FALSE to 0L; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_int() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.cpx_to_dbl() extracts real part when Im is zero (#226, #237)
│ │ ├─✅─.cpx_to_dbl() passes NA complex through as NA_real; valid TRUE (#226, #237)
│ │ ├─✅─.cpx_to_dbl() returns real part and valid FALSE for non-zero Im (#226, #237)
│ │ ├─✅─.cpx_to_int() converts whole-number complex with Im 0 (#226, #237)
│ │ ├─✅─.cpx_to_int() passes NA complex through; flags FALSE (#226, #237)
│ │ ├─✅─.cpx_to_int() marks non-zero Im as non_number (#226, #237)
│ │ ├─✅─.cpx_to_int() marks fractional real part as bad_precision (#226, #237)
│ │ ├─✅─.fct_to_dbl() converts numeric factor levels to doubles (#226, #237)
│ │ ├─✅─.fct_to_dbl() passes NA factor element through; valid TRUE (#226, #237)
│ │ ├─✅─.fct_to_dbl() marks non-numeric levels as not valid (#226, #237)
│ │ ├─✅─.fct_to_int() converts whole-number factor levels to integers (#226, #237)
│ │ ├─✅─.fct_to_int() passes NA factor element through; flags FALSE (#226, #237)
│ │ ├─✅─.fct_to_int() marks non-integer levels correctly (#226, #237)
│ │ ├─✅─.fct_to_lgl() converts TRUE/FALSE levels (#226, #237)
│ │ ├─✅─.fct_to_lgl() passes NA factor element through; valid TRUE (#226, #237)
│ │ ├─✅─.fct_to_lgl() marks non-logical levels as not valid (#226, #237)
│ │ ├─✅─.chr_are_fctish() returns all TRUE when levels is NULL (#226, #237)
│ │ ├─✅─.chr_are_fctish() checks membership in levels (#226, #237)
│ │ ├─✅─.chr_are_fctish() treats NA as fctish regardless of levels (#226, #237)
│ │ ├─✅─.chr_are_fctish() treats to_na values as fctish (#226, #237)
│ │ ├─✅─.fct_are_fctish() returns all TRUE when levels is NULL (#226, #237)
│ │ ├─✅─.fct_are_fctish() checks factor level membership in target levels (#226, #237)
│ │ ├─✅─.fct_are_fctish() treats NA as fctish regardless of levels (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts a flat list of numeric/lgl/int scalars (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts numeric character strings (#226, #237)
│ │ ├─✅─.lst_to_dbl() marks non-parseable character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts complex with Im == 0 (#226, #237)
│ │ ├─✅─.lst_to_dbl() marks complex with Im != 0 as not valid (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts factor elements via their level strings (#226, #237)
│ │ ├─✅─.lst_to_dbl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_dbl() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_dbl() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() converts a flat list of integer/logical scalars (#226, #237)
│ │ ├─✅─.lst_to_int() converts whole-number doubles (#226, #237)
│ │ ├─✅─.lst_to_int() converts integer-valued character strings (#226, #237)
│ │ ├─✅─.lst_to_int() marks fractional character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() marks non-number character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() converts complex with Im == 0 and whole Re (#226, #237)
│ │ ├─✅─.lst_to_int() marks complex with Im != 0 as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() converts factor elements via their level strings (#226, #237)
│ │ ├─✅─.lst_to_int() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_int() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_int() marks fractional doubles as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() marks non-coercible single-element as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_lgl() converts a flat list of lgl/int/dbl scalars (#226, #237)
│ │ ├─✅─.lst_to_lgl() converts lgl-ish character strings (#226, #237)
│ │ ├─✅─.lst_to_lgl() marks non-lgl character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_lgl() converts factor elements via their level strings (#226, #237)
│ │ ├─✅─.lst_to_lgl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_lgl() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_lgl() marks non-coercible single-element as not valid (#226, #237)
│ │ ├─✅─.lst_to_lgl() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_chr() passes character scalars through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_chr() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_chr() converts non-character atomic scalars to strings (#226, #237, #239)
│ │ ├─✅─.lst_to_chr() converts factor elements via their level labels (#226, #237, #239)
│ │ ├─✅─.lst_to_chr() marks non-character single-element vectors as not valid (#226, #237)
│ │ ├─✅─.lst_to_chr() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_fct() passes character scalars through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_fct() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_fct() marks non-character scalars as not valid (#226, #237)
│ │ ├─✅─.lst_to_fct() marks factor elements as valid (#226, #237)
│ │ ├─✅─.lst_to_fct() marks non-character/factor single-element vectors as not valid (#226, #237)
│ │ └─✅─.lst_to_fct() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ ├─☑️─Issue 221: C optimization: .are_not_dbl_ish_chr() — single-pass character-to-double failure detector
│ │ ├─✅─.chr_to_dbl() converts numeric strings (#221, #237)
│ │ ├─✅─.chr_to_dbl() converts Inf strings (#221, #237)
│ │ ├─✅─.chr_to_dbl() passes NA through as valid (#221, #237)
│ │ └─✅─.chr_to_dbl() marks invalid strings as not valid (#221, #237)
│ ├─☑️─Task 220: C optimization: .find_failures() / %&&% — inline min/max range checks
│ │ ├─✅─.check_min_dbl() returns NULL when all values pass (#220)
│ │ ├─✅─.check_min_dbl() returns failure indices for values below min (#220)
│ │ ├─✅─.check_min_dbl() treats NA as passing (#220)
│ │ ├─✅─.check_min_dbl() handles integer input (#220)
│ │ ├─✅─.check_max_dbl() returns NULL when all values pass (#220)
│ │ ├─✅─.check_max_dbl() returns failure indices for values above max (#220)
│ │ ├─✅─.check_max_dbl() treats NA as passing (#220)
│ │ └─✅─.check_max_dbl() handles integer input (#220)
│ ├─☑️─Issue 219: C optimization: are_int_ish.character() / to_int.character() — single-pass C checker
│ │ ├─✅─.chr_to_int() converts integer strings (#219, #237)
│ │ ├─✅─.chr_to_int() converts whole-number double strings (#219, #237)
│ │ ├─✅─.chr_to_int() passes NA through (#219, #237)
│ │ ├─✅─.chr_to_int() marks fractional strings as bad_precision (#219, #237)
│ │ ├─✅─.chr_to_int() marks non-number strings as non_number (#219, #237)
│ │ └─✅─.chr_to_int() marks Inf strings as bad_precision (#219, #237)
│ ├─☑️─Feature 218: C optimization: are_lgl_ish.character() / to_lgl.character() — eliminate duplicate toupper() call
│ │ ├─✅─.chr_to_lgl() converts TRUE/T strings to TRUE (#218, #237)
│ │ ├─✅─.chr_to_lgl() converts FALSE/F strings to FALSE (#218, #237)
│ │ ├─✅─.chr_to_lgl() passes NA through as valid (#218, #237)
│ │ ├─✅─.chr_to_lgl() converts numeric strings (#218, #237)
│ │ ├─✅─.chr_to_lgl() marks invalid strings as not valid (#218, #237)
│ │ └─✅─.dbl_are_lglish() returns all TRUE (#218, #237)
│ ├─☑️─Issue 217: C optimization: are_int_ish.double() — replace multi-vector arithmetic with single-pass C loop
│ │ ├─✅─are_int_ish() works for doubles (#93, #217)
│ │ ├─✅─.dbl_to_int() converts integer-valued doubles (#217, #237)
│ │ ├─✅─.dbl_to_int() passes NA and NaN through as not bad_precision (#217, #237)
│ │ ├─✅─.dbl_to_int() marks fractional doubles as bad_precision (#217, #237)
│ │ ├─✅─.dbl_to_int() marks Inf and out-of-range values as bad_precision (#217, #237)
│ │ └─✅─to_int() errors for dbls that would lose precision (#2, #217, #310)
│ ├─☑️─Feature 213: `pkg_warn()` and `pkg_inform()`
│ │ ├─✅─.stbl_warn() throws the expected warning (#213)
│ │ ├─✅─.stbl_inform() throws the expected message (#213)
│ │ ├─✅─.compile_pkg_message_classes() compiles message class chains (#213)
│ │ ├─✅─pkg_inform() signals the expected message (#213)
│ │ ├─✅─pkg_inform() uses parent when provided (#213)
│ │ ├─✅─pkg_inform() uses message_env when provided (#213)
│ │ ├─✅─expect_pkg_message_classes() tests expressions for classes (#213)
│ │ ├─✅─expect_pkg_message_snapshot() snapshots message class and message (#213, #301)
│ │ ├─✅─expect_pkg_message_snapshot() works with multiple class components (#213, #301)
│ │ ├─✅─expect_pkg_message_snapshot() works from an env without stbl attached (#213, #301)
│ │ ├─✅─expect_pkg_message_classes() works from an env without stbl attached (#213)
│ │ ├─✅─.compile_pkg_warning_classes() compiles warning class chains (#213)
│ │ ├─✅─pkg_warn() signals the expected warning (#213)
│ │ ├─✅─pkg_warn() uses parent when provided (#213)
│ │ ├─✅─pkg_warn() passes dots to cli_warn() (#213)
│ │ ├─✅─pkg_warn() uses message_env when provided (#213)
│ │ ├─✅─expect_pkg_warning_classes() tests expressions for classes (#213)
│ │ ├─✅─expect_pkg_warning_snapshot() snapshots warning class and message (#213, #301)
│ │ ├─✅─expect_pkg_warning_snapshot() works with multiple class components (#213, #301)
│ │ ├─✅─expect_pkg_warning_snapshot() works from an env without stbl attached (#213, #301)
│ │ ├─✅─expect_pkg_warning_classes() works from an env without stbl attached (#213)
│ │ ├─✅─.compile_dash() pastes with a dash separator (#213)
│ │ ├─✅─.collapse_dash() collapses a vector with dashes (#213)
│ │ └─✅─.compile_pkg_condition_classes() compiles condition class chains (#213)
│ ├─☑️─Feature 200: feat: dots in `to_lst()` and `to_df()`
│ │ ├─✅─to_df() rejects unused dots for methods that ignore them (#200)
│ │ └─✅─to_lst() rejects unused dots for methods that ignore them (#200)
│ ├─☑️─Feature 182: Implement `to()`
│ │ ├─✅─to() returns logical unchanged (#182)
│ │ ├─✅─to() returns integer unchanged (#182)
│ │ ├─✅─to() returns double unchanged (#182)
│ │ ├─✅─to() returns character unchanged (#182)
│ │ ├─✅─to() passes NULL through unchanged (#182)
│ │ ├─✅─to() converts integer to logical (#182)
│ │ ├─✅─to() converts double to logical (#182)
│ │ ├─✅─to() converts character to logical (#182)
│ │ ├─✅─to() errors for incompatible character -> logical (#182)
│ │ ├─✅─to() converts factor to logical (#182)
│ │ ├─✅─to() errors for incompatible factor -> logical (#182)
│ │ ├─✅─to() converts list to logical (#182)
│ │ ├─✅─to() converts logical to integer (#182)
│ │ ├─✅─to() converts double to integer (#182)
│ │ ├─✅─to() errors for double -> integer with precision loss (#182)
│ │ ├─✅─to() converts character to integer (#182)
│ │ ├─✅─to() errors for incompatible character -> integer (#182)
│ │ ├─✅─to() converts factor to integer (#182)
│ │ ├─✅─to() converts list to integer (#182)
│ │ ├─✅─to() converts integer to double (#182)
│ │ ├─✅─to() converts logical to double (#182)
│ │ ├─✅─to() converts character to double (#182)
│ │ ├─✅─to() errors for incompatible character -> double (#182)
│ │ ├─✅─to() converts factor to double (#182)
│ │ ├─✅─to() converts list to double (#182)
│ │ ├─✅─to() converts integer to character (#182)
│ │ ├─✅─to() converts double to character (#182)
│ │ ├─✅─to() converts logical to character (#182)
│ │ ├─✅─to() converts factor to character (#182)
│ │ ├─✅─to() converts list to character (#182)
│ │ ├─✅─to() converts character to factor using .to levels (#182)
│ │ ├─✅─to() converts integer to factor using .to levels (#182)
│ │ ├─✅─to() passes levels through from factor .to (#182)
│ │ ├─✅─to() respects explicit levels argument in to.factor (#182)
│ │ ├─✅─to() respects to_na argument in to.factor (#182)
│ │ ├─✅─to() converts to list (#182)
│ │ ├─✅─to() returns NULL by default when x is NULL (#182)
│ │ ├─✅─to() errors when x is NULL and allow_null = FALSE (#182)
│ │ ├─✅─to() passes data frame through unchanged with data frame target (#182)
│ │ ├─✅─to() converts list to data frame (#182)
│ │ ├─✅─to() converts vector to data frame (#182)
│ │ ├─✅─to() returns NULL when .to is NULL (#182)
│ │ ├─✅─to() errors when .to is NULL and allow_null = FALSE (#182)
│ │ ├─✅─to() respects coerce_character = FALSE for integer target (#182)
│ │ ├─✅─to() respects coerce_character = FALSE for double target (#182)
│ │ └─✅─to() errors for unsupported target types (#182)
│ ├─☑️─Feature 251: `to_chr.function()`
│ │ ├─✅─to_chr() converts named functions (#251)
│ │ └─✅─to_chr() errors for anonymous functions (#251)
│ ├─☑️─Feature 250: `to_fn()`
│ │ ├─✅─is_fn_ish() returns TRUE for functions (#250)
│ │ ├─✅─is_fn_ish() returns TRUE for formulas (#250)
│ │ ├─✅─is_fn_ish() returns TRUE for length-1 character strings (#250)
│ │ ├─✅─is_fn_ish() returns FALSE for NULL (#250)
│ │ ├─✅─is_fn_ish() returns FALSE for non-character vectors (#250)
│ │ ├─✅─is_fn_ish() returns FALSE for length != 1 character (#250)
│ │ ├─✅─is_fn_ish() returns FALSE with bad ':' usage (#250)
│ │ ├─✅─is_function_ish() exists (#250)
│ │ ├─✅─are_fn_ish() returns TRUE for functions (#250)
│ │ ├─✅─are_fn_ish() returns TRUE for formulas (#250)
│ │ ├─✅─are_fn_ish() does element-wise check for character (#250)
│ │ ├─✅─are_fn_ish() returns logical(0) for NULL (#250)
│ │ ├─✅─are_fn_ish() returns FALSE for non-fn-ish types (#250)
│ │ ├─✅─are_function_ish() is a synonym of are_fn_ish() (#250)
│ │ ├─✅─to_fn() works for functions (#250)
│ │ ├─✅─to_fn() works for lambda functions (#250)
│ │ ├─✅─to_fn() works for NULL (#250)
│ │ ├─✅─to_fn() respects allow_null for NULL input (#250)
│ │ ├─✅─to_fn() works for simple character names (#250)
│ │ ├─✅─to_fn() works for namespaced character names (#250)
│ │ ├─✅─to_fn() fails cleanly for weird ':' use in character names (#250)
│ │ ├─✅─to_fn() respects definition_env for character input (#250)
│ │ ├─✅─to_fn() errors informatively for unknown character names (#250)
│ │ ├─✅─to_fn() errors for length > 1 character input (#250)
│ │ ├─✅─to_fn() respects allow_null for length-0 character input (#250)
│ │ ├─✅─to_fn() errors informatively for bad namespaced names (#250)
│ │ ├─✅─to_fn() errors informatively for non-coercible types (#250)
│ │ ├─✅─to_function() exists (#250)
│ │ ├─✅─.chr_to_fn() works via C callable (#250)
│ │ ├─✅─.chr_are_fnish() works via C callable (#250)
│ │ ├─✅─to() converts character to function (#250)
│ │ ├─✅─to() passes function through unchanged (#250)
│ │ ├─✅─to() converts formula to function (#250)
│ │ └─✅─to() errors for non-coercible types to function (#250)
│ ├─☑️─Feature 241: Add `*_to_chr` and `*_to_fct` to the C API
│ │ ├─✅─to_chr() works for ints via C (#241)
│ │ ├─✅─to_chr() works for lgls via C (#241)
│ │ ├─✅─to_chr() works for dbls via C (#241)
│ │ ├─✅─to_chr() works for fcts via C (#241)
│ │ ├─✅─to_fct() works for ints via C (#241)
│ │ ├─✅─to_fct() errors for ints with unexpected levels (#241)
│ │ └─✅─to_fct() sorts integer levels numerically not lexicographically (#241)
│ ├─☑️─Feature 239: Replace all `to_*.list()` methods with C routines
│ │ ├─✅─.lst_to_dbl() unwraps singly-nested lists (#239)
│ │ ├─✅─.lst_to_dbl() marks multiply-nested lists as not valid (#239)
│ │ ├─✅─.lst_to_int() unwraps singly-nested lists (#239)
│ │ ├─✅─.lst_to_int() marks multiply-nested lists as not valid (#239)
│ │ ├─✅─.lst_to_lgl() unwraps singly-nested lists (#239)
│ │ ├─✅─.lst_to_lgl() marks multiply-nested lists as not valid (#239)
│ │ ├─✅─.lst_to_chr() converts non-character atomic scalars to strings (#226, #237, #239)
│ │ ├─✅─.lst_to_chr() converts factor elements via their level labels (#226, #237, #239)
│ │ ├─✅─.lst_to_chr() unwraps singly-nested lists (#239)
│ │ ├─✅─.lst_to_chr() marks multiply-nested lists as not valid (#239)
│ │ ├─✅─.lst_to_fct() unwraps singly-nested lists (#239)
│ │ └─✅─.lst_to_fct() marks multiply-nested lists as not valid (#239)
│ ├─☑️─Feature 237: Standardize callable C API: stbl_*_to_* should always return result + valid list
│ │ ├─✅─.chr_to_lgl() converts TRUE/T strings to TRUE (#218, #237)
│ │ ├─✅─.chr_to_lgl() converts FALSE/F strings to FALSE (#218, #237)
│ │ ├─✅─.chr_to_lgl() passes NA through as valid (#218, #237)
│ │ ├─✅─.chr_to_lgl() converts numeric strings (#218, #237)
│ │ ├─✅─.chr_to_lgl() marks invalid strings as not valid (#218, #237)
│ │ ├─✅─.chr_to_int() converts integer strings (#219, #237)
│ │ ├─✅─.chr_to_int() converts whole-number double strings (#219, #237)
│ │ ├─✅─.chr_to_int() passes NA through (#219, #237)
│ │ ├─✅─.chr_to_int() marks fractional strings as bad_precision (#219, #237)
│ │ ├─✅─.chr_to_int() marks non-number strings as non_number (#219, #237)
│ │ ├─✅─.chr_to_int() marks Inf strings as bad_precision (#219, #237)
│ │ ├─✅─.chr_to_dbl() converts numeric strings (#221, #237)
│ │ ├─✅─.chr_to_dbl() converts Inf strings (#221, #237)
│ │ ├─✅─.chr_to_dbl() passes NA through as valid (#221, #237)
│ │ ├─✅─.chr_to_dbl() marks invalid strings as not valid (#221, #237)
│ │ ├─✅─.dbl_to_int() converts integer-valued doubles (#217, #237)
│ │ ├─✅─.dbl_to_int() passes NA and NaN through as not bad_precision (#217, #237)
│ │ ├─✅─.dbl_to_int() marks fractional doubles as bad_precision (#217, #237)
│ │ ├─✅─.dbl_to_int() marks Inf and out-of-range values as bad_precision (#217, #237)
│ │ ├─✅─.dbl_to_lgl() converts 0 to FALSE with valid TRUE (#226, #237)
│ │ ├─✅─.dbl_to_lgl() converts non-zero to TRUE with valid TRUE (#226, #237)
│ │ ├─✅─.dbl_to_lgl() passes NA and NaN through; valid always TRUE (#226, #237)
│ │ ├─✅─.dbl_to_lgl() handles integer input; valid always TRUE (#226, #237)
│ │ ├─✅─.dbl_are_lglish() returns all TRUE (#218, #237)
│ │ ├─✅─.int_to_dbl() converts integers to doubles; valid always TRUE (#226, #237)
│ │ ├─✅─.int_to_dbl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.int_are_dblish() returns all TRUE (#226, #237)
│ │ ├─✅─.lgl_to_dbl() converts TRUE to 1.0; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_dbl() converts FALSE to 0.0; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_dbl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_are_dblish() returns all TRUE (#226, #237)
│ │ ├─✅─.lgl_to_int() converts TRUE to 1L; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_int() converts FALSE to 0L; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_to_int() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lgl_are_intish() returns all TRUE (#237)
│ │ ├─✅─.cpx_to_dbl() extracts real part when Im is zero (#226, #237)
│ │ ├─✅─.cpx_to_dbl() passes NA complex through as NA_real; valid TRUE (#226, #237)
│ │ ├─✅─.cpx_to_dbl() returns real part and valid FALSE for non-zero Im (#226, #237)
│ │ ├─✅─.cpx_to_int() converts whole-number complex with Im 0 (#226, #237)
│ │ ├─✅─.cpx_to_int() passes NA complex through; flags FALSE (#226, #237)
│ │ ├─✅─.cpx_to_int() marks non-zero Im as non_number (#226, #237)
│ │ ├─✅─.cpx_to_int() marks fractional real part as bad_precision (#226, #237)
│ │ ├─✅─.fct_to_dbl() converts numeric factor levels to doubles (#226, #237)
│ │ ├─✅─.fct_to_dbl() passes NA factor element through; valid TRUE (#226, #237)
│ │ ├─✅─.fct_to_dbl() marks non-numeric levels as not valid (#226, #237)
│ │ ├─✅─.fct_to_int() converts whole-number factor levels to integers (#226, #237)
│ │ ├─✅─.fct_to_int() passes NA factor element through; flags FALSE (#226, #237)
│ │ ├─✅─.fct_to_int() marks non-integer levels correctly (#226, #237)
│ │ ├─✅─.fct_to_lgl() converts TRUE/FALSE levels (#226, #237)
│ │ ├─✅─.fct_to_lgl() passes NA factor element through; valid TRUE (#226, #237)
│ │ ├─✅─.fct_to_lgl() marks non-logical levels as not valid (#226, #237)
│ │ ├─✅─.chr_are_fctish() returns all TRUE when levels is NULL (#226, #237)
│ │ ├─✅─.chr_are_fctish() checks membership in levels (#226, #237)
│ │ ├─✅─.chr_are_fctish() treats NA as fctish regardless of levels (#226, #237)
│ │ ├─✅─.chr_are_fctish() treats to_na values as fctish (#226, #237)
│ │ ├─✅─.fct_are_fctish() returns all TRUE when levels is NULL (#226, #237)
│ │ ├─✅─.fct_are_fctish() checks factor level membership in target levels (#226, #237)
│ │ ├─✅─.fct_are_fctish() treats NA as fctish regardless of levels (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts a flat list of numeric/lgl/int scalars (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts numeric character strings (#226, #237)
│ │ ├─✅─.lst_to_dbl() marks non-parseable character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts complex with Im == 0 (#226, #237)
│ │ ├─✅─.lst_to_dbl() marks complex with Im != 0 as not valid (#226, #237)
│ │ ├─✅─.lst_to_dbl() converts factor elements via their level strings (#226, #237)
│ │ ├─✅─.lst_to_dbl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_dbl() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_dbl() unpacks a one-element list with a double vector (#237)
│ │ ├─✅─.lst_to_dbl() unpacks a one-element list with an integer vector (#237)
│ │ ├─✅─.lst_to_dbl() unpacks a one-element list with a character vector (#237)
│ │ ├─✅─.lst_to_dbl() marks non-coercible single-element as not valid (#237)
│ │ ├─✅─.lst_to_dbl() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() converts a flat list of integer/logical scalars (#226, #237)
│ │ ├─✅─.lst_to_int() converts whole-number doubles (#226, #237)
│ │ ├─✅─.lst_to_int() converts integer-valued character strings (#226, #237)
│ │ ├─✅─.lst_to_int() marks fractional character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() marks non-number character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() converts complex with Im == 0 and whole Re (#226, #237)
│ │ ├─✅─.lst_to_int() marks complex with Im != 0 as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() converts factor elements via their level strings (#226, #237)
│ │ ├─✅─.lst_to_int() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_int() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_int() marks fractional doubles as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() unpacks a one-element list with an integer vector (#237)
│ │ ├─✅─.lst_to_int() unpacks a one-element list with a character vector (#237)
│ │ ├─✅─.lst_to_int() unpacks a one-element list with a double vector (#237)
│ │ ├─✅─.lst_to_int() marks non-coercible single-element as not valid (#226, #237)
│ │ ├─✅─.lst_to_int() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_lgl() converts a flat list of lgl/int/dbl scalars (#226, #237)
│ │ ├─✅─.lst_to_lgl() converts lgl-ish character strings (#226, #237)
│ │ ├─✅─.lst_to_lgl() marks non-lgl character strings as not valid (#226, #237)
│ │ ├─✅─.lst_to_lgl() converts factor elements via their level strings (#226, #237)
│ │ ├─✅─.lst_to_lgl() passes NA through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_lgl() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_lgl() unpacks a one-element list with a logical vector (#237)
│ │ ├─✅─.lst_to_lgl() unpacks a one-element list with a character vector (#237)
│ │ ├─✅─.lst_to_lgl() marks non-coercible single-element as not valid (#226, #237)
│ │ ├─✅─.lst_to_lgl() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_chr() passes character scalars through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_chr() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_chr() converts non-character atomic scalars to strings (#226, #237, #239)
│ │ ├─✅─.lst_to_chr() converts factor elements via their level labels (#226, #237, #239)
│ │ ├─✅─.lst_to_chr() unpacks a one-element list with a character vector (#237)
│ │ ├─✅─.lst_to_chr() marks non-character single-element vectors as not valid (#226, #237)
│ │ ├─✅─.lst_to_chr() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ │ ├─✅─.lst_to_fct() passes character scalars through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_fct() passes NA_character_ through; valid TRUE (#226, #237)
│ │ ├─✅─.lst_to_fct() marks non-character scalars as not valid (#226, #237)
│ │ ├─✅─.lst_to_fct() marks factor elements as valid (#226, #237)
│ │ ├─✅─.lst_to_fct() unpacks a one-element list with a character vector (#237)
│ │ ├─✅─.lst_to_fct() unpacks a one-element list with a factor (#237)
│ │ ├─✅─.lst_to_fct() marks non-character/factor single-element vectors as not valid (#226, #237)
│ │ └─✅─.lst_to_fct() marks non-scalar elements in multi-element lists as not valid (#226, #237)
│ ├─☑️─Feature 235: Callable C Functions
│ │ ├─✅─inst/include/stbl.h is installed (#235)
│ │ ├─✅─inst/include/stbl.c is installed (#235)
│ │ ├─✅─stbl.h declares all expected function pointers (#235)
│ │ ├─✅─stbl.h declares stbl_init_api() (#235)
│ │ ├─✅─stbl.c assigns all expected callables via R_GetCCallable (#235)
│ │ ├─✅─stbl.c defines stbl_init_api() (#235)
│ │ └─✅─src/init.c registers all expected C callables (#235)
│ ├─☑️─Bug 234: Allow users to define objects inside `expect_pkg_*_snapshot()`
│ │ ├─✅─expect_pkg_message_snapshot() allows object definition inside expression (#234, #301)
│ │ ├─✅─expect_pkg_warning_snapshot() allows object definition inside expression (#234, #301)
│ │ ├─✅─.capture_first_pkg_condition() captures a condition and returns it invisibly (#234)
│ │ ├─✅─.capture_first_pkg_condition() returns NULL when no condition is signalled (#234)
│ │ └─✅─.capture_first_pkg_condition() evaluates obj_expr in env (#234)
│ └─☑️─Feature 231: feat: add `max_levels` argument to `is_fct_ish()`
│   └─✅─is_fct_ish() supports max_levels (#231)
└─█─Milestone: v0.5.0 (15 issues, 337 tests)
  ├─☑️─Task 310: More specific error classes
  │ ├─✅─.stop_incompatible() throws the expected error (#95, #310)
  │ ├─✅─.stop_incompatible() passes dots (#95, #310)
  │ ├─✅─.check_cast_failures() works (#310)
  │ ├─✅─.check_lst_failures() errors when any element is invalid (#273, #310)
  │ ├─✅─.to_num_from_complex() works (#23, #310)
  │ ├─✅─specify_chr can build a regex checker (#147, #310, #325)
  │ ├─✅─specify_chr_scalar can build a regex checker (#147, #310, #325)
  │ ├─✅─specify_lst() respects pre-configured element specs (#110, #310)
  │ ├─✅─stabilize_lst() with nested specify_lst() propagates errors correctly (#204, #310)
  │ ├─✅─stabilize_any_of() includes Locations from incompatible_values errors (#215, #285, #310)
  │ ├─✅─stabilize_chr() errors for bad regex matches (#27, #52, #310)
  │ ├─✅─stabilize_chr() works with complex url regex (#52, #310)
  │ ├─✅─stabilize_chr() allows for customized error messages (#52, #310)
  │ ├─✅─stabilize_chr() works with regex that contains braces (#52, #310)
  │ ├─✅─stabilize_chr() accepts negated regex args (#85, #310)
  │ ├─✅─stabilize_chr() accepts multiple regex rules (#86, #85, #310)
  │ ├─✅─stabilize_chr() works with stringr::fixed() (#87, #310)
  │ ├─✅─stabilize_chr() works with stringr::coll() (#87, #310)
  │ ├─✅─stabilize_chr() works with stringr::regex() (#87, #310)
  │ ├─✅─stabilize_chr_scalar() works with regex that contains braces (#52, #310)
  │ ├─✅─stabilize_df() errors informatively when column fails validation (#142, #310)
  │ ├─✅─stabilize_df() validates extra columns with .extra_cols (#142, #310)
  │ ├─✅─stabilize_lst() errors informatively when element fails validation (#110, #310)
  │ ├─✅─stabilize_lst() validates extra named elements with .named (#110, #310)
  │ ├─✅─stabilize_lst() validates unnamed elements with .unnamed (#110, #310)
  │ ├─✅─to_chr() fails gracefully for weird cases (#22, #273, #310)
  │ ├─✅─to_chr_scalar() errors for uncoerceable types (#22, #273, #310)
  │ ├─✅─to_dbl() errors informatively for bad chrs (#23, #310)
  │ ├─✅─to_dbl() errors informatively for bad complexes (#23, #310)
  │ ├─✅─to_dbl() errors informatively for bad factors (#23, #310)
  │ ├─✅─to_dbl() works for lists (#128, #273, #310)
  │ ├─✅─to_fct() works for lists (#64, #273, #310)
  │ ├─✅─to_fct() errors for things that can't be coerced (#62, #273, #310)
  │ ├─✅─to_int() errors for dbls that would lose precision (#2, #217, #310)
  │ ├─✅─to_int() errors informatively for bad chrs (#2, #310)
  │ ├─✅─to_int() errors informatively for bad complexes (#2, #310)
  │ ├─✅─to_int() errors for complexes that would lose precision (#noissue, #310)
  │ ├─✅─to_int() errors informatively for bad factors (#4, #310)
  │ ├─✅─to_int() works for lists (#2, #273, #310)
  │ ├─✅─to_lgl() errors for bad characters (#21, #310)
  │ ├─✅─to_lgl errors for bad factors (#21, #310)
  │ ├─✅─to_lgl() works for lists (#21, #273, #310)
  │ ├─✅─to_lgl() errors for other types (#21, #273, #310)
  │ ├─✅─to_lgl_scalar() errors for bad characters (#32, #310)
  │ └─✅─.to_null() errors for bad allow_null (#129, #310)
  ├─☑️─Feature 274: Include `locations` object in conditions for element-wise failures
  │ ├─✅─.check_na() attaches failing locations (#274)
  │ ├─✅─.check_cast_failures() attaches failing locations (#274)
  │ ├─✅─.check_not_jagged() attaches failing locations (#274)
  │ ├─✅─object-level checks do not attach locations (#274)
  │ ├─✅─stabilize_chr() attaches regex failure locations (#274)
  │ ├─✅─stabilize_dbl() attaches value failure locations (#274)
  │ ├─✅─stabilize_int() attaches value failure locations (#274)
  │ └─✅─to_fct() attaches bad-level locations (#274)
  ├─☑️─Bug 273: fix: .check_lst_failures() should use .stop_incompatible() instead of .stop_cant_coerce()
  │ ├─✅─.check_lst_failures() returns NULL when all elements are valid (#273)
  │ ├─✅─.check_lst_failures() errors when any element is invalid (#273, #310)
  │ ├─✅─to_chr() fails gracefully for weird cases (#22, #273, #310)
  │ ├─✅─to_chr_scalar() errors for uncoerceable types (#22, #273, #310)
  │ ├─✅─to_dbl() works for lists (#128, #273, #310)
  │ ├─✅─to_fct() works for lists (#64, #273, #310)
  │ ├─✅─to_fct() errors for things that can't be coerced (#62, #273, #310)
  │ ├─✅─to_int() works for lists (#2, #273, #310)
  │ ├─✅─to_lgl() works for lists (#21, #273, #310)
  │ └─✅─to_lgl() errors for other types (#21, #273, #310)
  ├─☑️─Feature 215: `stabilize_one_of()` function
  │ ├─✅─stabilize_any_of() returns x unchanged when first function succeeds (#215, #285)
  │ ├─✅─stabilize_any_of() coerces via the first matching function (#215, #285)
  │ ├─✅─stabilize_any_of() falls through to the next function when first fails (#215, #285)
  │ ├─✅─stabilize_any_of() works with specify_* functions (#215, #285)
  │ ├─✅─stabilize_any_of() errors with a combined message when all functions fail (#215, #285)
  │ ├─✅─stabilize_any_of() includes Locations from incompatible_values errors (#215, #285, #310)
  │ ├─✅─stabilize_any_of() errors when ... is empty (#215, #285)
  │ ├─✅─stabilize_any_of() errors when ... contains named elements (#215, #285)
  │ ├─✅─stabilise_any_of() is a synonym for stabilize_any_of() (#215, #285)
  │ ├─✅─to_any_of() returns x unchanged when first prototype matches (#215, #285)
  │ ├─✅─to_any_of() coerces via the first matching prototype (#215, #285)
  │ ├─✅─to_any_of() falls through to the next prototype when first fails (#215, #285)
  │ ├─✅─to_any_of() errors with a combined message when all prototypes fail (#215, #285)
  │ └─✅─to_any_of() errors when ... is empty (#215, #285)
  ├─☑️─Feature 178: Custom stbl error  helpers
  │ ├─✅─ignore_stbl_error() returns NULL for a matching stbl error (#178)
  │ ├─✅─ignore_stbl_error() returns value when no error is thrown (#178)
  │ ├─✅─ignore_stbl_error() does not catch non-matching stbl errors (#178)
  │ ├─✅─ignore_stbl_error() catches broad subclass (#178)
  │ ├─✅─ignore_stbl_error() does not catch non-stbl errors (#178)
  │ ├─✅─replace_stbl_error() replaces the message of a matching stbl error (#178)
  │ ├─✅─replace_stbl_error() preserves the original call (#178)
  │ ├─✅─replace_stbl_error() preserves the original class hierarchy (#178)
  │ ├─✅─replace_stbl_error() prepends additional_class when provided (#178)
  │ ├─✅─replace_stbl_error() returns value when no error is thrown (#178)
  │ ├─✅─replace_stbl_error() does not catch non-matching stbl errors (#178)
  │ ├─✅─replace_stbl_error() catches broad subclass (#178)
  │ └─✅─replace_stbl_error() formats message with cli markup (#178)
  ├─☑️─Feature 105: feat: stabilize_datetime() (RFC 3339 date-time)
  │ ├─✅─specify_dttm can build a value checker (#105, #325)
  │ ├─✅─specify_dttm can enforce unique elements (#105, #325)
  │ ├─✅─specify_dttm respects tz (#105, #325)
  │ ├─✅─specify_dttm_scalar can build a value checker (#105, #325)
  │ ├─✅─specify_dttm_scalar defaults to allow_null = FALSE (#105, #325)
  │ ├─✅─specify_dttm_scalar defaults to allow_zero_length = FALSE (#105, #325)
  │ ├─✅─specify_dttm can enforce allowed_values (#105, #325)
  │ ├─✅─specify_dttm() creates a working stabilizer (#105, #325)
  │ ├─✅─specify_dttm_scalar() creates a working scalar stabilizer (#105, #325)
  │ ├─✅─stabilize_dttm() coerces and returns date-times (#105)
  │ ├─✅─stabilize_dttm() normalizes to the requested tz (#105)
  │ ├─✅─stabilize_dttm() works for NULL (#105)
  │ ├─✅─stabilize_dttm() respects allow_na (#105)
  │ ├─✅─stabilize_dttm() checks min_value (#105)
  │ ├─✅─stabilize_dttm() checks max_value (#105)
  │ ├─✅─stabilize_dttm() attaches value failure locations (#105)
  │ ├─✅─stabilize_dttm() checks size (#105)
  │ ├─✅─stabilize_dttm() enforces unique elements (#105)
  │ ├─✅─stabilize_dttm() checks allowed_values (#105)
  │ ├─✅─stabilize_dttm() rejects date-times without an offset (#105)
  │ ├─✅─stabilize_dttm() rejects an unrecognized tz (#105)
  │ ├─✅─stabilise_dttm() exists (#105)
  │ ├─✅─stabilize_datetime() exists (#105)
  │ ├─✅─stabilise_datetime() exists (#105)
  │ ├─✅─stabilize_dttm_scalar() allows length-1 date-times through (#105)
  │ ├─✅─stabilize_dttm_scalar() respects allow_null (#105)
  │ ├─✅─stabilize_dttm_scalar() errors on non-scalars (#105)
  │ ├─✅─stabilize_dttm_scalar() checks allowed_values (#105)
  │ ├─✅─stabilise_dttm_scalar() exists (#105)
  │ ├─✅─stabilize_datetime_scalar() exists (#105)
  │ ├─✅─stabilise_datetime_scalar() exists (#105)
  │ ├─✅─to_dttm() normalizes POSIXct to the target tz (#105)
  │ ├─✅─to_dttm() works for NULL (#105)
  │ ├─✅─to_dttm() respects allow_null (#105)
  │ ├─✅─to_dttm() parses RFC 3339 date-time strings with Z (#105)
  │ ├─✅─to_dttm() parses numeric UTC offsets (#105)
  │ ├─✅─to_dttm() allows a space instead of T (#105)
  │ ├─✅─to_dttm() parses fractional seconds (#105)
  │ ├─✅─to_dttm() normalizes to the requested tz (#105)
  │ ├─✅─to_dttm() keeps NA character elements as NA (#105)
  │ ├─✅─to_dttm() rejects date-times without an offset (#105)
  │ ├─✅─to_dttm() rejects unparseable date-time strings (#105)
  │ ├─✅─to_dttm() rejects impossible date-times (#105)
  │ ├─✅─to_dttm() rejects an unrecognized tz (#105)
  │ ├─✅─to_dttm() coerces factors via the character path (#105)
  │ ├─✅─to_dttm() errors informatively for bad factors (#105)
  │ ├─✅─to_dttm() converts POSIXlt (#105)
  │ ├─✅─to_dttm() treats Date as midnight UTC (#105)
  │ ├─✅─to_dttm() treats numerics as seconds since the epoch (#105)
  │ ├─✅─to_dttm() errors properly for other types (#105)
  │ ├─✅─to_dttm_scalar() allows length-1 date-times through (#105)
  │ ├─✅─to_dttm_scalar() coerces length-1 input (#105)
  │ ├─✅─to_dttm_scalar() provides informative error messages (#105)
  │ ├─✅─to_dttm_scalar() respects allow_null (#105)
  │ └─✅─to_dttm_scalar() respects allow_zero_length (#105)
  ├─☑️─Feature 104: feat: stabilize_date() (RFC 3339 date)
  │ ├─✅─specify_date can build a value checker (#104, #325)
  │ ├─✅─specify_date can enforce unique elements (#104, #325)
  │ ├─✅─specify_date_scalar can build a value checker (#104, #325)
  │ ├─✅─specify_date_scalar defaults to allow_null = FALSE (#104, #325)
  │ ├─✅─specify_date_scalar defaults to allow_zero_length = FALSE (#104, #325)
  │ ├─✅─specify_date can enforce allowed_values (#104, #325)
  │ ├─✅─specify_date() creates a working stabilizer (#104, #325)
  │ ├─✅─specify_date_scalar() creates a working scalar stabilizer (#104, #325)
  │ ├─✅─stabilize_date() coerces and returns dates (#104)
  │ ├─✅─stabilize_date() works for NULL (#104)
  │ ├─✅─stabilize_date() respects allow_na (#104)
  │ ├─✅─stabilize_date() checks min_value (#104)
  │ ├─✅─stabilize_date() checks max_value (#104)
  │ ├─✅─stabilize_date() attaches value failure locations (#104)
  │ ├─✅─stabilize_date() checks size (#104)
  │ ├─✅─stabilize_date() enforces unique elements (#104)
  │ ├─✅─stabilize_date() checks allowed_values (#104)
  │ ├─✅─stabilize_date() rejects ambiguous formats (#104)
  │ ├─✅─stabilise_date() exists (#104)
  │ ├─✅─stabilize_date_scalar() allows length-1 dates through (#104)
  │ ├─✅─stabilize_date_scalar() respects allow_null (#104)
  │ ├─✅─stabilize_date_scalar() errors on non-scalars (#104)
  │ ├─✅─stabilize_date_scalar() checks allowed_values (#104)
  │ ├─✅─stabilise_date_scalar() exists (#104)
  │ ├─✅─to_date() passes Dates through (#104)
  │ ├─✅─to_date() works for NULL (#104)
  │ ├─✅─to_date() respects allow_null (#104)
  │ ├─✅─to_date() parses RFC 3339 full-date strings (#104)
  │ ├─✅─to_date() keeps NA character elements as NA (#104)
  │ ├─✅─to_date() rejects ambiguous date formats (#104)
  │ ├─✅─to_date() rejects unparseable date strings (#104)
  │ ├─✅─to_date() rejects impossible dates (#104)
  │ ├─✅─to_date() coerces factors via the character path (#104)
  │ ├─✅─to_date() errors informatively for bad factors (#104)
  │ ├─✅─to_date() truncates POSIXct to the date component (#104)
  │ ├─✅─to_date() truncates POSIXlt to the date component (#104)
  │ ├─✅─to_date() treats numerics as days since the epoch (#104)
  │ ├─✅─to_date() errors properly for other types (#104)
  │ ├─✅─to_date_scalar() allows length-1 dates through (#104)
  │ ├─✅─to_date_scalar() coerces length-1 input (#104)
  │ ├─✅─to_date_scalar() provides informative error messages (#104)
  │ ├─✅─to_date_scalar() respects allow_null (#104)
  │ └─✅─to_date_scalar() respects allow_zero_length (#104)
  ├─☑️─Bug 301: Better snapshots from `expect_pkg_*_snapshot()`
  │ ├─✅─expect_pkg_error_snapshot() snapshots error class and message (#188, #301)
  │ ├─✅─expect_pkg_error_snapshot() works with multiple class components (#188, #301)
  │ ├─✅─expect_pkg_error_snapshot() works from an env without stbl attached (#188, #301)
  │ ├─✅─expect_pkg_message_snapshot() snapshots message class and message (#213, #301)
  │ ├─✅─expect_pkg_message_snapshot() works with multiple class components (#213, #301)
  │ ├─✅─expect_pkg_message_snapshot() allows object definition inside expression (#234, #301)
  │ ├─✅─expect_pkg_message_snapshot() works from an env without stbl attached (#213, #301)
  │ ├─✅─expect_pkg_warning_snapshot() snapshots warning class and message (#213, #301)
  │ ├─✅─expect_pkg_warning_snapshot() works with multiple class components (#213, #301)
  │ ├─✅─expect_pkg_warning_snapshot() allows object definition inside expression (#234, #301)
  │ └─✅─expect_pkg_warning_snapshot() works from an env without stbl attached (#213, #301)
  ├─☑️─Task 299: Rename `stabilize_present()` to `assert_present()`
  │ ├─✅─assert_present() returns any non-NULL value unchanged (#110, #299)
  │ ├─✅─assert_present() errors for NULL (#110, #299)
  │ ├─✅─assert_present() works as an element spec in stabilize_lst() (#110, #299)
  │ ├─✅─stabilize_present() is deprecated in favor of assert_present() (#299)
  │ └─✅─stabilize_present() errors even when x is NULL (#299)
  ├─☑️─Feature 295: feat: stabilize_duration() (ISO 8601 / RFC 3339 duration)
  │ ├─✅─specify_dur can build a value checker (#295, #325)
  │ ├─✅─specify_dur can enforce unique elements (#295, #325)
  │ ├─✅─specify_dur_scalar can build a value checker (#295, #325)
  │ ├─✅─specify_dur_scalar defaults to allow_null = FALSE (#295, #325)
  │ ├─✅─specify_dur_scalar defaults to allow_zero_length = FALSE (#295, #325)
  │ ├─✅─specify_dur can enforce allowed_values (#295, #325)
  │ ├─✅─specify_dur() creates a working stabilizer (#295, #325)
  │ ├─✅─specify_dur_scalar() creates a working scalar stabilizer (#295, #325)
  │ ├─✅─stabilize_dur() coerces and returns durations (#295)
  │ ├─✅─stabilize_dur() works for NULL (#295)
  │ ├─✅─stabilize_dur() respects allow_na (#295)
  │ ├─✅─stabilize_dur() checks min_value (#295)
  │ ├─✅─stabilize_dur() checks max_value (#295)
  │ ├─✅─stabilize_dur() attaches value failure locations (#295)
  │ ├─✅─stabilize_dur() checks size (#295)
  │ ├─✅─stabilize_dur() enforces unique elements (#295)
  │ ├─✅─stabilize_dur() flags non-adjacent duplicates with equal seconds components (#295)
  │ ├─✅─stabilize_dur() checks allowed_values (#295)
  │ ├─✅─stabilize_dur() rejects a bare P (#295)
  │ ├─✅─stabilise_dur() exists (#295)
  │ ├─✅─stabilize_dur_scalar() allows length-1 durations through (#295)
  │ ├─✅─stabilize_dur_scalar() respects allow_null (#295)
  │ ├─✅─stabilize_dur_scalar() errors on non-scalars (#295)
  │ ├─✅─stabilize_dur_scalar() checks allowed_values (#295)
  │ ├─✅─stabilise_dur_scalar() exists (#295)
  │ ├─✅─stabilize_duration_scalar() exists (#295)
  │ ├─✅─stabilise_duration_scalar() exists (#295)
  │ ├─✅─to_dur() passes Period through (#295)
  │ ├─✅─to_dur() works for NULL (#295)
  │ ├─✅─to_dur() respects allow_null (#295)
  │ ├─✅─to_dur() parses the full RFC 3339 date/time form (#295)
  │ ├─✅─to_dur() parses date-only durations (#295)
  │ ├─✅─to_dur() parses time-only durations (#295)
  │ ├─✅─to_dur() parses the week form (#295)
  │ ├─✅─to_dur() parses zero-length durations (#295)
  │ ├─✅─to_dur() keeps NA character elements as NA (#295)
  │ ├─✅─to_dur() rejects a bare P or PT (#295)
  │ ├─✅─to_dur() rejects mixing the week form with other units (#295)
  │ ├─✅─to_dur() rejects fractional components (#295)
  │ ├─✅─to_dur() rejects a negative sign (#295)
  │ ├─✅─to_dur() rejects out-of-order components (#295)
  │ ├─✅─to_dur() rejects lowercase durations (#295)
  │ ├─✅─to_dur() rejects unparseable duration strings (#295)
  │ ├─✅─to_dur() coerces factors via the character path (#295)
  │ ├─✅─to_dur() errors informatively for bad factors (#295)
  │ ├─✅─to_dur() converts difftime values, preserving their unit (#295)
  │ ├─✅─to_dur() converts hms values via the difftime path (#295)
  │ ├─✅─to_dur() treats numerics as seconds (#295)
  │ ├─✅─to_dur() errors properly for other types (#295)
  │ ├─✅─to_dur_scalar() allows length-1 durations through (#295)
  │ ├─✅─to_dur_scalar() coerces length-1 input (#295)
  │ ├─✅─to_dur_scalar() provides informative error messages (#295)
  │ ├─✅─to_dur_scalar() respects allow_null (#295)
  │ └─✅─to_dur_scalar() respects allow_zero_length (#295)
  ├─☑️─Feature 294: feat: stabilize_time() (RFC 3339 time-of-day)
  │ ├─✅─specify_time can build a value checker (#294, #325)
  │ ├─✅─specify_time can enforce unique elements (#294, #325)
  │ ├─✅─specify_time_scalar can build a value checker (#294, #325)
  │ ├─✅─specify_time_scalar defaults to allow_null = FALSE (#294, #325)
  │ ├─✅─specify_time_scalar defaults to allow_zero_length = FALSE (#294, #325)
  │ ├─✅─specify_time can enforce allowed_values (#294, #325)
  │ ├─✅─specify_time() creates a working stabilizer (#294, #325)
  │ ├─✅─specify_time_scalar() creates a working scalar stabilizer (#294, #325)
  │ ├─✅─stabilize_time() coerces and returns times (#294)
  │ ├─✅─stabilize_time() works for NULL (#294)
  │ ├─✅─stabilize_time() respects allow_na (#294)
  │ ├─✅─stabilize_time() checks min_value (#294)
  │ ├─✅─stabilize_time() checks max_value (#294)
  │ ├─✅─stabilize_time() attaches value failure locations (#294)
  │ ├─✅─stabilize_time() checks size (#294)
  │ ├─✅─stabilize_time() enforces unique elements (#294)
  │ ├─✅─stabilize_time() checks allowed_values (#294)
  │ ├─✅─stabilize_time() rejects times without an offset (#294)
  │ ├─✅─stabilise_time() exists (#294)
  │ ├─✅─stabilize_time_scalar() allows length-1 times through (#294)
  │ ├─✅─stabilize_time_scalar() respects allow_null (#294)
  │ ├─✅─stabilize_time_scalar() errors on non-scalars (#294)
  │ ├─✅─stabilize_time_scalar() checks allowed_values (#294)
  │ ├─✅─stabilise_time_scalar() exists (#294)
  │ ├─✅─to_time() passes hms through (#294)
  │ ├─✅─to_time() works for NULL (#294)
  │ ├─✅─to_time() respects allow_null (#294)
  │ ├─✅─to_time() parses RFC 3339 full-time strings with Z (#294)
  │ ├─✅─to_time() normalizes numeric offsets to UTC (#294)
  │ ├─✅─to_time() wraps offsets across midnight (#294)
  │ ├─✅─to_time() parses fractional seconds (#294)
  │ ├─✅─to_time() keeps NA character elements as NA (#294)
  │ ├─✅─to_time() rejects times without an offset (#294)
  │ ├─✅─to_time() rejects unparseable time strings (#294)
  │ ├─✅─to_time() rejects impossible times (#294)
  │ ├─✅─to_time() coerces factors via the character path (#294)
  │ ├─✅─to_time() errors informatively for bad factors (#294)
  │ ├─✅─to_time() extracts time-of-day from POSIXct, normalized to UTC (#294)
  │ ├─✅─to_time() converts POSIXlt (#294)
  │ ├─✅─to_time() treats numerics as seconds since midnight (#294)
  │ ├─✅─to_time() rejects out-of-range numerics (#294)
  │ ├─✅─to_time() converts difftime values (#294)
  │ ├─✅─to_time() rejects out-of-range difftime values (#294)
  │ ├─✅─to_time() rejects out-of-range hms values (#294)
  │ ├─✅─to_time() errors properly for other types (#294)
  │ ├─✅─to_time_scalar() allows length-1 times through (#294)
  │ ├─✅─to_time_scalar() coerces length-1 input (#294)
  │ ├─✅─to_time_scalar() provides informative error messages (#294)
  │ ├─✅─to_time_scalar() respects allow_null (#294)
  │ └─✅─to_time_scalar() respects allow_zero_length (#294)
  ├─☑️─Task 285: chore: rename stabilize_one_of() / to_one_of() to *_any_of()
  │ ├─✅─stabilize_any_of() returns x unchanged when first function succeeds (#215, #285)
  │ ├─✅─stabilize_any_of() coerces via the first matching function (#215, #285)
  │ ├─✅─stabilize_any_of() falls through to the next function when first fails (#215, #285)
  │ ├─✅─stabilize_any_of() works with specify_* functions (#215, #285)
  │ ├─✅─stabilize_any_of() errors with a combined message when all functions fail (#215, #285)
  │ ├─✅─stabilize_any_of() includes Locations from incompatible_values errors (#215, #285, #310)
  │ ├─✅─stabilize_any_of() errors when ... is empty (#215, #285)
  │ ├─✅─stabilize_any_of() errors when ... contains named elements (#215, #285)
  │ ├─✅─stabilise_any_of() is a synonym for stabilize_any_of() (#215, #285)
  │ ├─✅─to_any_of() returns x unchanged when first prototype matches (#215, #285)
  │ ├─✅─to_any_of() coerces via the first matching prototype (#215, #285)
  │ ├─✅─to_any_of() falls through to the next prototype when first fails (#215, #285)
  │ ├─✅─to_any_of() errors with a combined message when all prototypes fail (#215, #285)
  │ └─✅─to_any_of() errors when ... is empty (#215, #285)
  ├─☑️─Feature 282: feat: allowed-values constraint (enum / const) for stabilize_*()
  │ ├─✅─.check_allowed_values() works (#282)
  │ ├─✅─.check_allowed_values() permits NA independently of allowed_values (#282)
  │ ├─✅─.check_allowed_values() attaches failing locations (#282)
  │ ├─✅─specify_chr can enforce allowed_values (#282, #325)
  │ ├─✅─specify_dbl can enforce allowed_values (#282, #325)
  │ ├─✅─specify_int can enforce allowed_values (#282, #325)
  │ ├─✅─specify_lgl can enforce allowed_values (#282, #325)
  │ ├─✅─stabilize_chr() checks allowed_values (#282)
  │ ├─✅─stabilize_chr() permits NA independently of allowed_values (#282)
  │ ├─✅─stabilize_chr_scalar() checks allowed_values (#282)
  │ ├─✅─stabilize_dbl() checks allowed_values (#282)
  │ ├─✅─stabilize_dbl_scalar() checks allowed_values (#282)
  │ ├─✅─stabilize_int() checks allowed_values (#282)
  │ ├─✅─stabilize_int_scalar() checks allowed_values (#282)
  │ ├─✅─stabilize_lgl() checks allowed_values (#282)
  │ └─✅─stabilize_lgl_scalar() checks allowed_values (#282)
  ├─☑️─Feature 280: feat: unique-elements constraint (uniqueItems) for stabilize_*()
  │ ├─✅─.check_unique() works for atomic and list inputs (#280)
  │ ├─✅─specify_chr can enforce unique elements (#280, #325)
  │ ├─✅─specify_dbl can enforce unique elements (#280, #325)
  │ ├─✅─specify_int can enforce unique elements (#280, #325)
  │ ├─✅─specify_lst() enforces .unique when configured (#280)
  │ ├─✅─stabilize_chr() enforces unique elements (#280)
  │ ├─✅─stabilize_chr() reports duplicate locations, including NA duplicates (#280)
  │ ├─✅─stabilize_dbl() enforces unique elements (#280)
  │ ├─✅─stabilize_dbl() reports duplicate locations, including NA duplicates (#280)
  │ ├─✅─stabilize_int() enforces unique elements (#280)
  │ ├─✅─stabilize_int() reports duplicate locations, including NA duplicates (#280)
  │ ├─✅─stabilize_lst() enforces .unique (#280)
  │ ├─✅─stabilize_lst() reports duplicate locations for .unique failures (#280)
  │ └─✅─stabilize_lst() checks .unique after coercion (#280)
  └─☑️─Feature 275: feat: min_characters / max_characters for stabilize_chr()
    ├─✅─stabilize_chr() passes when all elements meet min_characters (#275)
    ├─✅─stabilize_chr() passes when all elements meet max_characters (#275)
    ├─✅─stabilize_chr() errors when elements have too few characters (#275)
    ├─✅─stabilize_chr() errors when elements have too many characters (#275)
    ├─✅─stabilize_chr() errors for single element with wrong character count (#275)
    ├─✅─stabilize_chr() errors when both min and max fail (#275)
    ├─✅─stabilize_chr() errors when min_characters > max_characters (#275)
    ├─✅─stabilize_chr() attaches locations for min/max_characters failures (#275)
    └─✅─stabilize_chr_scalar() errors for wrong character count (#275)
# Issue state: 📥 = open, ☑️ = closed (completed), ⛔ = closed (won't fix)
# Test disposition: ✅ = passed, ❌ = failed, 🚫 = skipped
✅ All tests passed

🟢 All issues have at least one test

🙈 35 issues with label "qcthat-nocov" were ignored


This report was generated by this GitHub Actions job.

Last updated: 2026-08-17 11:19:54 UTC

Session Info

@jonthegeek
jonthegeek merged commit 7b45fb2 into main Aug 17, 2026
12 checks passed
@jonthegeek
jonthegeek deleted the fix-325-specify-breakdown branch August 17, 2026 11:23
github-actions Bot pushed a commit that referenced this pull request Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: Refactor specify_*()

1 participant