Skip to content

Commit c73eb19

Browse files
shuaimuclaude
andcommitted
transpiler: fix let ref mut rvalue binding to emit owned value
When `let ref mut x = rvalue_expr;` binds a mutable reference to an rvalue (like String::new()), emit `auto x = rvalue_expr;` (owned value) instead of `auto& x = rvalue_expr;` (dangling reference to temporary). Add is_rvalue_expr() helper that detects function calls, literals, struct constructors, and other rvalue expressions. The ref_suffix is suppressed when by_ref + mut + rvalue init. Semver parity: 64 → 63 errors (1 fewer). Full matrix: 5/6 pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6499fe6 commit c73eb19

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

TODO.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2078,7 +2078,7 @@ Work on tasks defined in TODO.md. Repeat the following steps, don’t stop until
20782078
- `cd /tmp/rusty-parity-matrix-27-45-2-20260407-1/arrayvec && timeout 30s ./runner --rusty-single-test rusty_test_test_retain`
20792079
- `cd /tmp/rusty-parity-matrix-27-45-2-20260407-1/arrayvec && gdb -q ./runner -ex 'set args --rusty-single-test rusty_test_test_retain' -ex run -ex bt -ex quit`
20802080
- [x] *done* Guardrail check against wrong-approach section (`docs/rusty-cpp-transpiler.md` §11): maintained deterministic first-head + canonical-artifact workflow, kept fixes in shared transpiler/runtime surfaces, and introduced no crate-specific rewrites/scripts.
2081-
- [ ] Leaf 4.15.4.4: Priority pivot (2026-04-07): close current `semver` and `bitflags` Stage D heads. **Status (2026-04-08)**: semver 437→64 (85% reduction), bitflags 951→433 (54% reduction). Arrayvec reached 51/51 (100%). Overall: **5/7 crates pass**.
2081+
- [ ] Leaf 4.15.4.4: Priority pivot (2026-04-07): close current `semver` and `bitflags` Stage D heads. **Status (2026-04-08)**: semver 437→63 (86% reduction), bitflags 951→433 (54% reduction). Arrayvec reached 51/51 (100%). Overall: **5/7 crates pass**.
20822082
- [x] *done* Leaf 4.15.4.4.1: Collapse current `semver` Stage D formatter/runtime/type-surface head generically (starting with `std::move_only_function` unavailability and unresolved `NonNull` type surfaces), add fixture-agnostic regressions, then re-run `semver` parity.
20832083
- [x] *done* Plan/scope check: shared transpiler/runtime updates stayed well below the <1000 LOC guardrail and required no additional TODO decomposition.
20842084
- [x] *done* Implemented shared fixes (no crate-specific scripts):
@@ -2500,6 +2500,10 @@ Work on tasks defined in TODO.md. Repeat the following steps, don’t stop until
25002500
- [x] *done* Reordered `emit_file` to emit `ExternCrate` items before `emit_item_forward_decls`. This ensures forward declarations (like `namespace util { ... }` and `void test_*();`) appear AFTER `// extern crate` markers in the cppm output, so the parity runner's prelude-skip logic doesn't accidentally skip them.
25012501
- [x] *done* Semver errors: 75 → 64 (11 fewer, 15% reduction); eliminated all `prerelease`/`prerelease_err`/`build_metadata`/`assert_to_string`/`util` undeclared scope errors from the test_identifier target.
25022502
- [x] *done* Full matrix: 5/6 pass (either ✅, tap ✅, cfg-if ✅, take_mut ✅, arrayvec ✅); no regressions.
2503+
- [x] *done* Leaf 4.15.4.4.55: Fix `let ref mut` rvalue binding to emit owned value instead of reference.
2504+
- [x] *done* `let ref mut x = rvalue_expr;` now emits `auto x = rvalue_expr;` (owned) instead of `auto& x = rvalue_expr;` (dangling ref to temporary). Added `is_rvalue_expr()` helper for detection.
2505+
- [x] *done* Semver errors: 64 → 63 (1 fewer); eliminated `cannot bind non-const lvalue reference to rvalue` error from `let ref mut string = String::new()`.
2506+
- [x] *done* Full matrix: 5/6 pass; no regressions.
25032507
- [ ] Leaf 4.15.4.3.3.3.3.3.27.46: [Deferred by 4.15.4.4 priority pivot] Collapse the post-27.45.2 deterministic Stage E `test_retain` assertion-abort family generically (starting with abort immediately after `test_pop_at PASSED`, next scheduled wrapper `rusty_test_test_retain` at `runner.cpp:4840`, and failing assertion surface in `test_retain` at `runner.cpp:3133-3136`), add fixture-agnostic regressions, then re-run full seven-crate matrix.
25042508
- [x] *done* Leaf 4.15.4.3.3.3.3.3.27.46.1: Fix `as_mut_ptr()` type inference fallback: use current scope's type parameter (e.g., `T`) instead of `u8` when pointee type can't be recovered from receiver context. Fixes `retain` function using `uint8_t*` arithmetic instead of `T*`, causing wrong element stride and assertion failure.
25052509
- [x] *done* Arrayvec: `test_retain` now PASSES. 37/37 discovered-and-reached tests pass. Remaining 14 undiscovered tests crash from `free(): double free` during expected-panic exception unwinding (separate issue).

