Skip to content

Commit d4c0bba

Browse files
committed
Let C level vec_ptype_common() optionally opt out of ptype finalization
Then use this in `list_combine()` to delay finalization until `default` has been considered.
1 parent a5c1fe3 commit d4c0bba

File tree

12 files changed

+140
-26
lines changed

12 files changed

+140
-26
lines changed

R/type.R

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,18 @@ vec_ptype_common <- function(
120120
vec_ptype_common_params <- function(
121121
...,
122122
.ptype = NULL,
123+
.finalise = TRUE,
123124
.fallback_opts = fallback_opts(),
124125
.arg = "",
125126
.call = caller_env()
126127
) {
127-
.External2(ffi_ptype_common_params, list2(...), .ptype, .fallback_opts)
128+
.External2(
129+
ffi_ptype_common_params,
130+
list2(...),
131+
.ptype,
132+
.finalise,
133+
.fallback_opts
134+
)
128135
}
129136

130137
vec_ptype_common_fallback <- function(

src/bind.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ r_obj* vec_rbind(r_obj* xs,
7474
ptype = vec_ptype_common(
7575
xs,
7676
ptype,
77+
PTYPE_FINALISE_DEFAULT,
7778
S3_FALLBACK_true,
7879
p_arg,
7980
error_call
@@ -498,6 +499,7 @@ r_obj* vec_cbind(r_obj* xs,
498499
r_obj* type = KEEP(vec_ptype_common(
499500
xs_data_frames,
500501
ptype,
502+
PTYPE_FINALISE_DEFAULT,
501503
S3_FALLBACK_false,
502504
p_arg,
503505
error_call

src/cast.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ r_obj* vec_cast_common_opts(r_obj* xs,
255255
r_obj* type = KEEP(vec_ptype_common(
256256
xs,
257257
to,
258+
PTYPE_FINALISE_DEFAULT,
258259
opts->s3_fallback,
259260
opts->p_arg,
260261
opts->call

src/init.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ extern r_obj* ffi_new_data_frame(r_obj*);
390390
static
391391
const R_ExternalMethodDef ExtEntries[] = {
392392
{"ffi_ptype_common", (DL_FUNC) &ffi_ptype_common, 2},
393-
{"ffi_ptype_common_params", (DL_FUNC) &ffi_ptype_common_params, 3},
393+
{"ffi_ptype_common_params", (DL_FUNC) &ffi_ptype_common_params, 4},
394394
{"ffi_size_common", (DL_FUNC) &ffi_size_common, 3},
395395
{"ffi_recycle_common", (DL_FUNC) &ffi_recycle_common, 2},
396396
{"ffi_cast_common", (DL_FUNC) &ffi_cast_common, 2},

src/list-combine.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,7 @@ r_obj* list_combine_common_class_fallback(
809809
vec_ptype_common(
810810
xs,
811811
ptype,
812+
PTYPE_FINALISE_DEFAULT,
812813
s3_fallback,
813814
p_xs_arg,
814815
error_call
@@ -1802,10 +1803,12 @@ r_obj* ptype_common_with_default(
18021803

18031804
// Okay `ptype` is `NULL`. We determine it from `xs` and `default`.
18041805

1805-
// Use only `xs` and `p_xs_arg` first for best errors
1806+
// Use only `xs` and `p_xs_arg` first for best errors.
1807+
// Not finalising `ptype` yet in case we need to incorporating `default`!
18061808
ptype = KEEP(vec_ptype_common(
18071809
xs,
18081810
ptype,
1811+
PTYPE_FINALISE_false,
18091812
s3_fallback,
18101813
p_xs_arg,
18111814
error_call
@@ -1826,7 +1829,10 @@ r_obj* ptype_common_with_default(
18261829
}
18271830
KEEP(ptype);
18281831

1829-
FREE(2);
1832+
// Now finalise after incorporating `default`
1833+
ptype = KEEP(vec_ptype_finalise(ptype));
1834+
1835+
FREE(3);
18301836
return ptype;
18311837
}
18321838

src/ptype-common.c

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ r_obj* ffi_ptype_common(r_obj* ffi_call, r_obj* op, r_obj* args, r_obj* env) {
1818
struct r_lazy xs_arg_lazy = { .x = syms.dot_arg, .env = env };
1919
struct vctrs_arg xs_arg = new_lazy_arg(&xs_arg_lazy);
2020

21+
// User calls to `vec_ptype_common()` are always finalised and never fall back
22+
const enum ptype_finalise finalise = PTYPE_FINALISE_true;
23+
const enum s3_fallback s3_fallback = S3_FALLBACK_false;
24+
2125
r_obj* out = vec_ptype_common(
2226
xs,
2327
ptype,
24-
S3_FALLBACK_false,
28+
finalise,
29+
s3_fallback,
2530
&xs_arg,
2631
call
2732
);
@@ -35,17 +40,22 @@ r_obj* ffi_ptype_common_params(r_obj* ffi_call, r_obj* op, r_obj* args, r_obj* e
3540

3641
r_obj* xs = r_node_car(args); args = r_node_cdr(args);
3742
r_obj* ptype = r_node_car(args); args = r_node_cdr(args);
43+
r_obj* ffi_finalise = r_node_car(args); args = r_node_cdr(args);
3844
r_obj* opts = r_node_car(args);
3945

4046
struct r_lazy call = { .x = syms.dot_call, .env = env };
4147
struct r_lazy xs_arg_lazy = { .x = syms.dot_arg, .env = env };
4248
struct vctrs_arg xs_arg = new_lazy_arg(&xs_arg_lazy);
4349

50+
const enum ptype_finalise finalise = r_arg_as_bool(ffi_finalise, "finalise") ?
51+
PTYPE_FINALISE_true :
52+
PTYPE_FINALISE_false;
4453
const enum s3_fallback s3_fallback = s3_fallback_from_opts(opts);
4554

4655
r_obj* out = vec_ptype_common(
4756
xs,
4857
ptype,
58+
finalise,
4959
s3_fallback,
5060
&xs_arg,
5161
call
@@ -55,39 +65,50 @@ r_obj* ffi_ptype_common_params(r_obj* ffi_call, r_obj* op, r_obj* args, r_obj* e
5565
}
5666

5767
// Invariant of `vec_ptype_common()` is that the output is always a finalised `ptype`,
58-
// even if the user provided their own
68+
// even if the user provided their own, unless `PTYPE_FINALISE_false` is specified.
5969
r_obj* vec_ptype_common(
6070
r_obj* dots,
6171
r_obj* ptype,
72+
enum ptype_finalise finalise,
6273
enum s3_fallback s3_fallback,
6374
struct vctrs_arg* p_arg,
6475
struct r_lazy call
6576
) {
77+
int n_prot = 0;
78+
79+
r_obj* out;
80+
6681
if (ptype != r_null) {
67-
return vec_ptype_final(ptype, vec_args.dot_ptype, call);
82+
out = KEEP_N(vec_ptype(ptype, vec_args.dot_ptype, call), &n_prot);
83+
} else {
84+
if (r_is_true(r_peek_option("vctrs.no_guessing"))) {
85+
r_abort_lazy_call(r_lazy_null, "strict mode is activated; you must supply complete `.ptype`.");
86+
}
87+
88+
struct ptype_common_reduce_opts reduce_opts = {
89+
.call = call,
90+
.s3_fallback = s3_fallback
91+
};
92+
93+
out = KEEP_N(
94+
reduce(
95+
r_null,
96+
vec_args.empty,
97+
p_arg,
98+
dots,
99+
&ptype2_common,
100+
&reduce_opts
101+
),
102+
&n_prot
103+
);
68104
}
69105

70-
if (r_is_true(r_peek_option("vctrs.no_guessing"))) {
71-
r_abort_lazy_call(r_lazy_null, "strict mode is activated; you must supply complete `.ptype`.");
106+
if (should_finalise(finalise)) {
107+
out = KEEP_N(vec_ptype_finalise(out), &n_prot);
72108
}
73109

74-
struct ptype_common_reduce_opts reduce_opts = {
75-
.call = call,
76-
.s3_fallback = s3_fallback
77-
};
78-
79-
r_obj* type = KEEP(reduce(
80-
r_null,
81-
vec_args.empty,
82-
p_arg,
83-
dots,
84-
&ptype2_common,
85-
&reduce_opts
86-
));
87-
type = vec_ptype_finalise(type);
88-
89-
FREE(1);
90-
return type;
110+
FREE(n_prot);
111+
return out;
91112
}
92113

93114
static

src/ptype-common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "vctrs-core.h"
55
#include "ptype2.h"
6+
#include "unspecified.h"
67
#include "utils.h"
78

89
static inline
@@ -13,6 +14,7 @@ bool vec_is_common_class_fallback(r_obj* ptype) {
1314
r_obj* vec_ptype_common(
1415
r_obj* dots,
1516
r_obj* ptype,
17+
enum ptype_finalise finalise,
1618
enum s3_fallback s3_fallback,
1719
struct vctrs_arg* p_arg,
1820
struct r_lazy call

src/recode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ r_obj* ptype_finalize(
777777
ptype = KEEP(vec_ptype_common(
778778
to,
779779
r_null,
780+
PTYPE_FINALISE_DEFAULT,
780781
S3_FALLBACK_DEFAULT,
781782
p_to_arg,
782783
error_call

src/unspecified.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@
66
SEXP vec_unspecified(R_len_t n);
77
bool vec_is_unspecified(SEXP x);
88

9+
enum ptype_finalise {
10+
PTYPE_FINALISE_false,
11+
PTYPE_FINALISE_true
12+
};
13+
14+
#define PTYPE_FINALISE_DEFAULT PTYPE_FINALISE_true
15+
16+
static inline
17+
bool should_finalise(enum ptype_finalise finalise) {
18+
return finalise == PTYPE_FINALISE_true;
19+
}
20+
921
r_obj* vec_ptype_finalise(r_obj* x);
1022

1123
#endif

tests/testthat/test-case-when.R

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,17 @@ test_that("`default` that is an unused logical `NA` can still be cast to `values
272272
expect_identical(vec_case_when(list(TRUE), list("x"), default = NA), "x")
273273
})
274274

275+
test_that("`default` type is used when all values are logical `NA` (#2094)", {
276+
expect_identical(
277+
vec_case_when(list(TRUE), list(NA), default = "a"),
278+
NA_character_
279+
)
280+
expect_identical(
281+
vec_case_when(list(c(TRUE, FALSE)), list(NA), default = "a"),
282+
c(NA_character_, "a")
283+
)
284+
})
285+
275286
test_that("`default_arg` can be customized", {
276287
expect_snapshot(error = TRUE, {
277288
vec_case_when(list(FALSE), list(1L), default = 2:3, default_arg = "foo")

0 commit comments

Comments
 (0)