From f2003bf697398fb9d6964fbec15c07a2af3d17db Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 15 Jun 2026 20:13:40 +0200 Subject: [PATCH 1/7] transpile: Add `assign_result` snapshot test to `exprs.c` --- c2rust-transpile/tests/snapshots.rs | 2 +- c2rust-transpile/tests/snapshots/exprs.c | 6 ++++++ .../snapshots__transpile@exprs.c.2021.clang15.snap | 9 +++++++++ .../snapshots__transpile@exprs.c.2024.clang15.snap | 9 +++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 5b72ce1293..8f653efa7f 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -391,7 +391,7 @@ fn test_empty_init() { #[test] fn test_exprs() { - transpile("exprs.c").run(); + transpile("exprs.c").expect_compile_error(true).run(); } #[test] diff --git a/c2rust-transpile/tests/snapshots/exprs.c b/c2rust-transpile/tests/snapshots/exprs.c index a45c6ef869..09a3d8eab0 100644 --- a/c2rust-transpile/tests/snapshots/exprs.c +++ b/c2rust-transpile/tests/snapshots/exprs.c @@ -108,3 +108,9 @@ void pointer_arithmetic(void) { ptrdiff_t diff = p1 - p2; int diff_int = p1 - p2; } + +void assign_result(void) { + unsigned long l = 0; + size_t s1 = l = 1; + size_t s2 = l += 2; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap index 7a2bf79120..5eb8cc6038 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap @@ -16,6 +16,7 @@ extern "C" { fn puts(str: *const ::core::ffi::c_char) -> ::core::ffi::c_int; } pub type ptrdiff_t = isize; +pub type size_t = usize; pub type E = ::core::ffi::c_uint; pub const EA: E = 0; pub type int_t = ::core::ffi::c_int; @@ -134,3 +135,11 @@ pub unsafe extern "C" fn pointer_arithmetic() { let mut diff: ptrdiff_t = p1.offset_from(p2); let mut diff_int: ::core::ffi::c_int = p1.offset_from(p2) as ::core::ffi::c_int; } +#[no_mangle] +pub unsafe extern "C" fn assign_result() { + let mut l: ::core::ffi::c_ulong = 0 as ::core::ffi::c_ulong; + l = 1 as ::core::ffi::c_ulong; + let mut s1: size_t = l; + l = l.wrapping_add(2 as ::core::ffi::c_ulong) as size_t; + let mut s2: size_t = l; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap index 83541d017e..95b508b2af 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap @@ -16,6 +16,7 @@ unsafe extern "C" { unsafe fn puts(str: *const ::core::ffi::c_char) -> ::core::ffi::c_int; } pub type ptrdiff_t = isize; +pub type size_t = usize; pub type E = ::core::ffi::c_uint; pub const EA: E = 0; pub type int_t = ::core::ffi::c_int; @@ -134,3 +135,11 @@ pub unsafe extern "C" fn pointer_arithmetic() { let mut diff: ptrdiff_t = p1.offset_from(p2); let mut diff_int: ::core::ffi::c_int = p1.offset_from(p2) as ::core::ffi::c_int; } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn assign_result() { + let mut l: ::core::ffi::c_ulong = 0 as ::core::ffi::c_ulong; + l = 1 as ::core::ffi::c_ulong; + let mut s1: size_t = l; + l = l.wrapping_add(2 as ::core::ffi::c_ulong) as size_t; + let mut s2: size_t = l; +} From b1ade0f5e66324561ab01d98b1e0dc915e5f1281 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 15 Jun 2026 18:24:57 +0200 Subject: [PATCH 2/7] transpile: Remove unnecessary `allow` attribute in `convert_assignment_operator_with_rhs` --- c2rust-transpile/src/translator/operators.rs | 181 +++++++++---------- 1 file changed, 86 insertions(+), 95 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 12ebc9f56f..658f7c15db 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -390,109 +390,100 @@ impl<'c> Translation<'c> { }) }; - rhs_translation.zip(lhs_translation).and_then_try(|(rhs, lhs)| { - let NamedReference { - lvalue: write, - rvalue: read, - } = lhs; - - // Assignment expression itself - use CBinOp::*; - let assign_stmt = match op { - // Regular (possibly volatile) assignment - Assign if !is_volatile => WithStmts::new_val(mk().assign_expr(write, rhs)), - Assign => WithStmts::new_val(self.volatile_write( - write, - initial_lhs_type_id, - rhs, - )?).set_unsafe(), - - // Anything volatile needs to be desugared into explicit reads and writes - op if is_volatile || is_unsigned_arith => { - // Cast the lhs to the compute lhs type, do the compute, and then - // cast the compute result to the final lhs type. + rhs_translation + .zip(lhs_translation) + .and_then_try(|(rhs, lhs)| { + let NamedReference { + lvalue: write, + rvalue: read, + } = lhs; + + // Assignment expression itself + use CBinOp::*; + let assign_stmt = match op { + // Regular (possibly volatile) assignment + Assign if !is_volatile => WithStmts::new_val(mk().assign_expr(write, rhs)), + Assign => { + WithStmts::new_val(self.volatile_write(write, initial_lhs_type_id, rhs)?) + .set_unsafe() + } - let op = op - .underlying_assignment() - .expect("Cannot convert non-assignment operator"); + // Anything volatile needs to be desugared into explicit reads and writes + op if is_volatile || is_unsigned_arith => { + // Cast the lhs to the compute lhs type, do the compute, and then + // cast the compute result to the final lhs type. - let lhs = self.make_cast( - ctx.used(), - initial_lhs_type_id, - expr_or_comp_type_id, - WithStmts::new_val(read.clone()), - )?; + let op = op + .underlying_assignment() + .expect("Cannot convert non-assignment operator"); - let val = lhs.and_then_try(|lhs| - self.convert_binary_operator( - ctx, - result_type_id, - op, + let lhs = self.make_cast( + ctx.used(), + initial_lhs_type_id, expr_or_comp_type_id, - rhs_type_id, - lhs, + WithStmts::new_val(read.clone()), + )?; + + let val = lhs.and_then_try(|lhs| { + self.convert_binary_operator( + ctx, + result_type_id, + op, + expr_or_comp_type_id, + rhs_type_id, + lhs, + rhs, + ) + })?; + + let val = self.make_cast(ctx, result_type_id, expr_type_id, val)?; + + if is_volatile { + val.try_map(|val| self.volatile_write(write, initial_lhs_type_id, val))? + .set_unsafe() + } else { + val.map(|val| mk().assign_expr(write, val)) + } + } + + // Everything else + AssignAdd | AssignSubtract if pointer_lhs.is_some() => { + let ptr = self.convert_pointer_offset( + write.clone(), rhs, - ) - )?; - - let val = self.make_cast( - ctx.used(), - result_type_id, - expr_type_id, - val, - )?; - - #[allow(clippy::let_and_return /* , reason = "block is large, so variable name helps" */)] - let write = if is_volatile { - val.and_then_try(|val| { - TranslationResult::Ok(WithStmts::new_val( - self.volatile_write(write, initial_lhs_type_id, val)?, - ).set_unsafe()) - })? - } else { - val.map(|val| mk().assign_expr(write, val)) - }; - write - } + pointer_lhs.unwrap().ctype, + op == AssignSubtract, + false, + ); + ptr.map(|ptr| mk().assign_expr(write, ptr)) + } - // Everything else - AssignAdd | AssignSubtract if pointer_lhs.is_some() => { - let ptr = self.convert_pointer_offset( - write.clone(), - rhs, - pointer_lhs.unwrap().ctype, - op == AssignSubtract, - false, - ); - ptr.map(|ptr| mk().assign_expr(write, ptr)) - } + _ => { + let bin_op = op + .underlying_assignment() + .expect("Cannot convert non-assignment operator"); + let bin_op_kind = BinOp::from(op); - _ => { - let bin_op = op - .underlying_assignment() - .expect("Cannot convert non-assignment operator"); - let bin_op_kind = BinOp::from(op); - - self.convert_assignment_operator_aux( - ctx, - bin_op_kind, - bin_op, - read.clone(), - write, - rhs, - initial_lhs_type_id, - compute_lhs_type_id.unwrap(), - compute_res_type_id.unwrap(), - expr_type_id, - rhs_type_id, - )? - } - }; + self.convert_assignment_operator_aux( + ctx, + bin_op_kind, + bin_op, + read.clone(), + write, + rhs, + initial_lhs_type_id, + compute_lhs_type_id.unwrap(), + compute_res_type_id.unwrap(), + expr_type_id, + rhs_type_id, + )? + } + }; - Ok(assign_stmt.and_then(|assign_stmt| { - WithStmts::new(vec![mk().semi_stmt(assign_stmt)], read) - })) - }) + Ok(assign_stmt.and_then(|assign_stmt| { + WithStmts::new(vec![mk().semi_stmt(assign_stmt)], read) + })) + }) } /// Translate a non-assignment binary operator. It is expected that the `lhs` and `rhs` From 59d830086ad8ffb5f9d46f28b4854e7e8f37491f Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 15 Jun 2026 18:25:42 +0200 Subject: [PATCH 3/7] transpile: Make parameter names and order match in operators.rs --- c2rust-transpile/src/translator/mod.rs | 2 +- c2rust-transpile/src/translator/operators.rs | 66 ++++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index da92ccfea8..63fe658764 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3335,7 +3335,7 @@ impl<'c> Translation<'c> { ), Unary(type_id, op, arg, _lrvalue) => { - self.convert_unary_operator(ctx, op, override_ty.unwrap_or(type_id), arg) + self.convert_unary_operator(ctx, override_ty.unwrap_or(type_id), op, arg) } Conditional(ty, cond, lhs, rhs) => { diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 658f7c15db..9ffce378af 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -10,8 +10,8 @@ impl<'c> Translation<'c> { op: CBinOp, lhs: CExprId, rhs: CExprId, - opt_lhs_type_id: Option, - opt_res_type_id: Option, + compute_lhs_type_id: Option, + compute_res_type_id: Option, ) -> TranslationResult>> { // If we're not making an assignment, a binop will require parens // applied to ternary conditionals @@ -49,12 +49,12 @@ impl<'c> Translation<'c> { // No sequence-point cases op if op.is_assignment() => self.convert_assignment_operator( ctx, - op, expr_type_id, + op, lhs, rhs, - opt_lhs_type_id, - opt_res_type_id, + compute_lhs_type_id, + compute_res_type_id, ), _ => { @@ -189,6 +189,7 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_aux( &self, ctx: ExprContext, + expr_type_id: CQualTypeId, bin_op_kind: BinOp, bin_op: CBinOp, read: Box, @@ -197,11 +198,10 @@ impl<'c> Translation<'c> { initial_lhs_type_id: CQualTypeId, compute_lhs_type_id: CQualTypeId, compute_res_type_id: CQualTypeId, - lhs_type_id: CQualTypeId, rhs_type_id: CQualTypeId, ) -> TranslationResult>> { if self.ast_context.resolve_type_id(compute_lhs_type_id.ctype) - == self.ast_context.resolve_type_id(lhs_type_id.ctype) + == self.ast_context.resolve_type_id(expr_type_id.ctype) { Ok(WithStmts::new_val(mk().assign_op_expr( bin_op_kind, @@ -228,7 +228,7 @@ impl<'c> Translation<'c> { ) })?; - let val = self.make_cast(ctx.used(), compute_res_type_id, lhs_type_id, val)?; + let val = self.make_cast(ctx.used(), compute_res_type_id, expr_type_id, val)?; Ok(val.map(|val| mk().assign_expr(write.clone(), val))) } @@ -236,14 +236,14 @@ impl<'c> Translation<'c> { /// Translate an assignment binary operator. /// - /// `compute_lhs_ty` and `compute_res_ty` correspond to Clang's + /// `compute_lhs_type_id` and `compute_res_type_id` correspond to Clang's /// `CompoundAssignOperator::{CompLHSType,CompResultType}`; see the Clang docs: /// https://clang.llvm.org/doxygen/classclang_1_1CompoundAssignOperator.html#details fn convert_assignment_operator( &self, ctx: ExprContext, - op: CBinOp, expr_type_id: CQualTypeId, + op: CBinOp, lhs: CExprId, rhs: CExprId, compute_lhs_type_id: Option, @@ -299,8 +299,8 @@ impl<'c> Translation<'c> { // Now that we've translated the rhs, finish translating the assignment operator. self.convert_assignment_operator_with_rhs( ctx, - op, expr_type_id, + op, lhs, rhs_type_id, rhs_translation, @@ -313,8 +313,8 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_with_rhs( &self, ctx: ExprContext, - op: CBinOp, expr_type_id: CQualTypeId, + op: CBinOp, lhs: CExprId, rhs_type_id: CQualTypeId, rhs_translation: WithStmts>, @@ -466,6 +466,7 @@ impl<'c> Translation<'c> { self.convert_assignment_operator_aux( ctx, + expr_type_id, bin_op_kind, bin_op, read.clone(), @@ -474,7 +475,6 @@ impl<'c> Translation<'c> { initial_lhs_type_id, compute_lhs_type_id.unwrap(), compute_res_type_id.unwrap(), - expr_type_id, rhs_type_id, )? } @@ -589,7 +589,7 @@ impl<'c> Translation<'c> { fn convert_pre_increment( &self, ctx: ExprContext, - ty: CQualTypeId, + expr_type_id: CQualTypeId, op: CBinOp, arg: CExprId, ) -> TranslationResult>> { @@ -600,7 +600,7 @@ impl<'c> Translation<'c> { .get_qual_type() .ok_or_else(|| format_err!("bad arg type"))?; - let one = match self.ast_context.resolve_type(ty.ctype).kind { + let one = match self.ast_context.resolve_type(expr_type_id.ctype).kind { // TODO: If rust gets f16 support: // CTypeKind::Half | CTypeKind::Float | CTypeKind::Double => mk().lit_expr(mk().float_unsuffixed_lit("1.")), @@ -624,26 +624,26 @@ impl<'c> Translation<'c> { self.convert_assignment_operator_with_rhs( ctx, + expr_type_id, op, - ty, arg, one_type_id, WithStmts::new_val(one), Some(arg_type), - Some(ty), + Some(expr_type_id), ) } fn convert_post_increment( &self, ctx: ExprContext, - ty: CQualTypeId, + expr_type_id: CQualTypeId, op: CBinOp, arg: CExprId, ) -> TranslationResult>> { // If we aren't going to be using the result, may as well do a simple pre-increment if ctx.is_unused() { - return self.convert_pre_increment(ctx, ty, op, arg); + return self.convert_pre_increment(ctx, expr_type_id, op, arg); } let op = op @@ -735,30 +735,30 @@ impl<'c> Translation<'c> { pub fn convert_unary_operator( &self, ctx: ExprContext, - name: CUnOp, - cqual_type: CQualTypeId, + expr_type_id: CQualTypeId, + op: CUnOp, arg: CExprId, ) -> TranslationResult>> { - let mut unary = match name { - CUnOp::AddressOf => self.convert_address_of(ctx, cqual_type, arg), + let mut unary = match op { + CUnOp::AddressOf => self.convert_address_of(ctx, expr_type_id, arg), CUnOp::PreIncrement => { - self.convert_pre_increment(ctx, cqual_type, CBinOp::AssignAdd, arg) + self.convert_pre_increment(ctx, expr_type_id, CBinOp::AssignAdd, arg) } CUnOp::PreDecrement => { - self.convert_pre_increment(ctx, cqual_type, CBinOp::AssignSubtract, arg) + self.convert_pre_increment(ctx, expr_type_id, CBinOp::AssignSubtract, arg) } CUnOp::PostIncrement => { - self.convert_post_increment(ctx, cqual_type, CBinOp::AssignAdd, arg) + self.convert_post_increment(ctx, expr_type_id, CBinOp::AssignAdd, arg) } CUnOp::PostDecrement => { - self.convert_post_increment(ctx, cqual_type, CBinOp::AssignSubtract, arg) + self.convert_post_increment(ctx, expr_type_id, CBinOp::AssignSubtract, arg) } - CUnOp::Deref => self.convert_deref(ctx, cqual_type, arg), - CUnOp::Plus => self.convert_expr(ctx.used(), arg, Some(cqual_type)), // promotion is explicit in the clang AST + CUnOp::Deref => self.convert_deref(ctx, expr_type_id, arg), + CUnOp::Plus => self.convert_expr(ctx.used(), arg, Some(expr_type_id)), // promotion is explicit in the clang AST - CUnOp::Negate => self.convert_negate_operator(ctx, cqual_type, arg), + CUnOp::Negate => self.convert_negate_operator(ctx, expr_type_id, arg), CUnOp::Complement => Ok(self - .convert_expr(ctx.used(), arg, Some(cqual_type))? + .convert_expr(ctx.used(), arg, Some(expr_type_id))? .map(|a| mk().unary_expr(UnOp::Not(Default::default()), a))), CUnOp::Not => { @@ -766,7 +766,7 @@ impl<'c> Translation<'c> { Ok(val.map(|x| mk().cast_expr(x, mk().abs_path_ty(vec!["core", "ffi", "c_int"])))) } CUnOp::Extension => { - let arg = self.convert_expr(ctx, arg, Some(cqual_type))?; + let arg = self.convert_expr(ctx, arg, Some(expr_type_id))?; Ok(arg) } CUnOp::Real | CUnOp::Imag | CUnOp::Coawait => { @@ -780,7 +780,7 @@ impl<'c> Translation<'c> { // `UnOp::Extension` (`__extension__`) is another exception since // it's a no-op around the inner expression. if !matches!( - name, + op, CUnOp::PreDecrement | CUnOp::PreIncrement | CUnOp::PostDecrement From a042d8f59f27d41ca7cd7989ddbb12a88d7fc169 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 15 Jun 2026 18:43:19 +0200 Subject: [PATCH 4/7] transpile: Rename some variables in `convert_assignment_operator_with_rhs` --- c2rust-transpile/src/translator/operators.rs | 57 ++++++++++---------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 9ffce378af..28c60fa782 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -195,7 +195,7 @@ impl<'c> Translation<'c> { read: Box, write: Box, rhs: Box, - initial_lhs_type_id: CQualTypeId, + lhs_type_id: CQualTypeId, compute_lhs_type_id: CQualTypeId, compute_res_type_id: CQualTypeId, rhs_type_id: CQualTypeId, @@ -211,7 +211,7 @@ impl<'c> Translation<'c> { } else { let lhs = self.make_cast( ctx.used(), - initial_lhs_type_id, + lhs_type_id, compute_lhs_type_id, WithStmts::new_val(read.clone()), )?; @@ -331,16 +331,16 @@ impl<'c> Translation<'c> { let ty = self.convert_type(expr_type_id.ctype)?; - let result_type_id = compute_res_type_id.unwrap_or(expr_type_id); - let expr_or_comp_type_id = compute_lhs_type_id.unwrap_or(expr_type_id); - let initial_lhs = &self.ast_context.index_unwrap_parens(lhs).kind; - let initial_lhs_type_id = initial_lhs + let compute_res_type_id = compute_res_type_id.unwrap_or(expr_type_id); + let compute_lhs_type_id = compute_lhs_type_id.unwrap_or(expr_type_id); + let lhs_kind = &self.ast_context.index_unwrap_parens(lhs).kind; + let lhs_type_id = lhs_kind .get_qual_type() .ok_or_else(|| format_err!("bad initial lhs type"))?; - let bitfield_id = match initial_lhs { + let bitfield_id = match *lhs_kind { CExprKind::Member(_, _, decl_id, _, _) => { - let kind = &self.ast_context[*decl_id].kind; + let kind = &self.ast_context[decl_id].kind; if let CDeclKind::Field { bitfield_width: Some(_), @@ -357,16 +357,19 @@ impl<'c> Translation<'c> { if let Some(field_id) = bitfield_id { let rhs_expr = mk().cast_expr(rhs_translation.to_expr(), ty); - return self.convert_bitfield_assignment_op_with_rhs(ctx, op, lhs, rhs_expr, *field_id); + return self.convert_bitfield_assignment_op_with_rhs(ctx, op, lhs, rhs_expr, field_id); } - let is_volatile = initial_lhs_type_id.qualifiers.is_volatile; + let is_volatile = lhs_type_id.qualifiers.is_volatile; let is_volatile_compound_assign = op.underlying_assignment().is_some() && is_volatile; - let expr_resolved_ty = self.ast_context.resolve_type(expr_type_id.ctype); - let compute_resolved_ty = &self.ast_context.resolve_type(expr_or_comp_type_id.ctype); + let expr_type_kind = &self.ast_context.resolve_type(expr_type_id.ctype).kind; + let compute_lhs_type_kind = &self + .ast_context + .resolve_type(compute_lhs_type_id.ctype) + .kind; - let pointer_lhs = match &expr_resolved_ty.kind { + let pointer_lhs = match expr_type_kind { &CTypeKind::Pointer(pointee) => Some(pointee), _ => None, }; @@ -374,9 +377,9 @@ impl<'c> Translation<'c> { let is_unsigned_arith = op .underlying_assignment() .map_or(false, |op| op.is_arithmetic()) - && compute_resolved_ty.kind.is_unsigned_integral_type(); + && compute_lhs_type_kind.is_unsigned_integral_type(); - let lhs_translation = if initial_lhs_type_id.ctype != expr_or_comp_type_id.ctype + let lhs_translation = if lhs_type_id.ctype != compute_lhs_type_id.ctype || ctx.is_used() || pointer_lhs.is_some() || is_volatile_compound_assign @@ -403,10 +406,8 @@ impl<'c> Translation<'c> { let assign_stmt = match op { // Regular (possibly volatile) assignment Assign if !is_volatile => WithStmts::new_val(mk().assign_expr(write, rhs)), - Assign => { - WithStmts::new_val(self.volatile_write(write, initial_lhs_type_id, rhs)?) - .set_unsafe() - } + Assign => WithStmts::new_val(self.volatile_write(write, lhs_type_id, rhs)?) + .set_unsafe(), // Anything volatile needs to be desugared into explicit reads and writes op if is_volatile || is_unsigned_arith => { @@ -419,27 +420,27 @@ impl<'c> Translation<'c> { let lhs = self.make_cast( ctx.used(), - initial_lhs_type_id, - expr_or_comp_type_id, + lhs_type_id, + compute_res_type_id, WithStmts::new_val(read.clone()), )?; let val = lhs.and_then_try(|lhs| { self.convert_binary_operator( ctx, - result_type_id, + compute_res_type_id, op, - expr_or_comp_type_id, + compute_lhs_type_id, rhs_type_id, lhs, rhs, ) })?; - let val = self.make_cast(ctx, result_type_id, expr_type_id, val)?; + let val = self.make_cast(ctx, compute_res_type_id, expr_type_id, val)?; if is_volatile { - val.try_map(|val| self.volatile_write(write, initial_lhs_type_id, val))? + val.try_map(|val| self.volatile_write(write, lhs_type_id, val))? .set_unsafe() } else { val.map(|val| mk().assign_expr(write, val)) @@ -472,9 +473,9 @@ impl<'c> Translation<'c> { read.clone(), write, rhs, - initial_lhs_type_id, - compute_lhs_type_id.unwrap(), - compute_res_type_id.unwrap(), + lhs_type_id, + compute_lhs_type_id, + compute_res_type_id, rhs_type_id, )? } From f04b0c65884cfe7c0e7f4fdc85e7cd02a278e5c8 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 15 Jun 2026 18:58:08 +0200 Subject: [PATCH 5/7] transpile: Use `lhs_type_id` instead of `expr_type_id` in `convert_assignment_operator_with_rhs` --- c2rust-transpile/src/translator/operators.rs | 21 ++++++++----------- ...shots__transpile@exprs.c.2021.clang15.snap | 2 +- ...shots__transpile@exprs.c.2024.clang15.snap | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 28c60fa782..4f131f96db 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -189,7 +189,6 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_aux( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, bin_op_kind: BinOp, bin_op: CBinOp, read: Box, @@ -201,7 +200,7 @@ impl<'c> Translation<'c> { rhs_type_id: CQualTypeId, ) -> TranslationResult>> { if self.ast_context.resolve_type_id(compute_lhs_type_id.ctype) - == self.ast_context.resolve_type_id(expr_type_id.ctype) + == self.ast_context.resolve_type_id(lhs_type_id.ctype) { Ok(WithStmts::new_val(mk().assign_op_expr( bin_op_kind, @@ -228,7 +227,7 @@ impl<'c> Translation<'c> { ) })?; - let val = self.make_cast(ctx.used(), compute_res_type_id, expr_type_id, val)?; + let val = self.make_cast(ctx.used(), compute_res_type_id, lhs_type_id, val)?; Ok(val.map(|val| mk().assign_expr(write.clone(), val))) } @@ -313,7 +312,7 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_with_rhs( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, + _expr_type_id: CQualTypeId, op: CBinOp, lhs: CExprId, rhs_type_id: CQualTypeId, @@ -329,10 +328,6 @@ impl<'c> Translation<'c> { assert!(compute_res_type_id.is_some()); } - let ty = self.convert_type(expr_type_id.ctype)?; - - let compute_res_type_id = compute_res_type_id.unwrap_or(expr_type_id); - let compute_lhs_type_id = compute_lhs_type_id.unwrap_or(expr_type_id); let lhs_kind = &self.ast_context.index_unwrap_parens(lhs).kind; let lhs_type_id = lhs_kind .get_qual_type() @@ -356,6 +351,7 @@ impl<'c> Translation<'c> { }; if let Some(field_id) = bitfield_id { + let ty = self.convert_type(lhs_type_id.ctype)?; let rhs_expr = mk().cast_expr(rhs_translation.to_expr(), ty); return self.convert_bitfield_assignment_op_with_rhs(ctx, op, lhs, rhs_expr, field_id); } @@ -363,13 +359,15 @@ impl<'c> Translation<'c> { let is_volatile = lhs_type_id.qualifiers.is_volatile; let is_volatile_compound_assign = op.underlying_assignment().is_some() && is_volatile; - let expr_type_kind = &self.ast_context.resolve_type(expr_type_id.ctype).kind; + let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; + let compute_res_type_id = compute_res_type_id.unwrap_or(lhs_type_id); + let compute_lhs_type_id = compute_lhs_type_id.unwrap_or(lhs_type_id); let compute_lhs_type_kind = &self .ast_context .resolve_type(compute_lhs_type_id.ctype) .kind; - let pointer_lhs = match expr_type_kind { + let pointer_lhs = match lhs_type_kind { &CTypeKind::Pointer(pointee) => Some(pointee), _ => None, }; @@ -437,7 +435,7 @@ impl<'c> Translation<'c> { ) })?; - let val = self.make_cast(ctx, compute_res_type_id, expr_type_id, val)?; + let val = self.make_cast(ctx, compute_res_type_id, lhs_type_id, val)?; if is_volatile { val.try_map(|val| self.volatile_write(write, lhs_type_id, val))? @@ -467,7 +465,6 @@ impl<'c> Translation<'c> { self.convert_assignment_operator_aux( ctx, - expr_type_id, bin_op_kind, bin_op, read.clone(), diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap index 5eb8cc6038..ac079975e4 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap @@ -140,6 +140,6 @@ pub unsafe extern "C" fn assign_result() { let mut l: ::core::ffi::c_ulong = 0 as ::core::ffi::c_ulong; l = 1 as ::core::ffi::c_ulong; let mut s1: size_t = l; - l = l.wrapping_add(2 as ::core::ffi::c_ulong) as size_t; + l = l.wrapping_add(2 as ::core::ffi::c_ulong); let mut s2: size_t = l; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap index 95b508b2af..b9e1ebd01d 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap @@ -140,6 +140,6 @@ pub unsafe extern "C" fn assign_result() { let mut l: ::core::ffi::c_ulong = 0 as ::core::ffi::c_ulong; l = 1 as ::core::ffi::c_ulong; let mut s1: size_t = l; - l = l.wrapping_add(2 as ::core::ffi::c_ulong) as size_t; + l = l.wrapping_add(2 as ::core::ffi::c_ulong); let mut s2: size_t = l; } From 1f7e47eec83c179f31513be2379c3a55fbfb5849 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 15 Jun 2026 19:10:39 +0200 Subject: [PATCH 6/7] transpile: Pass expected type to `convert_assignment_operator_with_rhs` --- c2rust-transpile/src/translator/mod.rs | 9 ++- c2rust-transpile/src/translator/operators.rs | 82 +++++++++++++------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 63fe658764..59419acbce 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3334,8 +3334,8 @@ impl<'c> Translation<'c> { matches!(expr_kind, CExprKind::ExplicitCast(..)), ), - Unary(type_id, op, arg, _lrvalue) => { - self.convert_unary_operator(ctx, override_ty.unwrap_or(type_id), op, arg) + Unary(result_type_id, op, arg, _lrvalue) => { + self.convert_unary_operator(ctx, override_ty, result_type_id, op, arg) } Conditional(ty, cond, lhs, rhs) => { @@ -3422,10 +3422,11 @@ impl<'c> Translation<'c> { } } - Binary(type_id, op, lhs, rhs, opt_lhs_type_id, opt_res_type_id) => self + Binary(result_type_id, op, lhs, rhs, opt_lhs_type_id, opt_res_type_id) => self .convert_binary_expr( ctx, - override_ty.unwrap_or(type_id), + override_ty, + result_type_id, op, lhs, rhs, diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 4f131f96db..9c2df6a96c 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -6,13 +6,16 @@ impl<'c> Translation<'c> { pub fn convert_binary_expr( &self, mut ctx: ExprContext, - expr_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, op: CBinOp, lhs: CExprId, rhs: CExprId, compute_lhs_type_id: Option, compute_res_type_id: Option, ) -> TranslationResult>> { + let expr_type_id = expected_type_id.unwrap_or(result_type_id); + // If we're not making an assignment, a binop will require parens // applied to ternary conditionals if !op.is_assignment() { @@ -49,7 +52,8 @@ impl<'c> Translation<'c> { // No sequence-point cases op if op.is_assignment() => self.convert_assignment_operator( ctx, - expr_type_id, + expected_type_id, + result_type_id, op, lhs, rhs, @@ -241,7 +245,8 @@ impl<'c> Translation<'c> { fn convert_assignment_operator( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, op: CBinOp, lhs: CExprId, rhs: CExprId, @@ -298,7 +303,8 @@ impl<'c> Translation<'c> { // Now that we've translated the rhs, finish translating the assignment operator. self.convert_assignment_operator_with_rhs( ctx, - expr_type_id, + expected_type_id, + result_type_id, op, lhs, rhs_type_id, @@ -312,7 +318,8 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_with_rhs( &self, ctx: ExprContext, - _expr_type_id: CQualTypeId, + _expected_type_id: Option, + _result_type_id: CQualTypeId, op: CBinOp, lhs: CExprId, rhs_type_id: CQualTypeId, @@ -587,7 +594,8 @@ impl<'c> Translation<'c> { fn convert_pre_increment( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, op: CBinOp, arg: CExprId, ) -> TranslationResult>> { @@ -598,7 +606,7 @@ impl<'c> Translation<'c> { .get_qual_type() .ok_or_else(|| format_err!("bad arg type"))?; - let one = match self.ast_context.resolve_type(expr_type_id.ctype).kind { + let one = match self.ast_context.resolve_type(arg_type.ctype).kind { // TODO: If rust gets f16 support: // CTypeKind::Half | CTypeKind::Float | CTypeKind::Double => mk().lit_expr(mk().float_unsuffixed_lit("1.")), @@ -622,26 +630,28 @@ impl<'c> Translation<'c> { self.convert_assignment_operator_with_rhs( ctx, - expr_type_id, + expected_type_id, + result_type_id, op, arg, one_type_id, WithStmts::new_val(one), Some(arg_type), - Some(expr_type_id), + Some(result_type_id), ) } fn convert_post_increment( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, op: CBinOp, arg: CExprId, ) -> TranslationResult>> { // If we aren't going to be using the result, may as well do a simple pre-increment if ctx.is_unused() { - return self.convert_pre_increment(ctx, expr_type_id, op, arg); + return self.convert_pre_increment(ctx, expected_type_id, result_type_id, op, arg); } let op = op @@ -733,30 +743,48 @@ impl<'c> Translation<'c> { pub fn convert_unary_operator( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, op: CUnOp, arg: CExprId, ) -> TranslationResult>> { + let expr_type_id = expected_type_id.unwrap_or(result_type_id); let mut unary = match op { CUnOp::AddressOf => self.convert_address_of(ctx, expr_type_id, arg), - CUnOp::PreIncrement => { - self.convert_pre_increment(ctx, expr_type_id, CBinOp::AssignAdd, arg) - } - CUnOp::PreDecrement => { - self.convert_pre_increment(ctx, expr_type_id, CBinOp::AssignSubtract, arg) - } - CUnOp::PostIncrement => { - self.convert_post_increment(ctx, expr_type_id, CBinOp::AssignAdd, arg) - } - CUnOp::PostDecrement => { - self.convert_post_increment(ctx, expr_type_id, CBinOp::AssignSubtract, arg) - } + CUnOp::PreIncrement => self.convert_pre_increment( + ctx, + expected_type_id, + result_type_id, + CBinOp::AssignAdd, + arg, + ), + CUnOp::PreDecrement => self.convert_pre_increment( + ctx, + expected_type_id, + result_type_id, + CBinOp::AssignSubtract, + arg, + ), + CUnOp::PostIncrement => self.convert_post_increment( + ctx, + expected_type_id, + result_type_id, + CBinOp::AssignAdd, + arg, + ), + CUnOp::PostDecrement => self.convert_post_increment( + ctx, + expected_type_id, + result_type_id, + CBinOp::AssignSubtract, + arg, + ), CUnOp::Deref => self.convert_deref(ctx, expr_type_id, arg), - CUnOp::Plus => self.convert_expr(ctx.used(), arg, Some(expr_type_id)), // promotion is explicit in the clang AST + CUnOp::Plus => self.convert_expr(ctx.used(), arg, expected_type_id), // promotion is explicit in the clang AST CUnOp::Negate => self.convert_negate_operator(ctx, expr_type_id, arg), CUnOp::Complement => Ok(self - .convert_expr(ctx.used(), arg, Some(expr_type_id))? + .convert_expr(ctx.used(), arg, expected_type_id)? .map(|a| mk().unary_expr(UnOp::Not(Default::default()), a))), CUnOp::Not => { @@ -764,7 +792,7 @@ impl<'c> Translation<'c> { Ok(val.map(|x| mk().cast_expr(x, mk().abs_path_ty(vec!["core", "ffi", "c_int"])))) } CUnOp::Extension => { - let arg = self.convert_expr(ctx, arg, Some(expr_type_id))?; + let arg = self.convert_expr(ctx, arg, expected_type_id)?; Ok(arg) } CUnOp::Real | CUnOp::Imag | CUnOp::Coawait => { From dba5046d7f8849cda3d06bfafd1587b3ba5afe7c Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 6 Jul 2026 14:53:22 +0200 Subject: [PATCH 7/7] transpile: Cast assignment result to expected type if needed --- c2rust-transpile/src/translator/operators.rs | 19 ++++++++++++++----- c2rust-transpile/tests/snapshots.rs | 2 +- ...shots__transpile@exprs.c.2021.clang15.snap | 4 ++-- ...shots__transpile@exprs.c.2024.clang15.snap | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 9c2df6a96c..55bada9060 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -318,8 +318,8 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_with_rhs( &self, ctx: ExprContext, - _expected_type_id: Option, - _result_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, op: CBinOp, lhs: CExprId, rhs_type_id: CQualTypeId, @@ -485,9 +485,18 @@ impl<'c> Translation<'c> { } }; - Ok(assign_stmt.and_then(|assign_stmt| { - WithStmts::new(vec![mk().semi_stmt(assign_stmt)], read) - })) + let assign_result = self.make_cast( + ctx, + result_type_id, + expected_type_id.unwrap_or(result_type_id), + WithStmts::new_val(read), + )?; + + Ok(assign_stmt + .zip(assign_result) + .and_then(|(assign_stmt, assign_result)| { + WithStmts::new(vec![mk().semi_stmt(assign_stmt)], assign_result) + })) }) } diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 8f653efa7f..5b72ce1293 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -391,7 +391,7 @@ fn test_empty_init() { #[test] fn test_exprs() { - transpile("exprs.c").expect_compile_error(true).run(); + transpile("exprs.c").run(); } #[test] diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap index ac079975e4..c562665716 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap @@ -139,7 +139,7 @@ pub unsafe extern "C" fn pointer_arithmetic() { pub unsafe extern "C" fn assign_result() { let mut l: ::core::ffi::c_ulong = 0 as ::core::ffi::c_ulong; l = 1 as ::core::ffi::c_ulong; - let mut s1: size_t = l; + let mut s1: size_t = l as size_t; l = l.wrapping_add(2 as ::core::ffi::c_ulong); - let mut s2: size_t = l; + let mut s2: size_t = l as size_t; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap index b9e1ebd01d..e7b36aa49a 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap @@ -139,7 +139,7 @@ pub unsafe extern "C" fn pointer_arithmetic() { pub unsafe extern "C" fn assign_result() { let mut l: ::core::ffi::c_ulong = 0 as ::core::ffi::c_ulong; l = 1 as ::core::ffi::c_ulong; - let mut s1: size_t = l; + let mut s1: size_t = l as size_t; l = l.wrapping_add(2 as ::core::ffi::c_ulong); - let mut s2: size_t = l; + let mut s2: size_t = l as size_t; }