Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,19 @@ fn set_r_version_vars(ver: &RVersionInfo) {
println!("cargo:r_version_minor={}", ver.minor); // Becomes DEP_R_R_VERSION_MINOR for clients
println!("cargo:r_version_patch={}", ver.patch); // Becomes DEP_R_R_VERSION_PATCH for clients
println!("cargo:r_version_devel={}", ver.devel); // Becomes DEP_R_R_VERSION_DEVEL for clients

// Version cfg flags, for use inside this crate (e.g. the `backports`
// module, which needs to pick the right entry point per R version).
println!("cargo:rustc-check-cfg=cfg(r_4_4)");
println!("cargo:rustc-check-cfg=cfg(r_4_5)");
let major: u32 = ver.major.parse().unwrap_or(0);
let minor: u32 = ver.minor.parse().unwrap_or(0);
if (major, minor) >= (4, 4) {
println!("cargo:rustc-cfg=r_4_4");
}
if (major, minor) >= (4, 5) {
println!("cargo:rustc-cfg=r_4_5");
}
}

/// Retrieve bindings from cache, if available. Errors out otherwise.
Expand Down
239 changes: 239 additions & 0 deletions src/backports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//! Backport functions
//!
//! R's C API is changing and is stabilizing. As such some functions
//! that were available in previous versions of R are not available
//! in later versions of R, or they cause a warning in `R CMD check`.
//!
//! Use the functions in this module to ensure backwards compatibility.
//!
//! Ported from `extendr-ffi` (`src/backports.rs`). Gated on the `r_4_5`
//! cfg emitted by `build.rs`.

// R 4.5 backports notes saved in wayback machine here:
// https://web.archive.org/web/20250325171443/https://rstudio.github.io/r-manuals/r-exts/The-R-API.html#moving-into-c-api-compliance

use crate::{Rboolean, SEXP};

#[cfg(not(r_4_5))]
use crate::R_UnboundValue;

extern "C" {
#[cfg(not(r_4_5))]
fn ENCLOS(x: SEXP) -> SEXP;

#[cfg(r_4_5)]
fn R_ParentEnv(x: SEXP) -> SEXP;

#[cfg(not(r_4_5))]
fn Rf_findVar(arg1: SEXP, arg2: SEXP) -> SEXP;

#[cfg(r_4_5)]
fn R_getVar(arg1: SEXP, arg2: SEXP) -> SEXP;

#[cfg(not(r_4_5))]
fn Rf_findVarInFrame(arg1: SEXP, arg2: SEXP) -> SEXP;

#[cfg(r_4_5)]
fn R_getVarEx(arg1: SEXP, arg2: SEXP) -> SEXP;

#[cfg(not(r_4_5))]
fn CLOENV(x: SEXP) -> SEXP;

#[cfg(r_4_5)]
fn R_ClosureEnv(x: SEXP) -> SEXP;

#[cfg(not(r_4_5))]
fn BODY(x: SEXP) -> SEXP;

#[cfg(r_4_5)]
fn R_ClosureBody(x: SEXP) -> SEXP;

#[cfg(not(r_4_5))]
fn FORMALS(x: SEXP) -> SEXP;

#[cfg(r_4_5)]
fn R_ClosureFormals(x: SEXP) -> SEXP;

#[cfg(not(r_4_5))]
fn DATAPTR(x: SEXP) -> *mut ::std::os::raw::c_void;

#[cfg(r_4_5)]
fn DATAPTR_RO(x: SEXP) -> *const ::std::os::raw::c_void;

#[cfg(not(r_4_5))]
fn Rf_isFrame(x: SEXP) -> Rboolean;

#[cfg(r_4_5)]
fn Rf_isDataFrame(x: SEXP) -> Rboolean;
}

/// Returns the enclosing environment of env, which will usually be of type ENVSXP, except for the special environment R_EmptyEnv, which terminates the environment chain; its enclosing environment is R_NilValue.
///
/// # Safety
///
/// This function dereferences a raw SEXP pointer.
/// The caller must ensure that `x` is a valid SEXP pointer to an environment.
#[inline]
pub unsafe fn get_parent_env(x: SEXP) -> SEXP {
#[cfg(not(r_4_5))]
{
ENCLOS(x)
}
#[cfg(r_4_5)]
{
R_ParentEnv(x)
}
}

