Skip to content

Commit

Permalink
Support optional args represented by Option<T> (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
yutannihilation committed May 15, 2024
1 parent 8e0f4e2 commit 8b14d5b
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 30 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@
error when `options(warn = 2)`, so you should not ignore the error from
`r_warn()`. The error should be propagated to the R session.

* Savvy now translates `Option<T>` as an optional argument, i.e., an argument
with the default value of `NULL`.

Example:
``` rust
#[savvy]
fn default_value_vec(x: Option<IntegerSexp>) -> savvy::Result<Sexp> {
if let Some(x) = x {
x.iter().sum::<i32>().try_into()
} else {
(-1).try_into()
}
}
```
``` r
default_value_vec(1:10)
#> [1] 55

default_value_vec()
#> [1] -1
```

## [v0.6.3] (2024-05-05)

### New features
Expand Down
42 changes: 42 additions & 0 deletions R-package/R/000-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,16 @@ rep_str_slice <- function(x) {
}


default_value_scalar <- function(x = NULL) {
.Call(savvy_default_value_scalar__impl, x)
}


default_value_vec <- function(x = NULL) {
.Call(savvy_default_value_vec__impl, x)
}


print_foo_enum <- function(x) {
x <- .savvy_extract_ptr(x, "FooEnum")
invisible(.Call(savvy_print_foo_enum__impl, x))
Expand Down Expand Up @@ -595,6 +605,38 @@ print.FooEnum <- function(x, ...) {



### wrapper functions for FooWithDefault

FooWithDefault_default_value_method <- function(self) {
function(x = NULL) {
.Call(savvy_FooWithDefault_default_value_method__impl, self, x)
}
}

.savvy_wrap_FooWithDefault <- function(ptr) {
e <- new.env(parent = emptyenv())
e$.ptr <- ptr
e$default_value_method <- FooWithDefault_default_value_method(ptr)

class(e) <- "FooWithDefault"
e
}



FooWithDefault <- new.env(parent = emptyenv())

### associated functions for FooWithDefault

FooWithDefault$new <- function(default_value) {
.savvy_wrap_FooWithDefault(.Call(savvy_FooWithDefault_new__impl, default_value))
}

FooWithDefault$default_value_associated_fn <- function(x = NULL) {
.Call(savvy_FooWithDefault_default_value_associated_fn__impl, x)
}


### wrapper functions for Person

Person_another_person <- function(self) {
Expand Down
File renamed without changes.
File renamed without changes.
30 changes: 30 additions & 0 deletions R-package/src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,16 @@ SEXP savvy_rep_str_slice__impl(SEXP x) {
return handle_result(res);
}

SEXP savvy_default_value_scalar__impl(SEXP x) {
SEXP res = savvy_default_value_scalar__ffi(x);
return handle_result(res);
}

SEXP savvy_default_value_vec__impl(SEXP x) {
SEXP res = savvy_default_value_vec__ffi(x);
return handle_result(res);
}

SEXP savvy_print_foo_enum__impl(SEXP x) {
SEXP res = savvy_print_foo_enum__ffi(x);
return handle_result(res);
Expand Down Expand Up @@ -544,6 +554,21 @@ SEXP savvy_FooEnum_print__impl(SEXP self__) {
return handle_result(res);
}

SEXP savvy_FooWithDefault_new__impl(SEXP default_value) {
SEXP res = savvy_FooWithDefault_new__ffi(default_value);
return handle_result(res);
}

SEXP savvy_FooWithDefault_default_value_method__impl(SEXP self__, SEXP x) {
SEXP res = savvy_FooWithDefault_default_value_method__ffi(self__, x);
return handle_result(res);
}

SEXP savvy_FooWithDefault_default_value_associated_fn__impl(SEXP x) {
SEXP res = savvy_FooWithDefault_default_value_associated_fn__ffi(x);
return handle_result(res);
}

SEXP savvy_Person_new__impl(void) {
SEXP res = savvy_Person_new__ffi();
return handle_result(res);
Expand Down Expand Up @@ -698,6 +723,8 @@ static const R_CallMethodDef CallEntries[] = {
{"savvy_rep_bool_slice__impl", (DL_FUNC) &savvy_rep_bool_slice__impl, 1},
{"savvy_rep_str_vec__impl", (DL_FUNC) &savvy_rep_str_vec__impl, 1},
{"savvy_rep_str_slice__impl", (DL_FUNC) &savvy_rep_str_slice__impl, 1},
{"savvy_default_value_scalar__impl", (DL_FUNC) &savvy_default_value_scalar__impl, 1},
{"savvy_default_value_vec__impl", (DL_FUNC) &savvy_default_value_vec__impl, 1},
{"savvy_print_foo_enum__impl", (DL_FUNC) &savvy_print_foo_enum__impl, 1},
{"savvy_print_foo_enum_ref__impl", (DL_FUNC) &savvy_print_foo_enum_ref__impl, 1},
{"savvy_foo_a__impl", (DL_FUNC) &savvy_foo_a__impl, 0},
Expand Down Expand Up @@ -725,6 +752,9 @@ static const R_CallMethodDef CallEntries[] = {
{"savvy_fun_mod1__impl", (DL_FUNC) &savvy_fun_mod1__impl, 0},
{"savvy_fun_mod1_1_foo__impl", (DL_FUNC) &savvy_fun_mod1_1_foo__impl, 0},
{"savvy_FooEnum_print__impl", (DL_FUNC) &savvy_FooEnum_print__impl, 1},
{"savvy_FooWithDefault_new__impl", (DL_FUNC) &savvy_FooWithDefault_new__impl, 1},
{"savvy_FooWithDefault_default_value_method__impl", (DL_FUNC) &savvy_FooWithDefault_default_value_method__impl, 2},
{"savvy_FooWithDefault_default_value_associated_fn__impl", (DL_FUNC) &savvy_FooWithDefault_default_value_associated_fn__impl, 1},
{"savvy_Person_new__impl", (DL_FUNC) &savvy_Person_new__impl, 0},
{"savvy_Person_new2__impl", (DL_FUNC) &savvy_Person_new2__impl, 0},
{"savvy_Person_new_fallible__impl", (DL_FUNC) &savvy_Person_new_fallible__impl, 0},
Expand Down
7 changes: 7 additions & 0 deletions R-package/src/rust/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ SEXP savvy_rep_bool_vec__ffi(SEXP x);
SEXP savvy_rep_bool_slice__ffi(SEXP x);
SEXP savvy_rep_str_vec__ffi(SEXP x);
SEXP savvy_rep_str_slice__ffi(SEXP x);
SEXP savvy_default_value_scalar__ffi(SEXP x);
SEXP savvy_default_value_vec__ffi(SEXP x);
SEXP savvy_print_foo_enum__ffi(SEXP x);
SEXP savvy_print_foo_enum_ref__ffi(SEXP x);
SEXP savvy_foo_a__ffi(void);
Expand Down Expand Up @@ -103,6 +105,11 @@ SEXP savvy_fun_mod1_1_foo__ffi(void);
// methods and associated functions for FooEnum
SEXP savvy_FooEnum_print__ffi(SEXP self__);

// methods and associated functions for FooWithDefault
SEXP savvy_FooWithDefault_new__ffi(SEXP default_value);
SEXP savvy_FooWithDefault_default_value_method__ffi(SEXP self__, SEXP x);
SEXP savvy_FooWithDefault_default_value_associated_fn__ffi(SEXP x);

// methods and associated functions for Person
SEXP savvy_Person_new__ffi(void);
SEXP savvy_Person_new2__ffi(void);
Expand Down
1 change: 1 addition & 0 deletions R-package/src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod error_handling;
mod function;
mod init_vectors;
mod numeric;
mod optional_arg;
mod try_from_iter;

// to test if the definition over multiple files is accepted.
Expand Down
35 changes: 35 additions & 0 deletions R-package/src/rust/src/optional_arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use savvy::{savvy, IntegerSexp, Sexp};

#[savvy]
fn default_value_scalar(x: Option<i32>) -> savvy::Result<Sexp> {
x.unwrap_or(-1).try_into()
}

#[savvy]
fn default_value_vec(x: Option<IntegerSexp>) -> savvy::Result<Sexp> {
if let Some(x) = x {
x.iter().sum::<i32>().try_into()
} else {
(-1).try_into()
}
}

#[savvy]
struct FooWithDefault {
default_value: i32,
}

#[savvy]
impl FooWithDefault {
fn new(default_value: i32) -> Self {
Self { default_value }
}

fn default_value_method(&self, x: Option<i32>) -> savvy::Result<Sexp> {
x.unwrap_or(self.default_value).try_into()
}

fn default_value_associated_fn(x: Option<i32>) -> savvy::Result<Sexp> {
x.unwrap_or(-1).try_into()
}
}
13 changes: 13 additions & 0 deletions R-package/tests/testthat/test-optional_args.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
test_that("optional arg works", {
expect_equal(default_value_scalar(10L), 10L)
expect_equal(default_value_scalar(), -1L)
expect_equal(default_value_vec(1:10), 55L)
expect_equal(default_value_vec(), -1L)

expect_equal(FooWithDefault$default_value_associated_fn(10L), 10L)
expect_equal(FooWithDefault$default_value_associated_fn(), -1L)

x <- FooWithDefault$new(-100L)
expect_equal(x$default_value_method(10L), 10L)
expect_equal(x$default_value_method(), -100L)
})
75 changes: 51 additions & 24 deletions savvy-bindgen/src/gen/r.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,61 @@ impl SavvyFn {
}
}

fn get_r_args(&self) -> Vec<String> {
let mut out: Vec<_> = self.args.iter().map(|arg| arg.pat_string()).collect();
// The return value is (pat, default value)
fn get_r_args(&self) -> Vec<(String, Option<String>)> {
let mut out: Vec<_> = self
.args
.iter()
.map(|arg| {
let pat = arg.pat_string();
let default_value = if arg.is_optional() {
Some("NULL".to_string())
} else {
None
};

(pat, default_value)
})
.collect();

// if it's a method, add `self__` arg
if matches!(&self.fn_type, SavvyFnType::Method { .. }) {
out.insert(0, "self".to_string())
out.insert(0, ("self".to_string(), None))
}

out
}

fn get_r_args_for_signature(&self) -> Vec<String> {
self.get_r_args()
.iter()
.map(|(pat, default_value)| {
if let Some(value) = default_value {
format!(r#"{pat} = {value}"#)
} else {
pat.to_string()
}
})
.collect::<Vec<_>>()
}

fn get_r_args_for_call(&self) -> Vec<String> {
self.get_r_args()
.iter()
.map(|(pat, _)| pat.to_string())
.collect::<Vec<_>>()
}

fn to_r_function(&self) -> String {
let fn_name = self.fn_name_r();
let fn_name_c = self.fn_name_c_impl();

let doc_comments = get_r_doc_comment(self.docs.as_slice());

let args = self
.get_c_args()
.iter()
.map(|(pat, _)| pat.clone())
.collect::<Vec<String>>();
let args_sig = self.get_r_args_for_signature().join(", ");

let mut args_call = args.clone();
let mut args_call = self.get_r_args_for_call();
args_call.insert(0, format!("{fn_name_c}"));

let args = args.join(", ");
let args_call = args_call.join(", ");

let mut body_lines = self.get_extractions();
Expand All @@ -79,7 +107,7 @@ impl SavvyFn {

format!(
"{doc_comments}
{fn_name} <- function({args}) {{
{fn_name} <- function({args_sig}) {{
{body}
}}
"
Expand Down Expand Up @@ -127,17 +155,17 @@ fn generate_r_impl_for_impl(
let fn_name = x.fn_name_r();
let fn_name_c = x.fn_name_c_impl();

let mut args = x.get_r_args();
// Remove self from arguments for R
let args_r = args
.clone()
let args_sig = x
.get_r_args_for_signature()
.into_iter()
.filter(|e| *e != "self")
.collect::<Vec<String>>()
.join(", ");

args.insert(0, format!("{fn_name_c}"));
let args_call = args.join(", ");
let mut args_call = x.get_r_args_for_call();
args_call.insert(0, format!("{fn_name_c}"));
let args_call = args_call.join(", ");

let mut body_lines = x.get_extractions();

Expand All @@ -160,7 +188,7 @@ fn generate_r_impl_for_impl(

format!(
"{fn_name} <- function(self) {{
function({args_r}) {{
function({args_sig}) {{
{body}
}}
}}
Expand Down Expand Up @@ -196,12 +224,11 @@ fn generate_r_impl_for_impl(
let fn_name = x.fn_name.clone();
let fn_name_c = x.fn_name_c_impl();

let mut args = x.get_r_args();

let args_r = args.join(", ");
let args_sig = x.get_r_args_for_signature().join(", ");

args.insert(0, format!("{fn_name_c}"));
let args_call = args.join(", ");
let mut args_call = x.get_r_args_for_call();
args_call.insert(0, format!("{fn_name_c}"));
let args_call = args_call.join(", ");

let mut body_lines = x.get_extractions();

Expand All @@ -224,7 +251,7 @@ fn generate_r_impl_for_impl(
let body = body_lines.join("\n");

format!(
r#"{class_r}${fn_name} <- function({args_r}) {{
r#"{class_r}${fn_name} <- function({args_sig}) {{
{body}
}}
"#
Expand Down
Loading

0 comments on commit 8b14d5b

Please sign in to comment.