diff --git a/NEWS.md b/NEWS.md index 1e6389ed..7ccca3cb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # cpp11 (development version) +* `cpp11::as_cpp()` and `cpp11::as_cpp()` now implicitly coerce between all 3 types of single NA values (#53). + * The `END_CPP` macro now includes a `catch(...)` block to catch all C++ exceptions that do not inherit from `std::exception` (#47). * Improve consistency of inserting NA values in r_string objects (#45) diff --git a/cpp11test/src/test-as.cpp b/cpp11test/src/test-as.cpp index 855ea8a9..33f2ac84 100644 --- a/cpp11test/src/test-as.cpp +++ b/cpp11test/src/test-as.cpp @@ -55,6 +55,26 @@ context("as_cpp-C++") { UNPROTECT(1); } + test_that("as_cpp(NA)") { + SEXP r = PROTECT(Rf_allocVector(REALSXP, 1)); + SEXP i = PROTECT(Rf_allocVector(INTSXP, 1)); + SEXP l = PROTECT(Rf_allocVector(LGLSXP, 1)); + REAL(r)[0] = NA_REAL; + INTEGER(i)[0] = NA_INTEGER; + LOGICAL(l)[0] = NA_LOGICAL; + + auto x1 = cpp11::as_cpp(r); + expect_true(x1 == NA_INTEGER); + + auto x2 = cpp11::as_cpp(i); + expect_true(x2 == NA_INTEGER); + + auto x3 = cpp11::as_cpp(l); + expect_true(x3 == NA_INTEGER); + + UNPROTECT(3); + } + test_that("as_cpp(REALSXP)") { SEXP r = PROTECT(Rf_allocVector(REALSXP, 1)); REAL(r)[0] = 1.2; @@ -85,6 +105,26 @@ context("as_cpp-C++") { UNPROTECT(1); } + test_that("as_cpp(NA)") { + SEXP r = PROTECT(Rf_allocVector(REALSXP, 1)); + SEXP i = PROTECT(Rf_allocVector(INTSXP, 1)); + SEXP l = PROTECT(Rf_allocVector(LGLSXP, 1)); + REAL(r)[0] = NA_REAL; + INTEGER(i)[0] = NA_INTEGER; + LOGICAL(l)[0] = NA_LOGICAL; + + auto x1 = cpp11::as_cpp(r); + expect_true(ISNA(x1)); + + auto x2 = cpp11::as_cpp(i); + expect_true(ISNA(x2)); + + auto x3 = cpp11::as_cpp(l); + expect_true(ISNA(x3)); + + UNPROTECT(3); + } + test_that("as_cpp()") { SEXP r = PROTECT(Rf_allocVector(LGLSXP, 1)); LOGICAL(r)[0] = TRUE; @@ -382,7 +422,8 @@ context("as_cpp-C++") { } test_that("as_sexp(r_vector)") { - SEXP s1 = PROTECT(cpp11::as_sexp(std::vector({"foo", "bar", "baz"}))); + SEXP s1 = + PROTECT(cpp11::as_sexp(std::vector({"foo", "bar", "baz"}))); expect_true(Rf_isString(s1)); expect_true(Rf_xlength(s1) == 3); diff --git a/inst/include/cpp11/as.hpp b/inst/include/cpp11/as.hpp index 74b56434..3efd7aa1 100644 --- a/inst/include/cpp11/as.hpp +++ b/inst/include/cpp11/as.hpp @@ -50,11 +50,20 @@ is_integral as_cpp(SEXP from) { } } else if (Rf_isReal(from)) { if (Rf_xlength(from) == 1) { + if (ISNA(REAL_ELT(from, 0))) { + return NA_INTEGER; + } double value = REAL_ELT(from, 0); if (is_convertable_without_loss_to_integer(value)) { return value; } } + } else if (Rf_isLogical(from)) { + if (Rf_xlength(from) == 1) { + if (LOGICAL_ELT(from, 0) == NA_LOGICAL) { + return NA_INTEGER; + } + } } stop("Expected single integer value"); @@ -90,12 +99,25 @@ is_floating_point_value as_cpp(SEXP from) { return REAL_ELT(from, 0); } } - // All integers can be coerced to doubles, so we just convert them. + // All 32 bit integers can be coerced to doubles, so we just convert them. if (Rf_isInteger(from)) { if (Rf_xlength(from) == 1) { + if (INTEGER_ELT(from, 0) == NA_INTEGER) { + return NA_REAL; + } return INTEGER_ELT(from, 0); } } + + // Also allow NA values + if (Rf_isLogical(from)) { + if (Rf_xlength(from) == 1) { + if (LOGICAL_ELT(from, 0) == NA_LOGICAL) { + return NA_REAL; + } + } + } + stop("Expected single double value"); return T();