Skip to content

Commit a5c1fe3

Browse files
authored
Ensure that vec_ptype_common() always finalizes its output (#2102)
* Ensure that `vec_ptype_common()` always finalizes its output Even user supplied `ptype`s * Typo
1 parent 2a998b8 commit a5c1fe3

File tree

4 files changed

+60
-1
lines changed

4 files changed

+60
-1
lines changed

src/ptype-common.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ r_obj* ffi_ptype_common_params(r_obj* ffi_call, r_obj* op, r_obj* args, r_obj* e
5454
return out;
5555
}
5656

57+
// Invariant of `vec_ptype_common()` is that the output is always a finalised `ptype`,
58+
// even if the user provided their own
5759
r_obj* vec_ptype_common(
5860
r_obj* dots,
5961
r_obj* ptype,
@@ -62,7 +64,7 @@ r_obj* vec_ptype_common(
6264
struct r_lazy call
6365
) {
6466
if (ptype != r_null) {
65-
return vec_ptype(ptype, vec_args.dot_ptype, call);
67+
return vec_ptype_final(ptype, vec_args.dot_ptype, call);
6668
}
6769

6870
if (r_is_true(r_peek_option("vctrs.no_guessing"))) {

tests/testthat/_snaps/cast.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,11 @@
8383
i Please use `allow_lossy_cast()` instead.
8484
i We detected a lossy transformation from `x` <fct> to `to` <fct>. The result will contain lower-resolution values or missing values. To suppress this warning, wrap your code with `allow_lossy_cast()`.
8585

86+
# can cast to unspecified `NA` with `vec_cast()` and `vec_cast_common()` (#2099)
87+
88+
Code
89+
vec_cast(TRUE, to = unspecified(1))
90+
Condition
91+
Error:
92+
! Can't convert `TRUE` <logical> to <vctrs_unspecified>.
93+

tests/testthat/test-cast.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,23 @@ test_that("bare-type fallback for df-cast works", {
337337

338338
expect_error(vec_rbind(gdf, gdf), NA)
339339
})
340+
341+
test_that("can cast to unspecified `NA` with `vec_cast()` and `vec_cast_common()` (#2099)", {
342+
# In the `vec_cast()` case no `vec_ptype()` call is made, which means that no
343+
# finalization step is required. In the `vec_cast_common()` case, the
344+
# underlying call to `vec_ptype_common()` calls `vec_ptype(NA)` but also
345+
# finalizes that to `logical()` on the way out, so this still works.
346+
expect_identical(vec_cast(TRUE, to = NA), TRUE)
347+
expect_identical(vec_cast_common(TRUE, .to = NA), list(TRUE))
348+
349+
# `vec_cast()` itself does not call `vec_ptype()` and does not finalize,
350+
# so this stays <unspecified> and the cast fails
351+
# (this behavior is questionable, but is very much an edge case)
352+
expect_snapshot(error = TRUE, {
353+
vec_cast(TRUE, to = unspecified(1))
354+
})
355+
356+
# `vec_cast_common()` calls `vec_ptype_common()`, which always finalises,
357+
# so this technically works (but again, it is an edge case)
358+
expect_identical(vec_cast_common(TRUE, .to = unspecified(1)), list(TRUE))
359+
})

tests/testthat/test-type.R

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,32 @@ test_that("vec_ptype_common() handles spliced names consistently (#1570)", {
304304
)
305305
})
306306
})
307+
308+
test_that("vec_ptype() and vec_ptype2() don't finalize their output", {
309+
expect_identical(vec_ptype(NA), unspecified())
310+
expect_identical(vec_ptype2(NA, NA), unspecified())
311+
expect_identical(vec_ptype2(NA, NULL), unspecified())
312+
expect_identical(vec_ptype2(NULL, NA), unspecified())
313+
314+
expect_identical(vec_ptype(unspecified()), unspecified())
315+
expect_identical(vec_ptype2(unspecified(), unspecified()), unspecified())
316+
expect_identical(vec_ptype2(unspecified(), NULL), unspecified())
317+
expect_identical(vec_ptype2(NULL, unspecified()), unspecified())
318+
})
319+
320+
test_that("vec_ptype_common() always finalizes its output (#2099)", {
321+
expect_identical(vec_ptype_common(NA), logical())
322+
expect_identical(vec_ptype_common(NA, NA), logical())
323+
expect_identical(vec_ptype_common(unspecified(1)), logical())
324+
expect_identical(vec_ptype_common(unspecified(1), unspecified(1)), logical())
325+
326+
# Even explicit `.ptype`s
327+
expect_identical(
328+
vec_ptype_common(.ptype = NA),
329+
logical()
330+
)
331+
expect_identical(
332+
vec_ptype_common(.ptype = unspecified(1)),
333+
logical()
334+
)
335+
})

0 commit comments

Comments
 (0)