Clone Typed Jerboa Rust call args
ober
623cdb514f6aff9a5f90750fa774323b2cd2a1d4
--- a/docs/jerboa-to-rust.md +++ b/docs/jerboa-to-rust.md @@ -249,9 +249,13 @@ their own variant type are emitted as `std::boxed::Box<T>` fields, and constructors wrap those children when building Rust enum values. Match clauses that bind recursive child fields clone the boxed child back into an owned typed value before evaluating the branch body, which is enough for the first -recursive split-tree traversals. The first string-building helper, +recursive split-tree traversals. The Rust emitter also conservatively clones +function-call and constructor arguments for the current Clone-only owned-value +subset, so a matched recursive child can be passed to more than one helper in +the same branch. The first string-building helper, `string-append`, lowers to Rust `format!` for two string arguments and supports -the initial recursive `split-tree-flatten`. +the initial recursive `split-tree-flatten` and validity checks over stored +split sizes. ## Generics @@ -560,9 +564,9 @@ Exports: `split-tree-flatten`, `split-tree-find-parent`, `split-tree-remove`, `leaf-id`, and `leaf-text` - The split-tree smoke test now compares generated typed/Rust results against a - small dynamic tagged-list reference implementation. -- Next split-tree work: richer validity checks and use from a small Jerboa - caller + small dynamic tagged-list reference implementation and rejects a split whose + stored size disagrees with its recursive child sizes. +- Next split-tree work: use the typed split-tree from a small Jerboa caller Why: --- a/docs/typed-jerboa.md +++ b/docs/typed-jerboa.md @@ -183,7 +183,8 @@ Current landing: - `make typed-split-tree-smoke` builds the first recursive split-tree slice: typed leaf/split constructors, recursive total-size, validity, and flatten traversals, parent lookup, remove-by-leaf-id, leaf accessors, dynamic wrapper - checks, string returns, opaque handles, and explicit handle drops. + checks, rejection of inconsistent stored split sizes, string returns, opaque + handles, and explicit handle drops. - `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 @@ -202,6 +203,11 @@ Current landing: as `(Bytes -> Nat)` and lowers the same way. Imported calls and richer forms are reported as unsupported. It does not yet resolve imports, lower to typed core IR, or emit LLVM. +- The current Rust lowering uses a conservative Clone-only ownership model: + generated constructors, ordinary calls, and recursive match rebinding clone + owned values so recursive branch code can pass the same child value to more + than one helper. This is deliberately simple and correct for the first + split-tree slice; borrow-aware lowering remains future work. ## Surface Syntax @@ -848,13 +854,16 @@ Minimum excluded features: - Implement typed `split-tree` or `rope`. An initial recursive split-tree slice now lands in `tests/fixtures/typed/valid-split-tree.ss`, including recursive - child traversal, parent lookup, and remove-by-leaf-id through boxed Rust - variant fields. + child traversal, stored-size validity checks, parent lookup, and + remove-by-leaf-id through boxed Rust variant fields. - Add unit tests. Recursive variant lowering and recursive match child - rebinding are covered by Rust emitter tests. + rebinding are covered by Rust emitter tests, including conservative call-site + cloning for repeated recursive child use. - Add dynamic boundary tests. `make typed-split-tree-smoke` builds and calls the generated split-tree wrapper through Chez FFI and compares the typed - results with a small dynamic split-tree reference implementation. + results with a small dynamic split-tree reference implementation. It also + checks that invalid stored split sizes are rejected by the typed validity + function. - Use it from a small part of Jerboa. ### Milestone 5: Effects and Resources --- a/lib/jerboa/typed/rust.ss +++ b/lib/jerboa/typed/rust.ss @@ -171,11 +171,14 @@ (rust-type field-type))) (def (emit-owned-field-expression owner-type field-type expr) - (let ([out (emit-expression expr)]) + (let ([out (emit-argument-expression expr)]) (if (and owner-type (equal? owner-type field-type)) (string-append "std::boxed::Box::new(" out ")") out))) + (def (emit-argument-expression expr) + (string-append "(" (emit-expression expr) ").clone()")) + (def (append-map f xs) (let loop ([rest xs] [out '()]) (if (null? rest) @@ -729,7 +732,7 @@ (string-append (rust-symbol-name (typed-field-name field)) ": " - (emit-expression arg))) + (emit-argument-expression arg))) fields args) ", ") @@ -933,7 +936,7 @@ (string-append (rust-symbol-name name) "(" - (join-strings (map emit-expression args) ", ") + (join-strings (map emit-argument-expression args) ", ") ")")]))) (def (emit-expression expr) --- a/tests/fixtures/typed/valid-split-tree.ss +++ b/tests/fixtures/typed/valid-split-tree.ss @@ -30,8 +30,10 @@ (def (split-tree-valid? (tree : SplitTree)) : Bool (match tree ((Leaf _ _) #t) - ((Split _ left right _) (and (split-tree-valid? left) - (split-tree-valid? right))))) + ((Split _ left right size) (and (split-tree-valid? left) + (and (split-tree-valid? right) + (= size (+ (split-tree-total-size left) + (split-tree-total-size right)))))))) (def (split-tree-flatten (tree : SplitTree)) : String (match tree --- a/tests/test-typed-rust.ss +++ b/tests/test-typed-rust.ss @@ -145,7 +145,7 @@ ((Noop) 0))))) (define ops-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\n#[derive(Clone, Debug, PartialEq)]\npub struct Pane {\n pub id: u64,\n pub focused_p: bool,\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub enum EditOp {\n Insert {\n at: u64,\n text: String,\n },\n Noop,\n}\n\npub fn pane_id(pane: Pane) -> u64 {\n (pane).id\n}\n\npub fn make_insert(at: u64, text: String) -> EditOp {\n EditOp::Insert { at: at, text: text }\n}\n\npub fn make_noop() -> EditOp {\n EditOp::Noop\n}\n\npub fn edit_size(op: EditOp) -> u64 {\n match op { EditOp::Insert { at, text } => at, EditOp::Noop => 0u64, }\n}\n\n") + "// 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\n#[derive(Clone, Debug, PartialEq)]\npub struct Pane {\n pub id: u64,\n pub focused_p: bool,\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub enum EditOp {\n Insert {\n at: u64,\n text: String,\n },\n Noop,\n}\n\npub fn pane_id(pane: Pane) -> u64 {\n (pane).id\n}\n\npub fn make_insert(at: u64, text: String) -> EditOp {\n EditOp::Insert { at: (at).clone(), text: (text).clone() }\n}\n\npub fn make_noop() -> EditOp {\n EditOp::Noop\n}\n\npub fn edit_size(op: EditOp) -> u64 {\n match op { EditOp::Insert { at, text } => at, EditOp::Noop => 0u64, }\n}\n\n") (define handle-form '(typed-library (sample typed handles) @@ -228,17 +228,17 @@ (substring? recursive-rust "right: std::boxed::Box<SplitTree>,") (substring? recursive-rust - "SplitTree::Split { id: id, left: std::boxed::Box::new(left), right: std::boxed::Box::new(right), size: size }")) + "SplitTree::Split { id: (id).clone(), left: std::boxed::Box::new((left).clone()), right: std::boxed::Box::new((right).clone()), size: (size).clone() }")) #t) (test "rust unboxes recursive match bindings" (and (substring? recursive-rust - "SplitTree::Split { id: _, left: left_box, right: right_box, size: _ } => { let left = (*left_box).clone(); let right = (*right_box).clone(); (split_tree_total_size(left) + split_tree_total_size(right)) },")) + "SplitTree::Split { id: _, left: left_box, right: right_box, size: _ } => { let left = (*left_box).clone(); let right = (*right_box).clone(); (split_tree_total_size((left).clone()) + split_tree_total_size((right).clone())) },")) #t) (test "rust lowers string-append" (substring? recursive-rust - "format!(\"{}{}\", split_tree_flatten(left), split_tree_flatten(right))") + "format!(\"{}{}\", split_tree_flatten((left).clone()), split_tree_flatten((right).clone()))") #t) (test "rust lowers record and variant operations" --- a/tests/test-typed-split-tree-e2e.ss +++ b/tests/test-typed-split-tree-e2e.ss @@ -99,6 +99,9 @@ (= (split-tree-total-size root) 5)) (check "split valid" (split-tree-valid? root)) +(define bad-root (make-split 11 left right 99)) +(check "invalid split size rejected" + (not (split-tree-valid? bad-root))) (check "split flatten" (string=? (split-tree-flatten root) "abcde")) (check "typed flatten matches dynamic"