Skip to content

Commit 2bf7b6d

Browse files
Fix CRAN docs and harden FFI codegen
1 parent 3e57c38 commit 2bf7b6d

38 files changed

Lines changed: 452 additions & 13 deletions

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: Rtinycc
22
Title: Builds the 'TinyCC' Command-Line Interface and Library for 'C' Scripting in 'R'
3-
Version: 0.1.6
3+
Version: 0.1.7
44
Authors@R: c(
55
person(given = "Sounkou Mahamane", family = "Toure",
66
email = "sounkoutoure@gmail.com", role = c("aut", "cre")),

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# Rtinycc 0.1.7
2+
3+
- Add missing return-value documentation across exported helpers and print
4+
methods so generated reference pages consistently describe result class,
5+
meaning, or side-effect-only behavior.
6+
7+
- Update vignettes to ensure each article executes ordinary R code during
8+
builds, and correct conservative type-mapping docs so `const char *`
9+
examples reflect the default `ptr` mapping.
10+
111
# Rtinycc 0.1.6
212

313
- Exclude the top-level `scripts/` directory more explicitly from package

R/aaa.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@
99
NULL
1010

1111
#' we are using .Call directly, this is to make R CMD check happy
12+
#'
13+
#' @return The result returned by the invoked native routine. This alias is
14+
#' package-internal and exists to satisfy `R CMD check` native routine
15+
#' registration requirements.
16+
#' @keywords internal
1217
.RtinyccCall <- base::.Call

R/callbacks.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ tcc_callback_ptr <- function(callback) {
119119
#'
120120
#' @param x A tcc_callback object
121121
#' @param ... Ignored
122+
#' @return The input `tcc_callback` object, invisibly. Called for its
123+
#' side effect of printing the callback signature, thread-safety flag,
124+
#' and validity status.
122125
#' @export
123126
print.tcc_callback <- function(x, ...) {
124127
sig <- attr(x, "signature")

R/ffi.R

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,9 @@ make_callable <- function(fn_ptr, sym, state) {
18111811
#'
18121812
#' @param x A tcc_ffi object
18131813
#' @param ... Ignored
1814+
#' @return The input `tcc_ffi` object, invisibly. Called for its side
1815+
#' effect of printing the configured output mode, registered symbols,
1816+
#' and selected libraries/include paths.
18141817
#' @export
18151818
print.tcc_ffi <- function(x, ...) {
18161819
cat("<tcc_ffi>\n")
@@ -1836,6 +1839,9 @@ print.tcc_ffi <- function(x, ...) {
18361839
#'
18371840
#' @param x A tcc_compiled object
18381841
#' @param ... Ignored
1842+
#' @return The input `tcc_compiled` object, invisibly. Called for its side
1843+
#' effect of printing the compilation output mode and the status of
1844+
#' compiled callable symbols.
18391845
#' @export
18401846
print.tcc_compiled <- function(x, ...) {
18411847
cat("<tcc_compiled>\n")
@@ -2226,6 +2232,13 @@ tcc_cstring_object <- function(ptr, clone = TRUE, owned = FALSE) {
22262232
obj
22272233
}
22282234

2235+
#' Convert a `tcc_cstring` object to an R string
2236+
#'
2237+
#' @param x A `tcc_cstring` object.
2238+
#' @param ... Ignored.
2239+
#' @return A character scalar containing the string value. Returns the
2240+
#' cached R copy when available; otherwise reads the current NUL-terminated
2241+
#' C string from `x$ptr`.
22292242
#' @export
22302243
as.character.tcc_cstring <- function(x, ...) {
22312244
if (!is.null(x$cached_string)) {
@@ -2234,6 +2247,12 @@ as.character.tcc_cstring <- function(x, ...) {
22342247
tcc_read_cstring(x$ptr)
22352248
}
22362249

2250+
#' Print a `tcc_cstring` object
2251+
#'
2252+
#' @param x A `tcc_cstring` object.
2253+
#' @param ... Ignored.
2254+
#' @return The input `tcc_cstring` object, invisibly. Called for its side
2255+
#' effect of printing the current string value.
22372256
#' @export
22382257
print.tcc_cstring <- function(x, ...) {
22392258
str <- as.character(x)

R/ffi_codegen.R

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,8 +1088,51 @@ union_field_setter_lines <- function(type_name, mem_name, size) {
10881088
union_field_setter_rule(type_name, mem_name, size)
10891089
}
10901090

1091+
ffi_input_array_check_rule <- function(ffi_type, arg_name, r_name) {
1092+
check_line <- switch(
1093+
ffi_type,
1094+
raw = sprintf(
1095+
" if (TYPEOF(%s) != RAWSXP) Rf_error(\"expected raw vector for argument '%s'\");",
1096+
r_name,
1097+
arg_name
1098+
),
1099+
integer_array = sprintf(
1100+
" if (TYPEOF(%s) != INTSXP) Rf_error(\"expected integer vector for argument '%s'\");",
1101+
r_name,
1102+
arg_name
1103+
),
1104+
numeric_array = sprintf(
1105+
" if (TYPEOF(%s) != REALSXP) Rf_error(\"expected numeric vector for argument '%s'\");",
1106+
r_name,
1107+
arg_name
1108+
),
1109+
logical_array = sprintf(
1110+
" if (TYPEOF(%s) != LGLSXP) Rf_error(\"expected logical vector for argument '%s'\");",
1111+
r_name,
1112+
arg_name
1113+
),
1114+
character_array = sprintf(
1115+
" if (!Rf_isString(%s)) Rf_error(\"expected character vector for argument '%s'\");",
1116+
r_name,
1117+
arg_name
1118+
),
1119+
NULL
1120+
)
1121+
1122+
if (is.null(check_line)) {
1123+
return(NULL)
1124+
}
1125+
1126+
paste(c(check_line, ffi_input_rule(ffi_type, arg_name, r_name)), collapse = "\n")
1127+
}
1128+
10911129
ffi_input_special_rule <- function(ffi_type, arg_name, r_name) {
10921130
special_map <- list(
1131+
raw = function() ffi_input_array_check_rule(ffi_type, arg_name, r_name),
1132+
integer_array = function() ffi_input_array_check_rule(ffi_type, arg_name, r_name),
1133+
numeric_array = function() ffi_input_array_check_rule(ffi_type, arg_name, r_name),
1134+
logical_array = function() ffi_input_array_check_rule(ffi_type, arg_name, r_name),
1135+
character_array = function() ffi_input_array_check_rule(ffi_type, arg_name, r_name),
10931136
"enum:" = function() ffi_input_enum_rule(ffi_type, arg_name, r_name),
10941137
"callback" = function() ffi_input_callback_rule(ffi_type, arg_name, r_name)
10951138
)

R/tcc_ptr_utils.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#' These utilities handle pointer creation, buffer management, and memory operations
99
#' that are commonly needed when working with external libraries.
1010
#'
11+
#' @return No return value. This is a documentation topic that groups the
12+
#' pointer and buffer utility functions described below.
1113
#' @name tcc_ptr_utils
1214
NULL
1315

@@ -274,62 +276,80 @@ tcc_write_i8 <- function(ptr, offset, value) {
274276

275277
#' Write an unsigned 8-bit integer
276278
#' @inheritParams tcc_write_i8
279+
#' @return `NULL` (invisibly). Called for its side effect of writing one
280+
#' unsigned 8-bit value into native memory at `ptr + offset`.
277281
#' @export
278282
tcc_write_u8 <- function(ptr, offset, value) {
279283
invisible(.Call(RC_write_u8, ptr, offset, value))
280284
}
281285

282286
#' Write a signed 16-bit integer
283287
#' @inheritParams tcc_write_i8
288+
#' @return `NULL` (invisibly). Called for its side effect of writing one
289+
#' signed 16-bit value into native memory at `ptr + offset`.
284290
#' @export
285291
tcc_write_i16 <- function(ptr, offset, value) {
286292
invisible(.Call(RC_write_i16, ptr, offset, value))
287293
}
288294

289295
#' Write an unsigned 16-bit integer
290296
#' @inheritParams tcc_write_i8
297+
#' @return `NULL` (invisibly). Called for its side effect of writing one
298+
#' unsigned 16-bit value into native memory at `ptr + offset`.
291299
#' @export
292300
tcc_write_u16 <- function(ptr, offset, value) {
293301
invisible(.Call(RC_write_u16, ptr, offset, value))
294302
}
295303

296304
#' Write a signed 32-bit integer
297305
#' @inheritParams tcc_write_i8
306+
#' @return `NULL` (invisibly). Called for its side effect of writing one
307+
#' signed 32-bit value into native memory at `ptr + offset`.
298308
#' @export
299309
tcc_write_i32 <- function(ptr, offset, value) {
300310
invisible(.Call(RC_write_i32, ptr, offset, value))
301311
}
302312

303313
#' Write an unsigned 32-bit integer
304314
#' @inheritParams tcc_write_i8
315+
#' @return `NULL` (invisibly). Called for its side effect of writing one
316+
#' unsigned 32-bit value into native memory at `ptr + offset`.
305317
#' @export
306318
tcc_write_u32 <- function(ptr, offset, value) {
307319
invisible(.Call(RC_write_u32, ptr, offset, value))
308320
}
309321

310322
#' Write a signed 64-bit integer
311323
#' @inheritParams tcc_write_i8
324+
#' @return `NULL` (invisibly). Called for its side effect of writing one
325+
#' signed 64-bit value into native memory at `ptr + offset`.
312326
#' @export
313327
tcc_write_i64 <- function(ptr, offset, value) {
314328
invisible(.Call(RC_write_i64, ptr, offset, value))
315329
}
316330

317331
#' Write an unsigned 64-bit integer
318332
#' @inheritParams tcc_write_i8
333+
#' @return `NULL` (invisibly). Called for its side effect of writing one
334+
#' unsigned 64-bit value into native memory at `ptr + offset`.
319335
#' @export
320336
tcc_write_u64 <- function(ptr, offset, value) {
321337
invisible(.Call(RC_write_u64, ptr, offset, value))
322338
}
323339

324340
#' Write a 32-bit float
325341
#' @inheritParams tcc_write_i8
342+
#' @return `NULL` (invisibly). Called for its side effect of writing one
343+
#' 32-bit floating-point value into native memory at `ptr + offset`.
326344
#' @export
327345
tcc_write_f32 <- function(ptr, offset, value) {
328346
invisible(.Call(RC_write_f32, ptr, offset, value))
329347
}
330348

331349
#' Write a 64-bit double
332350
#' @inheritParams tcc_write_i8
351+
#' @return `NULL` (invisibly). Called for its side effect of writing one
352+
#' 64-bit floating-point value into native memory at `ptr + offset`.
333353
#' @export
334354
tcc_write_f64 <- function(ptr, offset, value) {
335355
invisible(.Call(RC_write_f64, ptr, offset, value))

R/treesitter_helpers.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ tcc_map_c_type_to_ffi <- function(c_type) {
249249
return(type_str)
250250
}
251251
last <- parts[length(parts)]
252+
if (last %in% c("*", "**") || grepl("^\\*+", last)) {
253+
return(type_str)
254+
}
252255
type_tokens <- c(
253256
"void",
254257
"bool",

inst/tinytest/test_codegen_soundness.R

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,47 @@ expect_equal(
350350
info = "logical_array round-trip preserves all values"
351351
)
352352

353+
# Array input wrappers reject wrong SEXPTYPE before borrowing vector storage.
354+
ffi <- tcc_ffi() |>
355+
tcc_source(
356+
"
357+
void touch_raw(unsigned char* buf) { (void) buf; }
358+
void touch_ints(int* x) { (void) x; }
359+
void touch_nums(double* x) { (void) x; }
360+
void touch_lgl(int* x) { (void) x; }
361+
void touch_chars(SEXP* x) { (void) x; }
362+
"
363+
) |>
364+
tcc_bind(
365+
touch_raw = list(args = list("raw"), returns = "void"),
366+
touch_ints = list(args = list("integer_array"), returns = "void"),
367+
touch_nums = list(args = list("numeric_array"), returns = "void"),
368+
touch_lgl = list(args = list("logical_array"), returns = "void"),
369+
touch_chars = list(args = list("character_array"), returns = "void")
370+
) |>
371+
tcc_compile()
372+
373+
expect_error(
374+
ffi$touch_raw(1:3),
375+
info = "raw input rejects non-raw vectors before borrowing"
376+
)
377+
expect_error(
378+
ffi$touch_ints(c(1, 2, 3)),
379+
info = "integer_array input rejects non-integer vectors before borrowing"
380+
)
381+
expect_error(
382+
ffi$touch_nums(1:3),
383+
info = "numeric_array input rejects non-numeric vectors before borrowing"
384+
)
385+
expect_error(
386+
ffi$touch_lgl(c(1L, 0L, 1L)),
387+
info = "logical_array input rejects non-logical vectors before borrowing"
388+
)
389+
expect_error(
390+
ffi$touch_chars(list("a", "b")),
391+
info = "character_array input rejects non-character vectors before borrowing"
392+
)
393+
353394
# NULL array return -> R_NilValue
354395
ffi <- tcc_ffi() |>
355396
tcc_source(
@@ -369,6 +410,32 @@ expect_true(
369410
info = "NULL array return gives R NULL"
370411
)
371412

413+
# Zero-length array returns still round-trip as empty R vectors when C returns
414+
# a non-NULL buffer. This guards the wrapper path used in the benchmark
415+
# vignette, where length 0 should yield numeric(0) rather than NULL.
416+
ffi <- tcc_ffi() |>
417+
tcc_source(
418+
"
419+
#include <stdlib.h>
420+
double* zero_vec(int n) {
421+
if (n != 0) return NULL;
422+
return (double*) malloc(sizeof(double));
423+
}
424+
"
425+
) |>
426+
tcc_bind(
427+
zero_vec = list(
428+
args = list("i32"),
429+
returns = list(type = "numeric_array", length_arg = 1, free = TRUE)
430+
)
431+
) |>
432+
tcc_compile()
433+
expect_equal(
434+
ffi$zero_vec(0L),
435+
numeric(0),
436+
info = "zero-length array return with non-NULL buffer yields empty R vector"
437+
)
438+
372439
# ===========================================================================
373440
# 4. STRUCT FIELD ROUND-TRIPS: set field, read it back for each type
374441
# ===========================================================================
@@ -1086,6 +1153,23 @@ expect_equal(
10861153
)
10871154
expect_equal(r, "hello", info = "cstring return: correct value")
10881155

1156+
# Regression: const-qualified char* returns must compile and box correctly.
1157+
ffi <- tcc_ffi() |>
1158+
tcc_source(
1159+
"
1160+
const char* const_message(void) {
1161+
return \"const-ok\";
1162+
}
1163+
"
1164+
) |>
1165+
tcc_bind(const_message = list(args = list(), returns = "cstring")) |>
1166+
tcc_compile()
1167+
expect_equal(
1168+
ffi$const_message(),
1169+
"const-ok",
1170+
info = "const char* return compiles and boxes into an R string"
1171+
)
1172+
10891173
# wrapper argument single-eval
10901174
ffi <- tcc_ffi() |>
10911175
tcc_source("int add3(int a, int b, int c) { return a + b + c; }") |>

inst/tinytest/test_ffi_codegen.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,23 @@ expect_true(grepl("bool flag = (bool)(_flag != 0)", code, fixed = TRUE))
2020

2121
# Test 2: Generate C input for array types
2222
code <- Rtinycc:::generate_c_input("buf", "arg1_", "raw")
23+
expect_true(grepl("TYPEOF\\(arg1_\\) != RAWSXP", code))
2324
expect_true(grepl("uint8_t[*] buf = RAW", code))
2425

2526
code <- Rtinycc:::generate_c_input("arr", "arg1_", "integer_array")
27+
expect_true(grepl("TYPEOF\\(arg1_\\) != INTSXP", code))
2628
expect_true(grepl("int32_t[*] arr = INTEGER", code))
2729

2830
code <- Rtinycc:::generate_c_input("nums", "arg1_", "numeric_array")
31+
expect_true(grepl("TYPEOF\\(arg1_\\) != REALSXP", code))
2932
expect_true(grepl("double[*] nums = REAL", code))
3033

34+
code <- Rtinycc:::generate_c_input("flags", "arg1_", "logical_array")
35+
expect_true(grepl("TYPEOF\\(arg1_\\) != LGLSXP", code))
36+
expect_true(grepl("int[*] flags = LOGICAL", code))
37+
3138
code <- Rtinycc:::generate_c_input("chars", "arg1_", "character_array")
39+
expect_true(grepl("Rf_isString\\(arg1_\\)", code))
3240
expect_true(grepl("STRING_PTR_RO", code, fixed = TRUE))
3341

3442
# Test 3: Generate C return code

0 commit comments

Comments
 (0)