transpiler/src/codegen.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8497,8 +8497,21 @@ impl CodeGen {
84978497
|| inferred_binding_ty
84988498
.as_ref()
84998499
.is_some_and(|ty| Self::is_mut_raw_pointer_type(ty));
8500-
// `let ref r = expr` → `const auto& r = expr;` (reference binding)
8501-
let ref_suffix = if pat_ident.by_ref.is_some() { "&" } else { "" };
8500+
// `let ref r = expr` → `const auto& r = expr;` (reference binding).
8501+
// Exception: `let ref mut r = rvalue_expr;` — when the init is
8502+
// an rvalue (e.g., function call), binding a mutable reference
8503+
// to it is invalid in C++. Emit as owned value instead.
8504+
let ref_suffix = if pat_ident.by_ref.is_some()
8505+
&& !(is_mut
8506+
&& local
8507+
.init
8508+
.as_ref()
8509+
.is_some_and(|init| self.is_rvalue_expr(&init.expr)))
8510+
{
8511+
"&"
8512+
} else {
8513+
""
8514+
};
85028515
let decl_type = if qualifier == "const " && mut_raw_ptr_binding && type_str.contains('*')
85038516
{
85048517
format!("{} const", type_str)
@@ -18354,6 +18367,26 @@ impl CodeGen {
1835418367
matches!(expr, syn::Expr::Reference(_))
1835518368
}
1835618369

18370+
/// Check if an expression is an rvalue (temporary/function-call result)
18371+
/// rather than an lvalue (variable/field access).
18372+
fn is_rvalue_expr(&self, expr: &syn::Expr) -> bool {
18373+
matches!(
18374+
expr,
18375+
syn::Expr::Call(_)
18376+
| syn::Expr::MethodCall(_)
18377+
| syn::Expr::Lit(_)
18378+
| syn::Expr::Struct(_)
18379+
| syn::Expr::Tuple(_)
18380+
| syn::Expr::Array(_)
18381+
| syn::Expr::Binary(_)
18382+
| syn::Expr::Unary(_)
18383+
| syn::Expr::If(_)
18384+
| syn::Expr::Match(_)
18385+
| syn::Expr::Block(_)
18386+
| syn::Expr::Closure(_)
18387+
)
18388+
}
18389+
1835718390
/// Extract the inner expression from a reference expression, as a string.
1835818391
fn extract_ref_inner(&self, expr: &syn::Expr) -> String {
1835918392
if let syn::Expr::Reference(r) = expr {
@@ -32495,4 +32528,28 @@ mod tests {
3249532528
"should not have constexpr self-referential const inside struct\nGot: {out}"
3249632529
);
3249732530
}
32531+
32532+
#[test]
32533+
fn test_leaf4154455_ref_mut_rvalue_emits_owned_local() {
32534+
// `let ref mut string = String::new();` should emit an owned local,
32535+
// not a reference to a temporary.
32536+
let out = transpile_str(
32537+
r#"
32538+
fn f() {
32539+
let ref mut string = String::new();
32540+
string.push('x');
32541+
}
32542+
"#,
32543+
);
32544+
// Should NOT contain `auto& string = ` (reference to temporary)
32545+
assert!(
32546+
!out.contains("auto& string"),
32547+
"ref mut binding to rvalue should emit owned local, not reference\nGot: {out}"
32548+
);
32549+
// Should contain `auto string = ` (owned value)
32550+
assert!(
32551+
out.contains("auto string"),
32552+
"ref mut binding to rvalue should emit 'auto string ='\nGot: {out}"
32553+
);
32554+
}
3249832555
}

0 commit comments

Comments
 (0)