Clone Typed Jerboa Rust call args

ober

623cdb514f6aff9a5f90750fa774323b2cd2a1d4

diff --git a/docs/jerboa-to-rust.md b/docs/jerboa-to-rust.md
index 587d228..a741eab 100644
--- 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:
 
diff --git a/docs/typed-jerboa.md b/docs/typed-jerboa.md
index 0b47462..2604afd 100644
--- 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
diff --git a/lib/jerboa/typed/rust.ss b/lib/jerboa/typed/rust.ss
index 11d5ef8..f687ecc 100644
--- 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)
diff --git a/tests/fixtures/typed/valid-split-tree.ss b/tests/fixtures/typed/valid-split-tree.ss
index d3f3a4e..bc0fd09 100644
--- 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
diff --git a/tests/test-typed-rust.ss b/tests/test-typed-rust.ss
index 7fc04d1..afd2e49 100644
--- 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"
diff --git a/tests/test-typed-split-tree-e2e.ss b/tests/test-typed-split-tree-e2e.ss
index f1ebc6a..c195c90 100644
--- 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"