From 3057ee3b90e8f25aa6059e102e35e0326daa9111 Mon Sep 17 00:00:00 2001 From: Rua Date: Sat, 13 Jun 2026 17:39:48 +0200 Subject: [PATCH 1/7] transpile: Use `effective_lhs_ty` to cast RHS in `convert_assignment_operator` --- c2rust-transpile/src/translator/operators.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 3c9dfccbf7..03570372e4 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -200,7 +200,7 @@ impl<'c> Translation<'c> { .get_qual_type() .ok_or_else(|| format_err!("bad initial lhs type"))?; - // First, translate the rhs. Then, if it must match the lhs but doesn't, add a cast. + // First, translate the rhs. let mut rhs_translation = self.convert_expr(ctx.used(), rhs, Some(rhs_type_id))?; let lhs_rhs_types_must_match = { let lhs_resolved_ty = &self.ast_context.resolve_type(lhs_type_id.ctype); @@ -219,11 +219,11 @@ impl<'c> Translation<'c> { }) }; if lhs_rhs_types_must_match { + // If the rhs must match the lhs but doesn't, add a cast. // For compound assignment, use the compute type; for regular assignment, use lhs type let effective_lhs_ty = compute_lhs_type_id.unwrap_or(lhs_type_id); if effective_lhs_ty.ctype != rhs_type_id.ctype { - let new_rhs_ty = - self.convert_type(compute_lhs_type_id.unwrap_or(lhs_type_id).ctype)?; + let new_rhs_ty = self.convert_type(effective_lhs_ty.ctype)?; rhs_translation = rhs_translation.map(|val| mk().cast_expr(val, new_rhs_ty)); } } From e0b72a28ee4764a5ccf508eac51087ab23cb8378 Mon Sep 17 00:00:00 2001 From: Rua Date: Sat, 25 Jul 2026 16:13:24 +0200 Subject: [PATCH 2/7] transpile: Refactor to use `kind` in `operators.rs` and `bubble_expr_types` --- c2rust-transpile/src/c_ast/mod.rs | 14 ++++----- c2rust-transpile/src/translator/operators.rs | 30 ++++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 0cbfdca7bd..f7d2098433 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -1589,22 +1589,22 @@ impl TypedAstContext { let lhs_kind = &self.ast_context.c_exprs[&lhs].kind; let lhs_type_id = lhs_kind.get_qual_type().unwrap(); - let lhs_resolved_ty = self.ast_context.resolve_type(lhs_type_id.ctype); - let rhs_resolved_ty = self.ast_context.resolve_type(rhs_type_id.ctype); + let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; + let rhs_type_kind = &self.ast_context.resolve_type(rhs_type_id.ctype).kind; if op == CBinOp::Subtract - && lhs_resolved_ty.kind.is_pointer() - && rhs_resolved_ty.kind.is_pointer() + && lhs_type_kind.is_pointer() + && rhs_type_kind.is_pointer() { // Pointer difference operator should return `ptrdiff_t`. let new_type_id = self.ast_context.type_for_kind(&CTypeKind::PtrDiff); Some(CQualTypeId::new(new_type_id)) } else { - let neither_ptr = !lhs_resolved_ty.kind.is_pointer() - && !rhs_resolved_ty.kind.is_pointer(); + let neither_ptr = + !lhs_type_kind.is_pointer() && !rhs_type_kind.is_pointer(); if op.all_types_same() && neither_ptr { - if CTypeKind::PULLBACK_KINDS.contains(&lhs_resolved_ty.kind) { + if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { Some(lhs_type_id) } else { Some(rhs_type_id) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 03570372e4..6b62074d96 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -78,30 +78,30 @@ impl<'c> Translation<'c> { // If this operation will (in Rust) take args of the same type, then propagate our // expected type down to the translation of our argument expressions. - let lhs_resolved_ty = self.ast_context.resolve_type(lhs_type_id.ctype); - let rhs_resolved_ty = self.ast_context.resolve_type(rhs_type_id.ctype); - let expr_ty_kind = &self.ast_context.index(expr_type_id.ctype).kind; + let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; + let rhs_type_kind = &self.ast_context.resolve_type(rhs_type_id.ctype).kind; + let expr_type_kind = &self.ast_context.index(expr_type_id.ctype).kind; + // Addition and subtraction can accept one pointer argument for .offset(), in which // case we don't want to homogenize arg types. - if !lhs_resolved_ty.kind.is_pointer() - && !rhs_resolved_ty.kind.is_pointer() - && !expr_ty_kind.is_pointer() + if !lhs_type_kind.is_pointer() + && !rhs_type_kind.is_pointer() + && !expr_type_kind.is_pointer() { if op.all_types_same() { // Ops like division and bitxor accept inputs of their expected result type. lhs_type_id = expr_type_id; rhs_type_id = expr_type_id; - } else if op.input_types_same() && lhs_resolved_ty.kind != rhs_resolved_ty.kind - { + } else if op.input_types_same() && lhs_type_kind != rhs_type_kind { // Ops like comparisons require argument types to match, but the result type // doesn't inform us what type to choose. Select a synthetic definition of a // portable rust type (e.g. u64 or usize) if either arg is one. trace!( "Binary op arg types differ: {:?} vs {:?}", - lhs_resolved_ty.kind, - rhs_resolved_ty.kind + lhs_type_kind, + rhs_type_kind ); - let ty = if CTypeKind::PULLBACK_KINDS.contains(&lhs_resolved_ty.kind) { + let ty = if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { lhs_type_id } else { rhs_type_id @@ -202,13 +202,13 @@ impl<'c> Translation<'c> { // First, translate the rhs. let mut rhs_translation = self.convert_expr(ctx.used(), rhs, Some(rhs_type_id))?; + let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; + let rhs_type_kind = &self.ast_context.resolve_type(rhs_type_id.ctype).kind; + let lhs_rhs_types_must_match = { - let lhs_resolved_ty = &self.ast_context.resolve_type(lhs_type_id.ctype); - let rhs_resolved_ty = &self.ast_context.resolve_type(rhs_type_id.ctype); // Addition and subtraction can accept one pointer argument for .offset(), in which // case we don't want to homogenize arg types. - let neither_ptr = - !lhs_resolved_ty.kind.is_pointer() && !rhs_resolved_ty.kind.is_pointer(); + let neither_ptr = !lhs_type_kind.is_pointer() && !rhs_type_kind.is_pointer(); op.underlying_assignment().map_or(true, |op| { if op.is_pointer_arithmetic() { From 72757ad98939d89c733862f3b48f66f151c81d07 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 12 Oct 2026 06:43:54 +0200 Subject: [PATCH 3/7] transpile: Special case mixed operand types for complex arithmetic --- c2rust-transpile/src/c_ast/conversion.rs | 6 ++ c2rust-transpile/src/c_ast/mod.rs | 101 +++++++++++++++++-- c2rust-transpile/src/translator/operators.rs | 27 ++++- 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/c2rust-transpile/src/c_ast/conversion.rs b/c2rust-transpile/src/c_ast/conversion.rs index de549e21d1..c57b3ec452 100644 --- a/c2rust-transpile/src/c_ast/conversion.rs +++ b/c2rust-transpile/src/c_ast/conversion.rs @@ -462,6 +462,12 @@ impl ConversionContext { self.add_type(new_id, not_located(rust_type_kind)); self.processed_nodes .insert(new_id, self::node_types::OTHER_TYPE); + + let complex_kind = CTypeKind::Complex(CTypeId(new_id)); + let new_id = self.id_mapper.fresh_id(); + self.add_type(new_id, not_located(complex_kind)); + self.processed_nodes + .insert(new_id, self::node_types::OTHER_TYPE); } // Continue popping Clang nodes off of the stack of nodes we have promised to visit diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index f7d2098433..f17dfa4660 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -1603,7 +1603,80 @@ impl TypedAstContext { let neither_ptr = !lhs_type_kind.is_pointer() && !rhs_type_kind.is_pointer(); - if op.all_types_same() && neither_ptr { + if op.is_arithmetic() && neither_ptr { + struct Operand { + type_id: CQualTypeId, + is_complex: bool, + is_pullback_kind: bool, + } + + let operand = |type_id, type_kind: &CTypeKind| -> Operand { + let real_type_id = type_kind.complex_real_type(); + let is_complex = real_type_id.is_some(); + let is_pullback_kind = CTypeKind::PULLBACK_KINDS.contains( + real_type_id.map_or(type_kind, |type_id| { + &self.ast_context.resolve_type(type_id).kind + }), + ); + + Operand { + type_id, + is_complex, + is_pullback_kind, + } + }; + + let operands = [ + &operand(lhs_type_id, lhs_type_kind), + &operand(rhs_type_id, rhs_type_kind), + ]; + + // For complex arithmetic, complex and real operands can be mixed, + // and the result will be `Complex` if at least one of the operands + // is `Complex`. + let is_complex_arithmetic = + operands.iter().any(|operand| operand.is_complex); + + let pullback_operand = + match operands.map(|operand| operand.is_pullback_kind) { + [true, true] => { + // If both operands are pullback kinds, then we can + // choose freely, so just pick the lhs. But if the rhs + // is `Complex` and the lhs is not, then prefer the rhs. + if operands[1].is_complex && !operands[0].is_complex { + operands[1] + } else { + operands[0] + } + } + [true, false] => operands[0], + [false, true] => operands[1], + [false, false] => return, + }; + let mut new_result_type_id = pullback_operand.type_id; + + // If we are doing complex arithmetic, but the chosen operand is not + // complex itself, then we need to wrap it in a `Complex` ourselves. + if is_complex_arithmetic && !pullback_operand.is_complex { + let real_type_id = new_result_type_id.ctype; + let new_type_id = self + .ast_context + .try_type_for_kind(&CTypeKind::Complex(real_type_id)) + .unwrap_or_else(|| { + // We are guaranteed to have a `Complex` version of all + // pullback kinds, but that does not necessarily include + // typedefs. If a type doesn't exist with the original + // real type, resolve it. + let real_type_id = + self.ast_context.resolve_type_id(real_type_id); + self.ast_context + .type_for_kind(&CTypeKind::Complex(real_type_id)) + }); + new_result_type_id = new_type_id.into(); + } + + Some(new_result_type_id) + } else if op.is_bitwise() { if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { Some(lhs_type_id) } else { @@ -2574,13 +2647,11 @@ impl CBinOp { /// Does the rust equivalent of this operator have type (T, T) -> U? pub fn input_types_same(&self) -> bool { - self.all_types_same() || self.is_logical() || self.is_comparison() || self.is_assignment() - } - - /// Does the rust equivalent of this operator have type (T, T) -> T? - /// This ignores cases where one argument is a pointer and we translate to `.offset()`. - pub fn all_types_same(&self) -> bool { - self.is_arithmetic() || self.is_bitwise() + self.is_arithmetic() + || self.is_bitwise() + || self.is_logical() + || self.is_comparison() + || self.is_assignment() } } @@ -2825,6 +2896,12 @@ impl CQualTypeId { } } +impl From for CQualTypeId { + fn from(value: CTypeId) -> Self { + CQualTypeId::new(value) + } +} + // TODO: these may be interesting, but I'm not sure if they fit here: // // * UnaryTransformType @@ -3186,6 +3263,14 @@ impl CTypeKind { }; Some(ty) } + + fn complex_real_type(&self) -> Option { + if let Self::Complex(real_type_id) = *self { + Some(real_type_id) + } else { + None + } + } } impl Display for CTypeKind { diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 6b62074d96..f351360579 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -88,8 +88,21 @@ impl<'c> Translation<'c> { && !rhs_type_kind.is_pointer() && !expr_type_kind.is_pointer() { - if op.all_types_same() { - // Ops like division and bitxor accept inputs of their expected result type. + if op.is_arithmetic() { + lhs_type_id = expr_type_id; + rhs_type_id = expr_type_id; + + // For complex arithmetic, complex and real values can be mixed. + if let &CTypeKind::Complex(result_scalar_type_id) = expr_type_kind { + if !matches!(lhs_type_kind, CTypeKind::Complex(..)) { + lhs_type_id.ctype = result_scalar_type_id; + } + + if !matches!(rhs_type_kind, CTypeKind::Complex(..)) { + rhs_type_id.ctype = result_scalar_type_id; + } + } + } else if op.is_bitwise() { lhs_type_id = expr_type_id; rhs_type_id = expr_type_id; } else if op.input_types_same() && lhs_type_kind != rhs_type_kind { @@ -221,7 +234,15 @@ impl<'c> Translation<'c> { if lhs_rhs_types_must_match { // If the rhs must match the lhs but doesn't, add a cast. // For compound assignment, use the compute type; for regular assignment, use lhs type - let effective_lhs_ty = compute_lhs_type_id.unwrap_or(lhs_type_id); + let mut effective_lhs_ty = compute_lhs_type_id.unwrap_or(lhs_type_id); + + // For complex arithmetic, complex and real values can be mixed. + if let &CTypeKind::Complex(lhs_scalar_type_id) = lhs_type_kind { + if !matches!(rhs_type_kind, CTypeKind::Complex(..)) { + effective_lhs_ty.ctype = lhs_scalar_type_id; + } + } + if effective_lhs_ty.ctype != rhs_type_id.ctype { let new_rhs_ty = self.convert_type(effective_lhs_ty.ctype)?; rhs_translation = rhs_translation.map(|val| mk().cast_expr(val, new_rhs_ty)); From 2d58301aebd551b2659694c5abb1aead6e455041 Mon Sep 17 00:00:00 2001 From: Rua Date: Fri, 23 Oct 2026 21:55:31 +0200 Subject: [PATCH 4/7] transpile: In `convert_binary_expr`, remove unnecessary `.is_used()` check --- c2rust-transpile/src/translator/operators.rs | 75 +++++++++----------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index f351360579..c5c1c7ffd8 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -126,54 +126,47 @@ impl<'c> Translation<'c> { } } - if !ctx.is_used() { - Ok(self - .convert_expr(ctx, lhs, Some(lhs_type_id))? - .and_then_try(|_| self.convert_expr(ctx, rhs, Some(rhs_type_id)))? - .map(|_| self.panic_or_err("Binary expression is not supposed to be used"))) - } else { - let rhs_ctx = ctx; + let rhs_ctx = ctx; - // When we use methods on pointers (ie wrapping_offset_from or offset) - // we must ensure we have an explicit raw ptr for the self param, as - // self references do not decay - if op.is_pointer_arithmetic() { - let ty_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; + // When we use methods on pointers (ie wrapping_offset_from or offset) + // we must ensure we have an explicit raw ptr for the self param, as + // self references do not decay + if op.is_pointer_arithmetic() { + let ty_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; - if let CTypeKind::Pointer(_) = ty_kind { - ctx = ctx.decay_ref(); - } + if let CTypeKind::Pointer(_) = ty_kind { + ctx = ctx.decay_ref(); } + } - // Using `.is_none()` and `.is_some()` for null comparison means we don't - // have to rely on `trait PartialEq` as much and it is also more idiomatic. - if matches!(op, CBinOp::EqualEqual | CBinOp::NotEqual) { - let is_null = op == CBinOp::EqualEqual; - - if self.ast_context.is_null_expr(lhs) { - let val = self.convert_pointer_is_null(rhs_ctx, rhs, is_null)?; - return Ok(val.map(bool_to_int)); - } else if self.ast_context.is_null_expr(rhs) { - let val = self.convert_pointer_is_null(ctx, lhs, is_null)?; - return Ok(val.map(bool_to_int)); - } + // Using `.is_none()` and `.is_some()` for null comparison means we don't + // have to rely on `trait PartialEq` as much and it is also more idiomatic. + if matches!(op, CBinOp::EqualEqual | CBinOp::NotEqual) { + let is_null = op == CBinOp::EqualEqual; + + if self.ast_context.is_null_expr(lhs) { + let val = self.convert_pointer_is_null(rhs_ctx, rhs, is_null)?; + return Ok(val.map(bool_to_int)); + } else if self.ast_context.is_null_expr(rhs) { + let val = self.convert_pointer_is_null(ctx, lhs, is_null)?; + return Ok(val.map(bool_to_int)); } + } - let lhs_val = self.convert_expr(ctx, lhs, Some(lhs_type_id))?; - let rhs_val = self.convert_expr(rhs_ctx, rhs, Some(rhs_type_id))?; + let lhs_val = self.convert_expr(ctx, lhs, Some(lhs_type_id))?; + let rhs_val = self.convert_expr(rhs_ctx, rhs, Some(rhs_type_id))?; - lhs_val.zip(rhs_val).and_then_try(|(lhs_val, rhs_val)| { - self.convert_binary_operator( - ctx, - expr_type_id, - op, - lhs_type_id, - rhs_type_id, - lhs_val, - rhs_val, - ) - }) - } + lhs_val.zip(rhs_val).and_then_try(|(lhs_val, rhs_val)| { + self.convert_binary_operator( + ctx, + expr_type_id, + op, + lhs_type_id, + rhs_type_id, + lhs_val, + rhs_val, + ) + }) } } } From 34f0d254ae9b9c7f66e3f7a699b7bf4df5c7341b Mon Sep 17 00:00:00 2001 From: Rua Date: Fri, 23 Oct 2026 21:52:11 +0200 Subject: [PATCH 5/7] transpile: In `convert_binary_expr`, only check for pointers where applicable --- c2rust-transpile/src/c_ast/mod.rs | 9 --- c2rust-transpile/src/translator/operators.rs | 65 ++++++++++---------- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index f17dfa4660..d67c438045 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -2644,15 +2644,6 @@ impl CBinOp { _ => return None, }) } - - /// Does the rust equivalent of this operator have type (T, T) -> U? - pub fn input_types_same(&self) -> bool { - self.is_arithmetic() - || self.is_bitwise() - || self.is_logical() - || self.is_comparison() - || self.is_assignment() - } } impl From for BinOp { diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index c5c1c7ffd8..8f857c38db 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -68,6 +68,8 @@ impl<'c> Translation<'c> { "bad lhs type for assignment" ) })?; + let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; + let rhs_kind = &self.ast_context.index_unwrap_parens(rhs).kind; let mut rhs_type_id = rhs_kind.get_qual_type().ok_or_else(|| { format_translation_err!( @@ -75,23 +77,19 @@ impl<'c> Translation<'c> { "bad rhs type for assignment" ) })?; + let rhs_type_kind = &self.ast_context.resolve_type(rhs_type_id.ctype).kind; // If this operation will (in Rust) take args of the same type, then propagate our // expected type down to the translation of our argument expressions. - let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; - let rhs_type_kind = &self.ast_context.resolve_type(rhs_type_id.ctype).kind; - let expr_type_kind = &self.ast_context.index(expr_type_id.ctype).kind; - - // Addition and subtraction can accept one pointer argument for .offset(), in which - // case we don't want to homogenize arg types. - if !lhs_type_kind.is_pointer() - && !rhs_type_kind.is_pointer() - && !expr_type_kind.is_pointer() - { - if op.is_arithmetic() { + if op.is_arithmetic() { + // Addition and subtraction can accept one pointer argument for .offset(), + // in which case we don't want to homogenize arg types. + if !(lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer()) { lhs_type_id = expr_type_id; rhs_type_id = expr_type_id; + let expr_type_kind = &self.ast_context.index(expr_type_id.ctype).kind; + // For complex arithmetic, complex and real values can be mixed. if let &CTypeKind::Complex(result_scalar_type_id) = expr_type_kind { if !matches!(lhs_type_kind, CTypeKind::Complex(..)) { @@ -102,28 +100,31 @@ impl<'c> Translation<'c> { rhs_type_id.ctype = result_scalar_type_id; } } - } else if op.is_bitwise() { - lhs_type_id = expr_type_id; - rhs_type_id = expr_type_id; - } else if op.input_types_same() && lhs_type_kind != rhs_type_kind { - // Ops like comparisons require argument types to match, but the result type - // doesn't inform us what type to choose. Select a synthetic definition of a - // portable rust type (e.g. u64 or usize) if either arg is one. - trace!( - "Binary op arg types differ: {:?} vs {:?}", - lhs_type_kind, - rhs_type_kind - ); - let ty = if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { - lhs_type_id - } else { - rhs_type_id - }; - lhs_type_id = ty; - rhs_type_id = ty; - } else if matches!(op, ShiftLeft | ShiftRight) { - lhs_type_id = expr_type_id; } + } else if op.is_bitwise() { + lhs_type_id = expr_type_id; + rhs_type_id = expr_type_id; + } else if op.is_bitshift() { + lhs_type_id = expr_type_id; + } else if op.is_comparison() + && !(lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer()) + && lhs_type_kind != rhs_type_kind + { + // Comparisons require argument types to match, but the result type + // doesn't inform us what type to choose. Select a synthetic definition of a + // portable rust type (e.g. u64 or usize) if either arg is one. + trace!( + "Binary op arg types differ: {:?} vs {:?}", + lhs_type_kind, + rhs_type_kind + ); + let chosen_type_id = if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { + lhs_type_id + } else { + rhs_type_id + }; + lhs_type_id = chosen_type_id; + rhs_type_id = chosen_type_id; } let rhs_ctx = ctx; From dd6e91cc2333842f93452f9e57ddaf68bb725d5d Mon Sep 17 00:00:00 2001 From: Rua Date: Fri, 23 Oct 2026 21:43:48 +0200 Subject: [PATCH 6/7] transpile: In `convert_binary_expr`, set operand expected type only if overridden --- c2rust-transpile/src/translator/operators.rs | 29 +++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 8f857c38db..a118bbbf81 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -62,7 +62,7 @@ impl<'c> Translation<'c> { } let lhs_kind = &self.ast_context.index_unwrap_parens(lhs).kind; - let mut lhs_type_id = lhs_kind.get_qual_type().ok_or_else(|| { + let lhs_type_id = lhs_kind.get_qual_type().ok_or_else(|| { format_translation_err!( self.ast_context.display_loc(lhs_loc), "bad lhs type for assignment" @@ -71,7 +71,7 @@ impl<'c> Translation<'c> { let lhs_type_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; let rhs_kind = &self.ast_context.index_unwrap_parens(rhs).kind; - let mut rhs_type_id = rhs_kind.get_qual_type().ok_or_else(|| { + let rhs_type_id = rhs_kind.get_qual_type().ok_or_else(|| { format_translation_err!( self.ast_context.display_loc(rhs_loc), "bad rhs type for assignment" @@ -81,31 +81,34 @@ impl<'c> Translation<'c> { // If this operation will (in Rust) take args of the same type, then propagate our // expected type down to the translation of our argument expressions. + let mut lhs_expected_type_id = None; + let mut rhs_expected_type_id = None; + if op.is_arithmetic() { // Addition and subtraction can accept one pointer argument for .offset(), // in which case we don't want to homogenize arg types. if !(lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer()) { - lhs_type_id = expr_type_id; - rhs_type_id = expr_type_id; + let lhs_expected_type_id = lhs_expected_type_id.insert(expr_type_id); + let rhs_expected_type_id = rhs_expected_type_id.insert(expr_type_id); let expr_type_kind = &self.ast_context.index(expr_type_id.ctype).kind; // For complex arithmetic, complex and real values can be mixed. if let &CTypeKind::Complex(result_scalar_type_id) = expr_type_kind { if !matches!(lhs_type_kind, CTypeKind::Complex(..)) { - lhs_type_id.ctype = result_scalar_type_id; + lhs_expected_type_id.ctype = result_scalar_type_id; } if !matches!(rhs_type_kind, CTypeKind::Complex(..)) { - rhs_type_id.ctype = result_scalar_type_id; + rhs_expected_type_id.ctype = result_scalar_type_id; } } } } else if op.is_bitwise() { - lhs_type_id = expr_type_id; - rhs_type_id = expr_type_id; + lhs_expected_type_id = Some(expr_type_id); + rhs_expected_type_id = Some(expr_type_id); } else if op.is_bitshift() { - lhs_type_id = expr_type_id; + lhs_expected_type_id = Some(expr_type_id); } else if op.is_comparison() && !(lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer()) && lhs_type_kind != rhs_type_kind @@ -123,8 +126,8 @@ impl<'c> Translation<'c> { } else { rhs_type_id }; - lhs_type_id = chosen_type_id; - rhs_type_id = chosen_type_id; + lhs_expected_type_id = Some(chosen_type_id); + rhs_expected_type_id = Some(chosen_type_id); } let rhs_ctx = ctx; @@ -154,8 +157,8 @@ impl<'c> Translation<'c> { } } - let lhs_val = self.convert_expr(ctx, lhs, Some(lhs_type_id))?; - let rhs_val = self.convert_expr(rhs_ctx, rhs, Some(rhs_type_id))?; + let lhs_val = self.convert_expr(ctx, lhs, lhs_expected_type_id)?; + let rhs_val = self.convert_expr(rhs_ctx, rhs, rhs_expected_type_id)?; lhs_val.zip(rhs_val).and_then_try(|(lhs_val, rhs_val)| { self.convert_binary_operator( From e8b515989af4c9aa21f7e3f636fe96b01f15d457 Mon Sep 17 00:00:00 2001 From: Rua Date: Fri, 23 Oct 2026 20:03:46 +0200 Subject: [PATCH 7/7] transpile: In `convert_binary_expr`, refactor the handling of pointer args --- c2rust-transpile/src/translator/operators.rs | 101 ++++++++----------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index a118bbbf81..bb58cd6923 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -5,7 +5,7 @@ use super::*; impl<'c> Translation<'c> { pub fn convert_binary_expr( &self, - mut ctx: ExprContext, + ctx: ExprContext, expected_type_id: Option, result_type_id: CQualTypeId, op: CBinOp, @@ -53,14 +53,6 @@ impl<'c> Translation<'c> { ), _ => { - // Comparing references to pointers isn't consistently supported by rust - // and so we need to decay references to pointers to do so. See - // https://github.com/rust-lang/rust/issues/53772. This might be removable - // once the above issue is resolved. - if op == CBinOp::EqualEqual || op == CBinOp::NotEqual { - ctx = ctx.decay_ref(); - } - let lhs_kind = &self.ast_context.index_unwrap_parens(lhs).kind; let lhs_type_id = lhs_kind.get_qual_type().ok_or_else(|| { format_translation_err!( @@ -83,11 +75,17 @@ impl<'c> Translation<'c> { // expected type down to the translation of our argument expressions. let mut lhs_expected_type_id = None; let mut rhs_expected_type_id = None; + let mut lhs_ctx = ctx; if op.is_arithmetic() { // Addition and subtraction can accept one pointer argument for .offset(), // in which case we don't want to homogenize arg types. - if !(lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer()) { + if lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer() { + // When we use methods on pointers (ie wrapping_offset_from or offset) + // we must ensure we have an explicit raw ptr for the self param, as + // self references do not decay + lhs_ctx = lhs_ctx.decay_ref(); + } else { let lhs_expected_type_id = lhs_expected_type_id.insert(expr_type_id); let rhs_expected_type_id = rhs_expected_type_id.insert(expr_type_id); @@ -109,56 +107,45 @@ impl<'c> Translation<'c> { rhs_expected_type_id = Some(expr_type_id); } else if op.is_bitshift() { lhs_expected_type_id = Some(expr_type_id); - } else if op.is_comparison() - && !(lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer()) - && lhs_type_kind != rhs_type_kind - { - // Comparisons require argument types to match, but the result type - // doesn't inform us what type to choose. Select a synthetic definition of a - // portable rust type (e.g. u64 or usize) if either arg is one. - trace!( - "Binary op arg types differ: {:?} vs {:?}", - lhs_type_kind, - rhs_type_kind - ); - let chosen_type_id = if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { - lhs_type_id - } else { - rhs_type_id - }; - lhs_expected_type_id = Some(chosen_type_id); - rhs_expected_type_id = Some(chosen_type_id); - } - - let rhs_ctx = ctx; - - // When we use methods on pointers (ie wrapping_offset_from or offset) - // we must ensure we have an explicit raw ptr for the self param, as - // self references do not decay - if op.is_pointer_arithmetic() { - let ty_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; - - if let CTypeKind::Pointer(_) = ty_kind { - ctx = ctx.decay_ref(); - } - } - - // Using `.is_none()` and `.is_some()` for null comparison means we don't - // have to rely on `trait PartialEq` as much and it is also more idiomatic. - if matches!(op, CBinOp::EqualEqual | CBinOp::NotEqual) { - let is_null = op == CBinOp::EqualEqual; - - if self.ast_context.is_null_expr(lhs) { - let val = self.convert_pointer_is_null(rhs_ctx, rhs, is_null)?; - return Ok(val.map(bool_to_int)); - } else if self.ast_context.is_null_expr(rhs) { - let val = self.convert_pointer_is_null(ctx, lhs, is_null)?; - return Ok(val.map(bool_to_int)); + } else if op.is_comparison() { + if lhs_type_kind.is_pointer() || rhs_type_kind.is_pointer() { + // Comparing references to pointers isn't consistently supported by rust + // and so we need to decay references to pointers to do so. See + // https://github.com/rust-lang/rust/issues/53772. This might be removable + // once the above issue is resolved. + lhs_ctx = lhs_ctx.decay_ref(); + let is_null = op == CBinOp::EqualEqual; + + // Using `.is_none()` and `.is_some()` for null comparison means we don't + // have to rely on `trait PartialEq` as much and it is also more idiomatic. + if self.ast_context.is_null_expr(lhs) { + let val = self.convert_pointer_is_null(ctx, rhs, is_null)?; + return Ok(val.map(bool_to_int)); + } else if self.ast_context.is_null_expr(rhs) { + let val = self.convert_pointer_is_null(lhs_ctx, lhs, is_null)?; + return Ok(val.map(bool_to_int)); + } + } else if lhs_type_kind != rhs_type_kind { + // Comparisons require argument types to match, but the result type + // doesn't inform us what type to choose. Select a synthetic definition of a + // portable rust type (e.g. u64 or usize) if either arg is one. + trace!( + "Binary op arg types differ: {:?} vs {:?}", + lhs_type_kind, + rhs_type_kind + ); + let chosen_type_id = if CTypeKind::PULLBACK_KINDS.contains(lhs_type_kind) { + lhs_type_id + } else { + rhs_type_id + }; + lhs_expected_type_id = Some(chosen_type_id); + rhs_expected_type_id = Some(chosen_type_id); } } - let lhs_val = self.convert_expr(ctx, lhs, lhs_expected_type_id)?; - let rhs_val = self.convert_expr(rhs_ctx, rhs, rhs_expected_type_id)?; + let lhs_val = self.convert_expr(lhs_ctx, lhs, lhs_expected_type_id)?; + let rhs_val = self.convert_expr(ctx, rhs, rhs_expected_type_id)?; lhs_val.zip(rhs_val).and_then_try(|(lhs_val, rhs_val)| { self.convert_binary_operator(