Support Typed Jerboa string returns
ober
2d1d97d785dd1dc150bc52c174fce26c43df8743
--- a/docs/jerboa-to-rust.md +++ b/docs/jerboa-to-rust.md @@ -233,13 +233,16 @@ should get separate ABI wrappers. Current landing: scalar ABI-safe exported functions get generated Rust `extern "C"` wrappers, and `.ss` Jerboa wrapper files call those symbols through Chez `foreign-procedure`. `String` and `Bytes` arguments can cross this -boundary for scalar-return functions as bytevector-plus-length pairs, with -`String` using UTF-8 conversion. Same-module record and variant values can cross -exported typed function boundaries as opaque `u64` handles stored in a generated -Rust registry and tagged by the generated Jerboa wrappers. Other non-scalar -returns, cross-module owned values, and conversion records remain future work. -ABI wrappers catch Rust panics before they cross Chez FFI and currently return -conservative default values; structured error returns are still future work. +boundary as bytevector-plus-length pairs, with `String` using UTF-8 conversion. +`String` and `Bytes` return values cross back through out pointer/length +parameters, a generated Rust-owned byte buffer, and a generated destructor that +the Scheme wrapper calls after copying into Chez-owned memory. Same-module +record and variant values can cross exported typed function boundaries as +opaque `u64` handles stored in a generated Rust registry and tagged by the +generated Jerboa wrappers. Other non-scalar returns, cross-module owned values, +and conversion records remain future work. ABI wrappers catch Rust panics before +they cross Chez FFI and currently return conservative default values; +structured error returns are still future work. ## Generics @@ -350,8 +353,9 @@ pub struct JtString { For strings, define clear ownership: -- Jerboa-to-Rust string arguments are copied in the MVP. -- Rust-to-Jerboa strings are returned with a destructor function. +- Jerboa-to-Rust string arguments are copied from generated UTF-8 bytevectors. +- Rust-to-Jerboa strings are returned as Rust-owned byte buffers, copied by the + generated Scheme wrapper, and then released through `jt_byte_buffer_free`. - Later, add borrowed string views with lifetime restrictions. ## Generated Jerboa Wrappers @@ -584,8 +588,9 @@ Second module: typed `rope`. `(Bytes -> Nat)` builtins. - Generate wrappers. Initial scalar `.ss` wrappers plus `String` and `Bytes` argument wrappers landed. Same-module record and variant values now cross the - wrapper boundary as opaque handles. String/bytes return, option, result, and - richer handle conversions remain future work. + wrapper boundary as opaque handles. `String` and `Bytes` returns now cross + through generated byte buffer ownership helpers. Option, result, and richer + handle conversions remain future work. ### Milestone 3: Records and Variants --- a/docs/typed-jerboa.md +++ b/docs/typed-jerboa.md @@ -163,15 +163,18 @@ Current landing: validate dynamic arguments, call `foreign-procedure`, convert `Char` values through unsigned code points, and pass `String` arguments as UTF-8 bytevector-plus-length pairs and `Bytes` arguments as bytevector-plus-length - pairs for scalar-return functions. + pairs. `String` and `Bytes` return values now cross back through a generated + Rust-owned byte buffer plus destructor; the Scheme wrapper copies the bytes + into Chez-owned memory and frees the Rust buffer. - Exported typed `def` functions can now cross records and variants through an opaque `u64` handle registry when the record or variant type is declared in the same typed module. Generated Scheme wrappers tag handles by typed name and reject wrong-handle calls before FFI. - `make typed-wrapper-smoke` builds the primitive Rust fixture, generates its wrapper, loads the cdylib, and calls the generated Jerboa functions through - Chez FFI. It also checks that bad dynamic calls are rejected by generated - wrapper predicates before crossing the FFI boundary. + Chez FFI, including returned strings, returned bytevectors, and opaque + record/variant handles. It also checks that bad dynamic calls are rejected by + generated wrapper predicates before crossing the FFI boundary. - `support/typed-rust.ss`, `make typed-rust`, and `make typed-build` generate a disposable Cargo crate under `build/typed/rust`; `typed-build` also writes wrappers under `build/typed/jerboa` and runs `cargo build` against the @@ -821,12 +824,13 @@ Minimum excluded features: - Compile generated Rust as a static or dynamic library. Initial disposable `rlib`/`staticlib`/`cdylib` builds landed. - Generate conversion functions. Initial scalar argument checks, `Char` - code-point conversions, `String` argument UTF-8 bytevector conversions, and - `Bytes` argument bytevector conversions landed at the wrapper boundary. + code-point conversions, `String` argument/return UTF-8 bytevector + conversions, and `Bytes` argument/return bytevector conversions landed at the + wrapper boundary. - Generate Jerboa wrappers. Initial `.ss` wrapper generation landed for - scalar ABI-safe exported functions, plus `String` and `Bytes` arguments with - scalar returns. Same-module record and variant values can cross exported - typed `def` boundaries as opaque handles. + scalar ABI-safe exported functions, plus `String` and `Bytes` arguments and + returns. Same-module record and variant values can cross exported typed + `def` boundaries as opaque handles. ### Milestone 4: First Real Module --- a/lib/jerboa/typed/rust.ss +++ b/lib/jerboa/typed/rust.ss @@ -318,9 +318,15 @@ (lookup-name type (*rust-variant-env*))) #t)) + (def (abi-return-buffer-type? type) + (and (symbol? type) + (memq type '(String Bytes)) + #t)) + (def (abi-safe-return-type? type) (or (abi-safe-type? type) - (abi-handle-type? type))) + (abi-handle-type? type) + (abi-return-buffer-type? type))) (def (abi-safe-param-type? type) (or (abi-safe-type? type) @@ -420,6 +426,34 @@ (write-line port 0 "}") (newline port)) + (def (emit-byte-buffer-runtime port) + (write-line port 0 "fn jt_return_bytes(bytes: Vec<u8>, out_ptr: *mut *mut u8, out_len: *mut usize) -> bool {") + (write-line port 1 "if out_ptr.is_null() || out_len.is_null() {") + (write-line port 2 "return false;") + (write-line port 1 "}") + (write-line port 1 "let mut boxed = bytes.into_boxed_slice();") + (write-line port 1 "let len = boxed.len();") + (write-line port 1 "let ptr = boxed.as_mut_ptr();") + (write-line port 1 "std::mem::forget(boxed);") + (write-line port 1 "// unsafe: out pointers are provided by the generated Jerboa wrapper.") + (write-line port 1 "unsafe {") + (write-line port 2 "*out_ptr = ptr;") + (write-line port 2 "*out_len = len;") + (write-line port 1 "}") + (write-line port 1 "true") + (write-line port 0 "}") + (newline port) + (write-line port 0 "#[unsafe(no_mangle)]") + (write-line port 0 "pub extern \"C\" fn jt_byte_buffer_free(ptr: *mut u8, len: usize) {") + (write-line port 1 "if !ptr.is_null() {") + (write-line port 2 "// unsafe: ptr/len must be a buffer returned by jt_return_bytes.") + (write-line port 2 "unsafe {") + (write-line port 3 "drop(std::boxed::Box::from_raw(std::slice::from_raw_parts_mut(ptr, len)));") + (write-line port 2 "}") + (write-line port 1 "}") + (write-line port 0 "}") + (newline port)) + (def (abi-def-uses-handles? def) (or (abi-handle-type? (typed-def-return-type def)) (let loop ([params (typed-def-params def)]) @@ -440,6 +474,38 @@ [else (loop (cdr decls))]))) + (def (module-needs-byte-buffer-runtime? module) + (let loop ([decls (typed-module-declarations module)]) + (cond + [(null? decls) #f] + [(and (typed-def? (car decls)) + (exported-def? module (car decls)) + (abi-safe-def? (car decls)) + (abi-return-buffer-type? (typed-def-return-type (car decls)))) + #t] + [else + (loop (cdr decls))]))) + + (def (modules-need-handle-registry? modules) + (let loop ([rest modules]) + (cond + [(null? rest) #f] + [(module-needs-handle-registry? (car rest)) #t] + [else (loop (cdr rest))]))) + + (def (modules-need-byte-buffer-runtime? modules) + (let loop ([rest modules]) + (cond + [(null? rest) #f] + [(module-needs-byte-buffer-runtime? (car rest)) #t] + [else (loop (cdr rest))]))) + + (def (emit-runtime-helpers modules port) + (when (modules-need-handle-registry? modules) + (emit-handle-registry port)) + (when (modules-need-byte-buffer-runtime? modules) + (emit-byte-buffer-runtime port))) + (def (emit-abi-param-conversion param port) (let ([name (rust-symbol-name (typed-param-name param))]) (case (typed-param-type param) @@ -494,6 +560,15 @@ [(abi-handle-type? return-type) (string-append "jt_store_handle(" call ")")] [else call]))) + (def (abi-return-buffer-expression def call) + (case (typed-def-return-type def) + [(String) + (string-append "jt_return_bytes(" call ".into_bytes(), out_ptr, out_len)")] + [(Bytes) + (string-append "jt_return_bytes(" call ", out_ptr, out_len)")] + [else (error 'typed-rust "unsupported ABI return buffer type" + (typed-def-return-type def))])) + (def (abi-default-expression type) (case type [(Unit) "()"] @@ -511,6 +586,8 @@ (def (emit-abi-wrapper module def port) (when (abi-safe-def? def) (let* ([params (typed-def-params def)] + [return-type (typed-def-return-type def)] + [return-buffer? (abi-return-buffer-type? return-type)] [param-names (map (lambda (param) (rust-symbol-name (typed-param-name param))) params)] @@ -519,16 +596,24 @@ (rust-symbol-name (typed-def-name def)) "(" (join-strings param-names ", ") - ")")]) + ")")] + [abi-params + (append + (append-map emit-abi-param params) + (if return-buffer? + '("out_ptr: *mut *mut u8" "out_len: *mut usize") + '()))]) (write-line port 0 "#[unsafe(no_mangle)]") (write-line port 0 (string-append "pub extern \"C\" fn " (abi-wrapper-name module def) "(" - (join-strings (append-map emit-abi-param params) ", ") + (join-strings abi-params ", ") ") -> " - (abi-rust-type (typed-def-return-type def)) + (if return-buffer? + "bool" + (abi-rust-type return-type)) " {")) (write-line port 1 "match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {") @@ -536,13 +621,18 @@ (lambda (param) (emit-abi-param-conversion param port)) params) - (write-line port 2 (abi-return-expression def call)) + (write-line port 2 + (if return-buffer? + (abi-return-buffer-expression def call) + (abi-return-expression def call))) (write-line port 1 "})) {") (write-line port 2 "Ok(value) => value,") (write-line port 2 (string-append "Err(_) => " - (abi-default-expression (typed-def-return-type def)) + (if return-buffer? + "false" + (abi-default-expression return-type)) ",")) (write-line port 1 "}") (write-line port 0 "}") @@ -767,6 +857,20 @@ [(char? expr) (rust-char-literal expr)] [(string? expr) (string-append "String::from(" (rust-string-literal expr) ")")] + [(bytevector? expr) + (let ([len (bytevector-length expr)]) + (string-append + "vec![" + (let loop ([i 0] [out '()]) + (if (= i len) + (join-strings (reverse out) ", ") + (loop (+ i 1) + (cons + (string-append + (number->string (bytevector-u8-ref expr i)) + "u8") + out)))) + "]"))] [(integer? expr) (if (>= expr 0) (string-append (number->string expr) "u64") @@ -829,8 +933,6 @@ (newline port)) (def (emit-module-declarations module port) - (when (module-needs-handle-registry? module) - (emit-handle-registry port)) (for-each (lambda (decl) (emit-declaration decl port)) @@ -844,6 +946,7 @@ (def (emit-module module port) (emit-rust-header port) + (emit-runtime-helpers (list module) port) (emit-module-declarations module port)) (def (typed-module->rust-string module) @@ -875,6 +978,7 @@ (emit-to-string (lambda (port) (emit-rust-header port) + (emit-runtime-helpers modules port) (for-each (lambda (module) (emit-module-declarations module port)) --- a/lib/jerboa/typed/wrapper.ss +++ b/lib/jerboa/typed/wrapper.ss @@ -109,9 +109,15 @@ (memq type '(Unit Bool Char Int Nat Fixnum Float)) #t)) + (def (abi-wrapper-return-buffer-type? type) + (and (symbol? type) + (memq type '(String Bytes)) + #t)) + (def (wrapper-safe-return-type? module type) (or (abi-wrapper-scalar-type? type) - (module-handle-type? module type))) + (module-handle-type? module type) + (abi-wrapper-return-buffer-type? type))) (def (wrapper-safe-param-type? module type) (or (abi-wrapper-scalar-type? type) @@ -128,9 +134,10 @@ [else #f])))) (def (abi-chez-return-type module type) - (if (module-handle-type? module type) - 'unsigned-64 - (abi-chez-type type))) + (cond + [(abi-wrapper-return-buffer-type? type) 'boolean] + [(module-handle-type? module type) 'unsigned-64] + [else (abi-chez-type type)])) (def (abi-chez-argument-types module type) (case type @@ -150,6 +157,13 @@ (loop (cdr rest) (cons (car rest) out))] [else (loop (cdr rest) out)]))) + (def (module-needs-return-buffer? module) + (let loop ([defs (wrapper-defs module)]) + (cond + [(null? defs) #f] + [(abi-wrapper-return-buffer-type? (typed-def-return-type (car defs))) #t] + [else (loop (cdr defs))]))) + (def (ffi-binding-name def) (string->symbol (string-append "%" (rust-symbol-name (typed-def-name def))))) @@ -227,24 +241,38 @@ (list (string-append "(%typed-rust-handle-id " name ")")) (list name))]))) - (def (wrapper-call-expression module def) + (def (wrapper-call-expression* module def extra-args) (string-append "(" (datum->code (ffi-binding-name def)) - (let ([args (append-map - (lambda (param) - (wrapper-argument-expressions module param)) - (typed-def-params def))]) + (let ([args (append + (append-map + (lambda (param) + (wrapper-argument-expressions module param)) + (typed-def-params def)) + extra-args)]) (if (null? args) "" (string-append " " (join-strings args " ")))) ")")) + (def (wrapper-call-expression module def) + (wrapper-call-expression* module def '())) + (def (wrapper-return-expression module def) (let ([call (wrapper-call-expression module def)] [return-type (typed-def-return-type def)]) (cond [(eq? return-type 'Char) (string-append "(integer->char " call ")")] + [(abi-wrapper-return-buffer-type? return-type) + (string-append + (if (eq? return-type 'String) + "(%typed-rust-return-string " + "(%typed-rust-return-bytes ") + (quoted-symbol-code (typed-def-name def)) + " (lambda (%out_ptr %out_len) " + (wrapper-call-expression* module def '("%out_ptr" "%out_len")) + "))")] [(module-handle-type? module return-type) (string-append "(%typed-rust-make-handle " @@ -265,11 +293,15 @@ (datum->code (abi-wrapper-name module def)) " (" (join-strings - (append-map - (lambda (param) - (map datum->code - (abi-chez-argument-types module (typed-param-type param)))) - (typed-def-params def)) + (append + (append-map + (lambda (param) + (map datum->code + (abi-chez-argument-types module (typed-param-type param)))) + (typed-def-params def)) + (if (abi-wrapper-return-buffer-type? (typed-def-return-type def)) + '("void*" "void*") + '())) " ") ") " (datum->code (abi-chez-return-type module (typed-def-return-type def))) @@ -324,10 +356,14 @@ (write-line port 0 ")") (newline port))) - (def (emit-wrapper-header port) + (def (emit-wrapper-header needs-return-buffer? port) (write-line port 0 ";; Generated by Jerboa's typed wrapper backend. Do not edit.") (write-line port 0 "(import (jerboa prelude)") - (write-line port 0 " (only (chezscheme) foreign-procedure getenv load-shared-object))") + (if needs-return-buffer? + (begin + (write-line port 0 " (only (chezscheme) foreign-procedure getenv load-shared-object") + (write-line port 0 " foreign-alloc foreign-free foreign-ref foreign-set! foreign-sizeof))")) + (write-line port 0 " (only (chezscheme) foreign-procedure getenv load-shared-object))")) (newline port) (write-line port 0 "(def %typed-rust-library-path (getenv \"JERBOA_TYPED_RUST_LIB\"))") (write-line port 0 "(when %typed-rust-library-path") @@ -359,10 +395,50 @@ (newline port) (write-line port 0 "(def (%typed-rust-handle-id value)") (write-line port 1 "(vector-ref value 2))") - (newline port)) + (if needs-return-buffer? + (begin + (newline port) + (write-line port 0 "(def %typed-rust-byte-buffer-free") + (write-line port 1 "(foreign-procedure \"jt_byte_buffer_free\" (void* size_t) void))") + (newline port) + (write-line port 0 "(def (%typed-rust-copy-byte-buffer ptr len)") + (write-line port 1 "(let ([out (make-bytevector len)])") + (write-line port 2 "(let loop ([i 0])") + (write-line port 3 "(when (< i len)") + (write-line port 4 "(bytevector-u8-set! out i (foreign-ref 'unsigned-8 ptr i))") + (write-line port 4 "(loop (+ i 1))))") + (write-line port 2 "out))") + (newline port) + (write-line port 0 "(def (%typed-rust-take-byte-buffer ptr len)") + (write-line port 1 "(dynamic-wind") + (write-line port 2 "(lambda () #f)") + (write-line port 2 "(lambda () (%typed-rust-copy-byte-buffer ptr len))") + (write-line port 2 "(lambda () (%typed-rust-byte-buffer-free ptr len))))") + (newline port) + (write-line port 0 "(def (%typed-rust-return-bytes who thunk)") + (write-line port 1 "(let ([ptr-box (foreign-alloc (foreign-sizeof 'void*))]") + (write-line port 2 "[len-box (foreign-alloc (foreign-sizeof 'size_t))])") + (write-line port 2 "(dynamic-wind") + (write-line port 3 "(lambda () #f)") + (write-line port 3 "(lambda ()") + (write-line port 4 "(foreign-set! 'void* ptr-box 0 0)") + (write-line port 4 "(foreign-set! 'size_t len-box 0 0)") + (write-line port 4 "(unless (thunk ptr-box len-box)") + (write-line port 5 "(error who \"typed Rust call failed\"))") + (write-line port 4 "(%typed-rust-take-byte-buffer") + (write-line port 5 "(foreign-ref 'void* ptr-box 0)") + (write-line port 5 "(foreign-ref 'size_t len-box 0)))") + (write-line port 3 "(lambda ()") + (write-line port 4 "(foreign-free ptr-box)") + (write-line port 4 "(foreign-free len-box)))))") + (newline port) + (write-line port 0 "(def (%typed-rust-return-string who thunk)") + (write-line port 1 "(utf8->string (%typed-rust-return-bytes who thunk)))") + (newline port)) + (newline port))) (def (emit-module-wrapper module port) - (emit-wrapper-header port) + (emit-wrapper-header (module-needs-return-buffer? module) port) (for-each (lambda (def) (emit-ffi-binding module def port)) --- a/tests/fixtures/typed/rust-basic.ss +++ b/tests/fixtures/typed/rust-basic.ss @@ -1,6 +1,6 @@ (typed-library (sample typed rust-basic) - (export zero add-one positive? choose greeting double-add text-length bytes-length - make-box box-value make-some token-size) + (export zero add-one positive? choose greeting echo-text double-add text-length + bytes-length echo-bytes make-box box-value make-some token-size) (record Box ((value : Nat))) @@ -24,6 +24,9 @@ (def (greeting) : String "hello") + (def (echo-text (text : String)) : String + text) + (def (double-add (x : Nat)) : Nat (let ((y (+ x x))) (+ y 1))) @@ -34,6 +37,9 @@ (def (bytes-length (data : Bytes)) : Nat (bytevector-length data)) + (def (echo-bytes (data : Bytes)) : Bytes + data) + (def (make-box (value : Nat)) : Box (make-Box value)) --- a/tests/test-typed-rust.ss +++ b/tests/test-typed-rust.ss @@ -83,6 +83,19 @@ (define bytes-rust "// Generated by Jerboa's typed Rust backend. Do not edit.\n#![deny(unsafe_op_in_unsafe_fn)]\n#![allow(unused_parens)]\n#![allow(unused_variables)]\n\npub fn bytes_length(data: Vec<u8>) -> u64 {\n (data).len() as u64\n}\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn jt_sample_typed_bytes_bytes_length(data_ptr: *const u8, data_len: usize) -> u64 {\n match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n let data = {\n let bytes: &[u8] = if data_ptr.is_null() {\n &[]\n } else {\n // unsafe: pointer and length are produced by the generated Jerboa wrapper.\n unsafe { std::slice::from_raw_parts(data_ptr, data_len) }\n };\n bytes.to_vec()\n };\n bytes_length(data)\n })) {\n Ok(value) => value,\n Err(_) => 0u64,\n }\n}\n\n") +(define return-form + '(typed-library (sample typed return-values) + (export greeting echo-text echo-bytes) + (def (greeting) : String + "hello") + (def (echo-text (text : String)) : String + text) + (def (echo-bytes (data : Bytes)) : Bytes + data))) + +(define return-rust + (typed-library-form->rust-string return-form)) + (define ops-form '(typed-library (sample typed ops) (export) @@ -154,6 +167,33 @@ (typed-library-form->rust-string bytes-form) bytes-rust) +(test "rust emits return byte buffer runtime" + (and (substring? return-rust + "fn jt_return_bytes(bytes: Vec<u8>, out_ptr: *mut *mut u8, out_len: *mut usize) -> bool") + (substring? return-rust + "pub extern \"C\" fn jt_byte_buffer_free(ptr: *mut u8, len: usize)") + (substring? return-rust + "std::boxed::Box::from_raw(std::slice::from_raw_parts_mut(ptr, len))")) + #t) + +(test "rust emits string return ABI wrappers" + (and (substring? return-rust + "pub extern \"C\" fn jt_sample_typed_return_values_greeting(out_ptr: *mut *mut u8, out_len: *mut usize) -> bool") + (substring? return-rust + "jt_return_bytes(greeting().into_bytes(), out_ptr, out_len)") + (substring? return-rust + "pub extern \"C\" fn jt_sample_typed_return_values_echo_text(text_ptr: *const u8, text_len: usize, out_ptr: *mut *mut u8, out_len: *mut usize) -> bool") + (substring? return-rust + "jt_return_bytes(echo_text(text).into_bytes(), out_ptr, out_len)")) + #t) + +(test "rust emits bytes return ABI wrappers" + (and (substring? return-rust + "pub extern \"C\" fn jt_sample_typed_return_values_echo_bytes(data_ptr: *const u8, data_len: usize, out_ptr: *mut *mut u8, out_len: *mut usize) -> bool") + (substring? return-rust + "jt_return_bytes(echo_bytes(data), out_ptr, out_len)")) + #t) + (test "rust lowers record and variant operations" (typed-library-form->rust-string ops-form) ops-rust) --- a/tests/test-typed-wrapper-e2e.ss +++ b/tests/test-typed-wrapper-e2e.ss @@ -29,15 +29,33 @@ (thunk) #f)) +(define (bytevector-equal? a b) + (and (= (bytevector-length a) (bytevector-length b)) + (let loop ([i 0]) + (cond + [(= i (bytevector-length a)) #t] + [(= (bytevector-u8-ref a i) (bytevector-u8-ref b i)) + (loop (+ i 1))] + [else #f])))) + +(define sample-bytes (make-bytevector 4 0)) +(bytevector-u8-set! sample-bytes 0 1) +(bytevector-u8-set! sample-bytes 1 2) +(bytevector-u8-set! sample-bytes 2 3) +(bytevector-u8-set! sample-bytes 3 255) + (printf "--- Typed Jerboa wrapper FFI smoke ---~%") (check "zero" (= (zero) 0)) (check "add-one" (= (add-one 41) 42)) (check "positive?" (positive? 9)) (check "choose" (= (choose #t) 1)) +(check "greeting" (string=? (greeting) "hello")) +(check "echo-text" (string=? (echo-text "sample") "sample")) (check "double-add" (= (double-add 20) 41)) (check "text-length" (= (text-length "hello") 5)) (check "bytes-length" (= (bytes-length (make-bytevector 7 0)) 7)) +(check "echo-bytes" (bytevector-equal? (echo-bytes sample-bytes) sample-bytes)) (check "record handle round trip" (= (box-value (make-box 17)) 17)) (check "variant handle round trip" @@ -48,8 +66,12 @@ (raises? (lambda () (choose 1)))) (check "text-length rejects non-String" (raises? (lambda () (text-length (make-bytevector 3 0))))) +(check "echo-text rejects non-String" + (raises? (lambda () (echo-text (make-bytevector 3 0))))) (check "bytes-length rejects non-Bytes" (raises? (lambda () (bytes-length "not bytes")))) +(check "echo-bytes rejects non-Bytes" + (raises? (lambda () (echo-bytes "not bytes")))) (check "record handle rejects wrong type" (raises? (lambda () (box-value (make-some 7))))) (check "variant handle rejects wrong type" --- a/tests/test-typed-wrappers.ss +++ b/tests/test-typed-wrappers.ss @@ -60,6 +60,19 @@ (define bytes-wrapper (typed-library-form->jerboa-wrapper-string bytes-form)) +(define return-form + '(typed-library (sample typed return-values) + (export greeting echo-text echo-bytes) + (def (greeting) : String + "hello") + (def (echo-text (text : String)) : String + text) + (def (echo-bytes (data : Bytes)) : Bytes + data))) + +(define return-wrapper + (typed-library-form->jerboa-wrapper-string return-form)) + (define handle-form '(typed-library (sample typed handles) (export make-box box-value make-some token-size) @@ -134,6 +147,40 @@ "(%bytes_length data (bytevector-length data))") #t) +(test "wrapper emits return byte buffer helpers" + (and (substring? return-wrapper + "(only (chezscheme) foreign-procedure getenv load-shared-object") + (substring? return-wrapper + "foreign-alloc foreign-free foreign-ref foreign-set! foreign-sizeof") + (substring? return-wrapper + "(foreign-procedure \"jt_byte_buffer_free\" (void* size_t) void)") + (substring? return-wrapper + "(def (%typed-rust-return-bytes who thunk)") + (substring? return-wrapper + "(def (%typed-rust-return-string who thunk)")) + #t) + +(test "wrapper binds String returns as out buffers" + (and (substring? return-wrapper + "(foreign-procedure \"jt_sample_typed_return_values_greeting\" (void* void*) boolean)") + (substring? return-wrapper + "(foreign-procedure \"jt_sample_typed_return_values_echo_text\" (u8* size_t void* void*) boolean)")) + #t) + +(test "wrapper converts String returns" + (and (substring? return-wrapper + "(%typed-rust-return-string 'greeting (lambda (%out_ptr %out_len) (%greeting %out_ptr %out_len)))") + (substring? return-wrapper + "(%typed-rust-return-string 'echo-text (lambda (%out_ptr %out_len) (%echo_text %text_bytes (bytevector-length %text_bytes) %out_ptr %out_len)))")) + #t) + +(test "wrapper binds and converts Bytes returns" + (and (substring? return-wrapper + "(foreign-procedure \"jt_sample_typed_return_values_echo_bytes\" (u8* size_t void* void*) boolean)") + (substring? return-wrapper + "(%typed-rust-return-bytes 'echo-bytes (lambda (%out_ptr %out_len) (%echo_bytes data (bytevector-length data) %out_ptr %out_len)))")) + #t) + (test "wrapper returns record and variant handles" (and (substring? handle-wrapper "(%typed-rust-make-handle 'Box (%make_box value))")