/// Returns a variable from an environment
///
/// # Safety
///
/// This function dereferences raw SEXP pointers.
/// The caller must ensure that `symbol` and `env` are valid SEXP pointers.
#[inline]
pub unsafe fn get_var(symbol: SEXP, env: SEXP) -> SEXP {
#[cfg(not(r_4_5))]
{
Rf_findVar(symbol, env)
}
#[cfg(r_4_5)]
{
R_getVar(symbol, env)
}
}

/// Returns a variable from an environment, or `None` if unbound.
///
/// On R < 4.5, uses `Rf_findVar` and checks against `R_UnboundValue`.
/// On R >= 4.5, uses `R_getVar` which signals an error if not found;
/// callers should wrap this with `catch_r_error` if needed.
///
/// # Safety
///
/// This function dereferences raw SEXP pointers.
/// The caller must ensure that `symbol` and `env` are valid SEXP pointers.
#[inline]
pub unsafe fn get_var_safe(symbol: SEXP, env: SEXP) -> Option<SEXP> {
#[cfg(not(r_4_5))]
{
let var = Rf_findVar(symbol, env);
if var == R_UnboundValue {
None
} else {
Some(var)
}
}
#[cfg(r_4_5)]
{
Some(R_getVar(symbol, env))
}
}

/// Returns the value of the requested variable in an environment
///
/// # Safety
///
/// This function dereferences raw SEXP pointers.
/// The caller must ensure that `symbol` and `env` are valid SEXP pointers.
#[inline]
pub unsafe fn get_var_in_frame(symbol: SEXP, env: SEXP) -> SEXP {
#[cfg(not(r_4_5))]
{
Rf_findVarInFrame(symbol, env)
}
#[cfg(r_4_5)]
{
R_getVarEx(env, symbol)
}
}

/// Return the environment of a closure
///
/// # Safety
///
/// This function dereferences a raw SEXP pointer.
/// The caller must ensure that `x` is a valid SEXP pointer to a closure.
#[inline]
pub unsafe fn get_closure_env(x: SEXP) -> SEXP {
#[cfg(not(r_4_5))]
{
CLOENV(x)
}
#[cfg(r_4_5)]
{
R_ClosureEnv(x)
}
}

/// Return the body of a closure
///
/// # Safety
///
/// This function dereferences a raw SEXP pointer.
/// The caller must ensure that `x` is a valid SEXP pointer to a closure.
#[inline]
pub unsafe fn get_closure_body(x: SEXP) -> SEXP {
#[cfg(not(r_4_5))]
{
BODY(x)
}
#[cfg(r_4_5)]
{
R_ClosureBody(x)
}
}

/// Access a closure's arguments
///
/// # Safety
///
/// This function dereferences a raw SEXP pointer.
/// The caller must ensure that `x` is a valid SEXP pointer to a closure.
#[inline]
pub unsafe fn get_closure_formals(x: SEXP) -> SEXP {
#[cfg(not(r_4_5))]
{
FORMALS(x)
}
#[cfg(r_4_5)]
{
R_ClosureFormals(x)
}
}

/// Access a DATAPTR
///
/// # Safety
///
/// This function dereferences a raw SEXP pointer.
/// The caller must ensure that `x` is a valid SEXP pointer.
#[inline]
pub unsafe fn dataptr(x: SEXP) -> *const ::std::os::raw::c_void {
#[cfg(not(r_4_5))]
{
DATAPTR(x) as *const _
}
#[cfg(r_4_5)]
{
DATAPTR_RO(x)
}
}

/// Check is data.frame
///
/// # Safety
///
/// This function dereferences a raw SEXP pointer.
/// The caller must ensure that `x` is a valid SEXP pointer.
#[inline]
pub unsafe fn is_data_frame(x: SEXP) -> Rboolean {
#[cfg(not(r_4_5))]
{
Rf_isFrame(x)
}
#[cfg(r_4_5)]
{
Rf_isDataFrame(x)
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

pub mod backports;

#[non_exhaustive]
#[repr(transparent)]
#[derive(Debug)]
Expand Down
Loading