From 02f2e1d7b81ddc2770387c371ae2fc34620486fe Mon Sep 17 00:00:00 2001 From: Rua Date: Sun, 26 Apr 2026 12:27:43 +0200 Subject: [PATCH 01/22] transpile: Split off `convert_decl_ref` --- c2rust-transpile/src/translator/mod.rs | 267 +++++++++++++------------ 1 file changed, 138 insertions(+), 129 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index a86e745bdd..272dcc47ef 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3002,135 +3002,9 @@ impl<'c> Translation<'c> { } } - DeclRef(qual_ty, decl_id, lrvalue) => { - let decl = &self - .ast_context - .get_decl(&decl_id) - .ok_or_else(|| format_err!("Missing declref {:?}", decl_id))? - .kind; - if ctx.expanding_macro.is_some() { - // TODO Determining which declarations have been declared within the scope of the const macro expr - // vs. which are out-of-scope of the const macro is non-trivial, - // so for now, we don't allow const macros referencing any declarations. - return Err(format_translation_err!( - self.ast_context.display_loc(src_loc), - "Cannot yet refer to declarations in a const expr", - )); - - #[allow(unreachable_code)] // TODO temporary (see above). - if let CDeclKind::Variable { - has_static_duration: true, - .. - } = decl - { - return Err(format_translation_err!( - self.ast_context.display_loc(src_loc), - "Cannot refer to static duration variable in a const expression", - )); - } - } - - let varname = decl.get_name().expect("expected variable name").to_owned(); - let rustname = self - .renamer - .borrow_mut() - .get(&decl_id) - .ok_or_else(|| format_err!("name not declared: '{}'", varname))?; - - // Import the referenced global decl into our submodule - if self.tcfg.reorganize_definitions { - self.add_import(decl_id, &rustname); - // match decl { - // CDeclKind::Variable { is_defn: false, .. } => {} - // _ => self.add_import(decl_id, &rustname), - // } - } - - let mut val = mk().path_expr(vec![rustname]); - let mut set_unsafe = false; - - match decl { - CDeclKind::EnumConstant { .. } => { - // If the variable is actually an `EnumConstant`, we need to add a cast to - // the expected integral type. - val = self.convert_cast_from_enum(qual_ty.ctype, val)?; - } - - CDeclKind::Function { parameters, .. } => { - // If we are referring to a function and need its address, we - // need to cast it to fn() to ensure that it has a real address. - if ctx.needs_address() { - let ty = self.convert_type(qual_ty.ctype)?; - let actual_ty = self - .type_converter - .borrow_mut() - .knr_function_type_with_parameters( - &self.ast_context, - qual_ty.ctype, - parameters, - )?; - if let Some(actual_ty) = actual_ty { - if actual_ty != ty { - // If we're casting a concrete function to - // a K&R function pointer type, use transmute - self.import_type(qual_ty.ctype); - - val = transmute_expr(actual_ty, ty, val); - set_unsafe = true; - } - } else { - let decl_kind = &self.ast_context[decl_id].kind; - let kind_with_declared_args = - self.ast_context.fn_decl_ty_with_declared_args(decl_kind); - - if let Some(ty) = self - .ast_context - .type_for_kind(&kind_with_declared_args) - .map(CQualTypeId::new) - { - let ty = self.convert_type(ty.ctype)?; - val = mk().cast_expr(val, ty); - } else { - val = mk().cast_expr(val, ty); - } - } - } - } - - CDeclKind::Variable { - has_static_duration, - has_thread_duration, - .. - } => { - // Accessing a static variable is unsafe. - // In the current nightly, this applies also to taking a raw pointer, - // but this requirement was removed in later versions of the - // `raw_ref_op` feature. - if (*has_static_duration || *has_thread_duration) - && (self.tcfg.edition < Edition2024 || !ctx.needs_address()) - { - set_unsafe = true; - } - } - - _ => {} - } - - if let CTypeKind::VariableArray(..) = - self.ast_context.resolve_type(qual_ty.ctype).kind - { - val = mk().method_call_expr(val, "as_mut_ptr", vec![]); - } - - // if the context wants a different type, add a cast - if let Some(expected_ty) = override_ty { - if lrvalue.is_rvalue() && expected_ty != qual_ty { - val = mk().cast_expr(val, self.convert_type(expected_ty.ctype)?); - } - } - - Ok(WithStmts::new_val(val).merge_unsafe(set_unsafe)) - } + DeclRef(qual_ty, decl_id, lrvalue) => self + .convert_decl_ref(ctx, qual_ty, decl_id, lrvalue, override_ty) + .map_err(|e| e.add_loc(self.ast_context.display_loc(src_loc))), OffsetOf(ty, ref kind) => match kind { OffsetOfKind::Constant(val) => Ok(WithStmts::new_val(self.mk_int_lit( @@ -3453,6 +3327,141 @@ impl<'c> Translation<'c> { } } + fn convert_decl_ref( + &self, + ctx: ExprContext, + qual_ty: CQualTypeId, + decl_id: CDeclId, + lrvalue: LRValue, + override_ty: Option, + ) -> TranslationResult>> { + let decl = &self + .ast_context + .get_decl(&decl_id) + .ok_or_else(|| format_err!("Missing declref {:?}", decl_id))? + .kind; + if ctx.expanding_macro.is_some() { + // TODO Determining which declarations have been declared within the scope of the const macro expr + // vs. which are out-of-scope of the const macro is non-trivial, + // so for now, we don't allow const macros referencing any declarations. + return Err(format_translation_err!( + None, + "Cannot yet refer to declarations in a const expr", + )); + + #[allow(unreachable_code)] // TODO temporary (see above). + if let CDeclKind::Variable { + has_static_duration: true, + .. + } = decl + { + return Err(format_translation_err!( + None, + "Cannot refer to static duration variable in a const expression", + )); + } + } + + let varname = decl.get_name().expect("expected variable name").to_owned(); + let rustname = self + .renamer + .borrow_mut() + .get(&decl_id) + .ok_or_else(|| format_err!("name not declared: '{}'", varname))?; + + // Import the referenced global decl into our submodule + if self.tcfg.reorganize_definitions { + self.add_import(decl_id, &rustname); + // match decl { + // CDeclKind::Variable { is_defn: false, .. } => {} + // _ => self.add_import(decl_id, &rustname), + // } + } + + let mut val = mk().path_expr(vec![rustname]); + let mut set_unsafe = false; + + match decl { + CDeclKind::EnumConstant { .. } => { + // If the variable is actually an `EnumConstant`, we need to add a cast to + // the expected integral type. + val = self.convert_cast_from_enum(qual_ty.ctype, val)?; + } + + CDeclKind::Function { parameters, .. } => { + // If we are referring to a function and need its address, we + // need to cast it to fn() to ensure that it has a real address. + if ctx.needs_address() { + let ty = self.convert_type(qual_ty.ctype)?; + let actual_ty = self + .type_converter + .borrow_mut() + .knr_function_type_with_parameters( + &self.ast_context, + qual_ty.ctype, + parameters, + )?; + if let Some(actual_ty) = actual_ty { + if actual_ty != ty { + // If we're casting a concrete function to + // a K&R function pointer type, use transmute + self.import_type(qual_ty.ctype); + + val = transmute_expr(actual_ty, ty, val); + set_unsafe = true; + } + } else { + let decl_kind = &self.ast_context[decl_id].kind; + let kind_with_declared_args = + self.ast_context.fn_decl_ty_with_declared_args(decl_kind); + + if let Some(ty) = self + .ast_context + .type_for_kind(&kind_with_declared_args) + .map(CQualTypeId::new) + { + let ty = self.convert_type(ty.ctype)?; + val = mk().cast_expr(val, ty); + } else { + val = mk().cast_expr(val, ty); + } + } + } + } + + CDeclKind::Variable { + has_static_duration, + has_thread_duration, + .. + } => { + // Accessing a static variable is unsafe. + // In the current nightly, this applies also to taking a raw pointer, + // but this requirement was removed in later versions of the + // `raw_ref_op` feature. + if (*has_static_duration || *has_thread_duration) + && (self.tcfg.edition < Edition2024 || !ctx.needs_address()) + { + set_unsafe = true; + } + } + + _ => {} + } + + if let CTypeKind::VariableArray(..) = self.ast_context.resolve_type(qual_ty.ctype).kind { + val = mk().method_call_expr(val, "as_mut_ptr", vec![]); + } + + // if the context wants a different type, add a cast + if let Some(expected_ty) = override_ty { + if lrvalue.is_rvalue() && expected_ty != qual_ty { + val = mk().cast_expr(val, self.convert_type(expected_ty.ctype)?); + } + } + + Ok(WithStmts::new_val(val).merge_unsafe(set_unsafe)) + } + pub fn convert_constant(&self, constant: ConstIntExpr) -> TranslationResult> { let expr = match constant { ConstIntExpr::U(n) => mk().lit_expr(mk().int_unsuffixed_lit(n as u128)), From 695125ddfb4dc0372ac00de0043a49f8047e10ea Mon Sep 17 00:00:00 2001 From: Rua Date: Sat, 25 Apr 2026 21:35:42 +0200 Subject: [PATCH 02/22] transpile: Modify enum/pointer cast methods to take `CQualTypeId` and return `WithStmts` --- c2rust-transpile/src/translator/enums.rs | 33 +++++++++------ c2rust-transpile/src/translator/mod.rs | 23 ++++------- c2rust-transpile/src/translator/pointers.rs | 46 +++++++++++---------- 3 files changed, 53 insertions(+), 49 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index d1a9c0b8c9..ed82cb7cc2 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -66,12 +66,14 @@ impl<'c> Translation<'c> { /// Translate a cast where the source type, but not the target type, is an `enum` type. pub fn convert_cast_from_enum( &self, - target_cty: CTypeId, - val: Box, - ) -> TranslationResult> { + target_cty: CQualTypeId, + mut val: Box, + ) -> TranslationResult>> { // Convert it to the expected integral type. - let ty = self.convert_type(target_cty)?; - Ok(mk().cast_expr(val, ty)) + let ty = self.convert_type(target_cty.ctype)?; + val = mk().cast_expr(val, ty); + + Ok(WithStmts::new_val(val)) } /// Translate a cast where the target type is an `enum` type. @@ -83,11 +85,11 @@ impl<'c> Translation<'c> { pub fn convert_cast_to_enum( &self, ctx: ExprContext, - enum_type_id: CTypeId, + enum_type_id: CQualTypeId, enum_id: CEnumId, expr: Option, - val: Box, - ) -> TranslationResult> { + mut val: Box, + ) -> TranslationResult>> { if let Some(expr) = expr { match self.ast_context[expr].kind { // This is the case of finding a variable which is an `EnumConstant` of the same @@ -102,19 +104,22 @@ impl<'c> Translation<'c> { // If this DeclRef expanded to a const macro, we actually need to insert a cast, // because the translation of a const macro skips implicit casts in its context. if !expr_is_macro { - return Ok(self.enum_constant_expr(enum_constant_id)); + val = self.enum_constant_expr(enum_constant_id); + return Ok(WithStmts::new_val(val)); } } CExprKind::Literal(_, CLiteral::Integer(i, _)) => { - return Ok(self.enum_for_i64(enum_type_id, i as i64)); + val = self.enum_for_i64(enum_type_id.ctype, i as i64); + return Ok(WithStmts::new_val(val)); } CExprKind::Unary(_, CUnOp::Negate, subexpr_id, _) => { if let &CExprKind::Literal(_, CLiteral::Integer(i, _)) = &self.ast_context[subexpr_id].kind { - return Ok(self.enum_for_i64(enum_type_id, -(i as i64))); + val = self.enum_for_i64(enum_type_id.ctype, -(i as i64)); + return Ok(WithStmts::new_val(val)); } } @@ -122,8 +127,10 @@ impl<'c> Translation<'c> { } } - let target_ty = self.convert_type(enum_type_id)?; - Ok(mk().cast_expr(val, target_ty)) + let target_ty = self.convert_type(enum_type_id.ctype)?; + val = mk().cast_expr(val, target_ty); + + Ok(WithStmts::new_val(val)) } /// Given an integer value this attempts to either generate the corresponding enum diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 272dcc47ef..9e61400809 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3385,7 +3385,7 @@ impl<'c> Translation<'c> { CDeclKind::EnumConstant { .. } => { // If the variable is actually an `EnumConstant`, we need to add a cast to // the expected integral type. - val = self.convert_cast_from_enum(qual_ty.ctype, val)?; + return self.convert_cast_from_enum(qual_ty, val); } CDeclKind::Function { parameters, .. } => { @@ -3620,20 +3620,16 @@ impl<'c> Translation<'c> { match kind { CastKind::BitCast | CastKind::NoOp => { - self.convert_pointer_to_pointer_cast(source_cty.ctype, target_cty.ctype, val) + self.convert_pointer_to_pointer_cast(source_cty, target_cty, val) } CastKind::IntegralToPointer => { - self.convert_integral_to_pointer_cast(ctx, source_cty.ctype, target_cty.ctype, val) + self.convert_integral_to_pointer_cast(ctx, source_cty, target_cty, val) } - CastKind::PointerToIntegral => self.convert_pointer_to_integral_cast( - ctx, - source_cty.ctype, - target_cty.ctype, - val, - expr, - ), + CastKind::PointerToIntegral => { + self.convert_pointer_to_integral_cast(ctx, source_cty, target_cty, val, expr) + } CastKind::IntegralCast | CastKind::FloatingCast @@ -3668,16 +3664,15 @@ impl<'c> Translation<'c> { { self.f128_cast_to(val, target_ty_kind) } else if let &CTypeKind::Enum(enum_decl_id) = target_ty_kind { - // Casts targeting `enum` types... - val.try_map(|val| { - self.convert_cast_to_enum(ctx, target_cty.ctype, enum_decl_id, expr, val) + val.and_then_try(|val| { + self.convert_cast_to_enum(ctx, target_cty, enum_decl_id, expr, val) }) } else if target_ty_kind.is_floating_type() && source_ty_kind.is_bool() { Ok(val.map(|val| { mk().cast_expr(mk().cast_expr(val, mk().path_ty(vec!["u8"])), target_ty) })) } else if let &CTypeKind::Enum(..) = source_ty_kind { - val.try_map(|val| self.convert_cast_from_enum(target_cty.ctype, val)) + val.and_then_try(|val| self.convert_cast_from_enum(target_cty, val)) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index a15fef518c..0f7920b2f7 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -426,35 +426,35 @@ impl<'c> Translation<'c> { pub fn convert_pointer_to_pointer_cast( &self, - source_cty: CTypeId, - target_cty: CTypeId, + source_cty: CQualTypeId, + target_cty: CQualTypeId, val: WithStmts>, ) -> TranslationResult>> { - if self.ast_context.is_function_pointer(target_cty) - || self.ast_context.is_function_pointer(source_cty) + if self.ast_context.is_function_pointer(target_cty.ctype) + || self.ast_context.is_function_pointer(source_cty.ctype) { let source_ty = self .type_converter .borrow_mut() - .convert(&self.ast_context, source_cty)?; + .convert(&self.ast_context, source_cty.ctype)?; let target_ty = self .type_converter .borrow_mut() - .convert(&self.ast_context, target_cty)?; + .convert(&self.ast_context, target_cty.ctype)?; if source_ty == target_ty { return Ok(val); } - self.import_type(source_cty); - self.import_type(target_cty); + self.import_type(source_cty.ctype); + self.import_type(target_cty.ctype); Ok(val.and_then(|val| { WithStmts::new_val(transmute_expr(source_ty, target_ty, val)).set_unsafe() })) } else { // Normal case - let target_ty = self.convert_type(target_cty)?; + let target_ty = self.convert_type(target_cty.ctype)?; Ok(val.map(|val| mk().cast_expr(val, target_ty))) } } @@ -462,14 +462,14 @@ impl<'c> Translation<'c> { pub fn convert_integral_to_pointer_cast( &self, ctx: ExprContext, - source_cty: CTypeId, - target_cty: CTypeId, + source_cty: CQualTypeId, + target_cty: CQualTypeId, val: WithStmts>, ) -> TranslationResult>> { - let source_ty_kind = &self.ast_context.resolve_type(source_cty).kind; - let target_ty = self.convert_type(target_cty)?; + let source_ty_kind = &self.ast_context.resolve_type(source_cty.ctype).kind; + let target_ty = self.convert_type(target_cty.ctype)?; - if self.ast_context.is_function_pointer(target_cty) { + if self.ast_context.is_function_pointer(target_cty.ctype) { if ctx.is_const { return Err(format_translation_err!( None, @@ -493,7 +493,7 @@ impl<'c> Translation<'c> { mk().cast_expr(val, target_ty) })) } else if let &CTypeKind::Enum(..) = source_ty_kind { - val.try_map(|val| self.convert_cast_from_enum(target_cty, val)) + val.and_then_try(|val| self.convert_cast_from_enum(target_cty, val)) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } @@ -502,8 +502,8 @@ impl<'c> Translation<'c> { pub fn convert_pointer_to_integral_cast( &self, ctx: ExprContext, - source_cty: CTypeId, - target_cty: CTypeId, + source_cty: CQualTypeId, + target_cty: CQualTypeId, val: WithStmts>, expr: Option, ) -> TranslationResult>> { @@ -514,16 +514,18 @@ impl<'c> Translation<'c> { )); } - let target_ty = self.convert_type(target_cty)?; - let source_ty = self.convert_type(source_cty)?; - let target_ty_kind = &self.ast_context.resolve_type(target_cty).kind; + let target_ty = self.convert_type(target_cty.ctype)?; + let source_ty = self.convert_type(source_cty.ctype)?; + let target_ty_kind = &self.ast_context.resolve_type(target_cty.ctype).kind; - if self.ast_context.is_function_pointer(source_cty) { + if self.ast_context.is_function_pointer(source_cty.ctype) { Ok(val.and_then(|val| { WithStmts::new_val(transmute_expr(source_ty, target_ty, val)).set_unsafe() })) } else if let &CTypeKind::Enum(enum_decl_id) = target_ty_kind { - val.try_map(|val| self.convert_cast_to_enum(ctx, target_cty, enum_decl_id, expr, val)) + val.and_then_try(|val| { + self.convert_cast_to_enum(ctx, target_cty, enum_decl_id, expr, val) + }) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } From f873624d4ac16694505676ab1d30b9284f511b27 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 19:20:40 +0200 Subject: [PATCH 03/22] transpile: Add `convert_enum_constant_decl_ref` --- c2rust-transpile/src/translator/enums.rs | 12 ++++++++++++ c2rust-transpile/src/translator/mod.rs | 10 ++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index ed82cb7cc2..0a41690704 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -63,6 +63,18 @@ impl<'c> Translation<'c> { WithStmts::new_val(self.enum_for_i64(type_id, 0)) } + /// Translates a `DeclRef` for an `EnumConstant`. + pub fn convert_enum_constant_decl_ref( + &self, + enum_constant_id: CEnumConstantId, + target_type_id: CQualTypeId, + ) -> TranslationResult>> { + let val = self.enum_constant_expr(enum_constant_id); + + // Add a cast to the expected integral type. + self.convert_cast_from_enum(target_type_id, val) + } + /// Translate a cast where the source type, but not the target type, is an `enum` type. pub fn convert_cast_from_enum( &self, diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 9e61400809..5fb88de821 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3362,6 +3362,10 @@ impl<'c> Translation<'c> { } } + if let CDeclKind::EnumConstant { .. } = decl { + return self.convert_enum_constant_decl_ref(decl_id, qual_ty); + } + let varname = decl.get_name().expect("expected variable name").to_owned(); let rustname = self .renamer @@ -3382,12 +3386,6 @@ impl<'c> Translation<'c> { let mut set_unsafe = false; match decl { - CDeclKind::EnumConstant { .. } => { - // If the variable is actually an `EnumConstant`, we need to add a cast to - // the expected integral type. - return self.convert_cast_from_enum(qual_ty, val); - } - CDeclKind::Function { parameters, .. } => { // If we are referring to a function and need its address, we // need to cast it to fn() to ensure that it has a real address. From 7b407fe635b59f1a625d8c12561dd3217790e4d1 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 18:11:57 +0200 Subject: [PATCH 04/22] transpile: Add `enum_constructor_expr`, replace params accordingly --- c2rust-transpile/src/translator/enums.rs | 37 +++++++++++---------- c2rust-transpile/src/translator/mod.rs | 8 ++--- c2rust-transpile/src/translator/pointers.rs | 6 ++-- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index 0a41690704..d5d4594645 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -7,8 +7,8 @@ use crate::{ diagnostics::TranslationResult, translator::{signed_int_expr, ConvertedDecl, ExprContext, Translation}, with_stmts::WithStmts, - CDeclKind, CEnumConstantId, CEnumId, CExprId, CExprKind, CLiteral, CQualTypeId, CTypeId, - CTypeKind, ConstIntExpr, + CDeclKind, CEnumConstantId, CEnumId, CExprId, CExprKind, CLiteral, CQualTypeId, CTypeKind, + ConstIntExpr, }; impl<'c> Translation<'c> { @@ -59,8 +59,8 @@ impl<'c> Translation<'c> { )) } - pub fn convert_enum_zero_initializer(&self, type_id: CTypeId) -> WithStmts> { - WithStmts::new_val(self.enum_for_i64(type_id, 0)) + pub fn convert_enum_zero_initializer(&self, enum_id: CEnumId) -> WithStmts> { + WithStmts::new_val(self.enum_for_i64(enum_id, 0)) } /// Translates a `DeclRef` for an `EnumConstant`. @@ -97,7 +97,6 @@ impl<'c> Translation<'c> { pub fn convert_cast_to_enum( &self, ctx: ExprContext, - enum_type_id: CQualTypeId, enum_id: CEnumId, expr: Option, mut val: Box, @@ -122,7 +121,7 @@ impl<'c> Translation<'c> { } CExprKind::Literal(_, CLiteral::Integer(i, _)) => { - val = self.enum_for_i64(enum_type_id.ctype, i as i64); + val = self.enum_for_i64(enum_id, i as i64); return Ok(WithStmts::new_val(val)); } @@ -130,7 +129,7 @@ impl<'c> Translation<'c> { if let &CExprKind::Literal(_, CLiteral::Integer(i, _)) = &self.ast_context[subexpr_id].kind { - val = self.enum_for_i64(enum_type_id.ctype, -(i as i64)); + val = self.enum_for_i64(enum_id, -(i as i64)); return Ok(WithStmts::new_val(val)); } } @@ -139,20 +138,14 @@ impl<'c> Translation<'c> { } } - let target_ty = self.convert_type(enum_type_id.ctype)?; - val = mk().cast_expr(val, target_ty); + val = self.enum_constructor_expr(enum_id, val); Ok(WithStmts::new_val(val)) } /// Given an integer value this attempts to either generate the corresponding enum /// variant directly, otherwise it converts a number to the enum type. - fn enum_for_i64(&self, enum_type_id: CTypeId, value: i64) -> Box { - let enum_id = match self.ast_context.resolve_type(enum_type_id).kind { - CTypeKind::Enum(enum_id) => enum_id, - _ => panic!("{:?} does not point to an `enum` type", enum_type_id), - }; - + fn enum_for_i64(&self, enum_id: CEnumId, value: i64) -> Box { if let Some(enum_constant_id) = self.enum_variant_for_i64(enum_id, value) { return self.enum_constant_expr(enum_constant_id); } @@ -164,8 +157,7 @@ impl<'c> Translation<'c> { _ => signed_int_expr(value), }; - let target_ty = self.convert_type(enum_type_id).unwrap(); - mk().cast_expr(value, target_ty) + self.enum_constructor_expr(enum_id, value) } /// Returns the id of the variant of `enum_id` whose value matches `value`, if any. @@ -192,6 +184,17 @@ impl<'c> Translation<'c> { mk().ident_expr(name) } + fn enum_constructor_expr(&self, enum_id: CEnumId, value: Box) -> Box { + let enum_name = self + .type_converter + .borrow() + .resolve_decl_name(enum_id) + .unwrap(); + self.add_import(enum_id, &enum_name); + + mk().cast_expr(value, mk().ident_ty(enum_name)) + } + fn is_variant_of_enum(&self, enum_id: CEnumId, enum_constant_id: CEnumConstantId) -> bool { let variants = match self.ast_context[enum_id].kind { CDeclKind::Enum { ref variants, .. } => variants, diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 5fb88de821..f6180f0d34 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3661,10 +3661,8 @@ impl<'c> Translation<'c> { self.ast_context[source_cty.ctype].kind { self.f128_cast_to(val, target_ty_kind) - } else if let &CTypeKind::Enum(enum_decl_id) = target_ty_kind { - val.and_then_try(|val| { - self.convert_cast_to_enum(ctx, target_cty, enum_decl_id, expr, val) - }) + } else if let &CTypeKind::Enum(enum_id) = target_ty_kind { + val.and_then_try(|val| self.convert_cast_to_enum(ctx, enum_id, expr, val)) } else if target_ty_kind.is_floating_type() && source_ty_kind.is_bool() { Ok(val.map(|val| { mk().cast_expr(mk().cast_expr(val, mk().path_ty(vec!["u8"])), target_ty) @@ -3936,7 +3934,7 @@ impl<'c> Translation<'c> { } // Transmute the number `0` into the enum type - CDeclKind::Enum { .. } => self.convert_enum_zero_initializer(type_id), + CDeclKind::Enum { .. } => self.convert_enum_zero_initializer(decl_id), _ => { return Err(TranslationError::generic( diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 0f7920b2f7..2678954731 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -522,10 +522,8 @@ impl<'c> Translation<'c> { Ok(val.and_then(|val| { WithStmts::new_val(transmute_expr(source_ty, target_ty, val)).set_unsafe() })) - } else if let &CTypeKind::Enum(enum_decl_id) = target_ty_kind { - val.and_then_try(|val| { - self.convert_cast_to_enum(ctx, target_cty, enum_decl_id, expr, val) - }) + } else if let &CTypeKind::Enum(enum_id) = target_ty_kind { + val.and_then_try(|val| self.convert_cast_to_enum(ctx, enum_id, expr, val)) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } From 9161caa689b0b0f92b52ec25e8d0e58598941970 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 17:26:21 +0200 Subject: [PATCH 05/22] transpile: Cast enums only if actually needed --- c2rust-transpile/src/translator/enums.rs | 48 +++++++++++++++---- c2rust-transpile/src/translator/mod.rs | 12 +++-- c2rust-transpile/src/translator/pointers.rs | 6 +-- ...shots__transpile@exprs.c.2021.clang15.snap | 2 +- ...shots__transpile@exprs.c.2024.clang15.snap | 2 +- ...s__transpile@macrocase.c.2021.clang15.snap | 2 +- ...s__transpile@macrocase.c.2024.clang15.snap | 2 +- 7 files changed, 54 insertions(+), 20 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index d5d4594645..04f1390fd9 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -66,26 +66,36 @@ impl<'c> Translation<'c> { /// Translates a `DeclRef` for an `EnumConstant`. pub fn convert_enum_constant_decl_ref( &self, + ctx: ExprContext, enum_constant_id: CEnumConstantId, target_type_id: CQualTypeId, ) -> TranslationResult>> { + let enum_id = self.ast_context.parents[&enum_constant_id]; let val = self.enum_constant_expr(enum_constant_id); // Add a cast to the expected integral type. - self.convert_cast_from_enum(target_type_id, val) + self.convert_cast_from_enum(ctx, enum_id, target_type_id, val) } /// Translate a cast where the source type, but not the target type, is an `enum` type. pub fn convert_cast_from_enum( &self, + ctx: ExprContext, + enum_id: CEnumId, target_cty: CQualTypeId, - mut val: Box, + val: Box, ) -> TranslationResult>> { - // Convert it to the expected integral type. - let ty = self.convert_type(target_cty.ctype)?; - val = mk().cast_expr(val, ty); - - Ok(WithStmts::new_val(val)) + // Cast from the enum's integral type to the expected integral type. + let source_cty = self.enum_integral_type(enum_id); + self.convert_cast( + ctx, + source_cty, + target_cty, + WithStmts::new_val(val), + None, + None, + None, + ) } /// Translate a cast where the target type is an `enum` type. @@ -97,6 +107,7 @@ impl<'c> Translation<'c> { pub fn convert_cast_to_enum( &self, ctx: ExprContext, + mut source_cty: CQualTypeId, enum_id: CEnumId, expr: Option, mut val: Box, @@ -138,9 +149,28 @@ impl<'c> Translation<'c> { } } - val = self.enum_constructor_expr(enum_id, val); + // We could be casting from enum to enum... + if let CTypeKind::Enum(source_enum_id) = + self.ast_context.resolve_type(source_cty.ctype).kind + { + // Casting to ourselves, the audacity! + if source_enum_id == enum_id { + return Ok(WithStmts::new_val(val)); + } + + source_cty = self.enum_integral_type(source_enum_id); + } + + let enum_integral_type = self.enum_integral_type(enum_id); + let mut val = WithStmts::new_val(val); + let source_type_kind = &self.ast_context.resolve_type(source_cty.ctype).kind; + let enum_integral_type_kind = &self.ast_context.resolve_type(enum_integral_type.ctype).kind; + + if source_type_kind != enum_integral_type_kind { + val = val.map(|val| self.enum_constructor_expr(enum_id, val)); + } - Ok(WithStmts::new_val(val)) + Ok(val) } /// Given an integer value this attempts to either generate the corresponding enum diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index f6180f0d34..18402da15a 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3363,7 +3363,7 @@ impl<'c> Translation<'c> { } if let CDeclKind::EnumConstant { .. } = decl { - return self.convert_enum_constant_decl_ref(decl_id, qual_ty); + return self.convert_enum_constant_decl_ref(ctx, decl_id, qual_ty); } let varname = decl.get_name().expect("expected variable name").to_owned(); @@ -3662,13 +3662,17 @@ impl<'c> Translation<'c> { { self.f128_cast_to(val, target_ty_kind) } else if let &CTypeKind::Enum(enum_id) = target_ty_kind { - val.and_then_try(|val| self.convert_cast_to_enum(ctx, enum_id, expr, val)) + val.and_then_try(|val| { + self.convert_cast_to_enum(ctx, source_cty, enum_id, expr, val) + }) } else if target_ty_kind.is_floating_type() && source_ty_kind.is_bool() { Ok(val.map(|val| { mk().cast_expr(mk().cast_expr(val, mk().path_ty(vec!["u8"])), target_ty) })) - } else if let &CTypeKind::Enum(..) = source_ty_kind { - val.and_then_try(|val| self.convert_cast_from_enum(target_cty, val)) + } else if let &CTypeKind::Enum(enum_id) = source_ty_kind { + val.and_then_try(|val| { + self.convert_cast_from_enum(ctx, enum_id, target_cty, val) + }) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 2678954731..7e6adb50b7 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -492,8 +492,8 @@ impl<'c> Translation<'c> { val = mk().cast_expr(val, mk().abs_path_ty(vec!["libc", "size_t"])); mk().cast_expr(val, target_ty) })) - } else if let &CTypeKind::Enum(..) = source_ty_kind { - val.and_then_try(|val| self.convert_cast_from_enum(target_cty, val)) + } else if let &CTypeKind::Enum(enum_id) = source_ty_kind { + val.and_then_try(|val| self.convert_cast_from_enum(ctx, enum_id, target_cty, val)) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } @@ -523,7 +523,7 @@ impl<'c> Translation<'c> { WithStmts::new_val(transmute_expr(source_ty, target_ty, val)).set_unsafe() })) } else if let &CTypeKind::Enum(enum_id) = target_ty_kind { - val.and_then_try(|val| self.convert_cast_to_enum(ctx, enum_id, expr, val)) + val.and_then_try(|val| self.convert_cast_to_enum(ctx, source_cty, enum_id, expr, val)) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } 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 56ee5f78bf..2f45733bbe 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 @@ -60,7 +60,7 @@ pub unsafe extern "C" fn unsigned_compound_desugaring() { let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; let mut u: ::core::ffi::c_uint = 0 as ::core::ffi::c_uint; let mut e: E = EA; - e = (e as ::core::ffi::c_uint).wrapping_add(u) as E; + e = e.wrapping_add(u); i = (i as ::core::ffi::c_uint).wrapping_add(u) as ::core::ffi::c_int; } #[no_mangle] 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 04e6019c9c..13e0833e62 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 @@ -60,7 +60,7 @@ pub unsafe extern "C" fn unsigned_compound_desugaring() { let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; let mut u: ::core::ffi::c_uint = 0 as ::core::ffi::c_uint; let mut e: E = EA; - e = (e as ::core::ffi::c_uint).wrapping_add(u) as E; + e = e.wrapping_add(u); i = (i as ::core::ffi::c_uint).wrapping_add(u) as ::core::ffi::c_int; } #[unsafe(no_mangle)] diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap index 07857d1fdf..50ff8785d9 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap @@ -18,7 +18,7 @@ pub const ZSTD_d_format: ::core::ffi::c_uint = 1000 as ::core::ffi::c_uint; #[no_mangle] pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> ::core::ffi::c_int { let mut bounds: ::core::ffi::c_int = 0 as ::core::ffi::c_int; - match dParam as ::core::ffi::c_uint { + match dParam { 100 => { bounds = 1 as ::core::ffi::c_int; return bounds; diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap index cdf1b52d30..b4b6f37e89 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap @@ -19,7 +19,7 @@ pub const ZSTD_d_format: ::core::ffi::c_uint = 1000 as ::core::ffi::c_uint; #[unsafe(no_mangle)] pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> ::core::ffi::c_int { let mut bounds: ::core::ffi::c_int = 0 as ::core::ffi::c_int; - match dParam as ::core::ffi::c_uint { + match dParam { 100 => { bounds = 1 as ::core::ffi::c_int; return bounds; From 9f76c0ec00dcb4cfe3571026057ea525d8bb3b6b Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 18:31:55 +0200 Subject: [PATCH 06/22] transpile: Use correct types for enums in `convert_pre_increment` --- c2rust-transpile/src/translator/enums.rs | 2 +- c2rust-transpile/src/translator/operators.rs | 29 +++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index 04f1390fd9..a5c15c3469 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -234,7 +234,7 @@ impl<'c> Translation<'c> { variants.contains(&enum_constant_id) } - fn enum_integral_type(&self, enum_id: CEnumId) -> CQualTypeId { + pub fn enum_integral_type(&self, enum_id: CEnumId) -> CQualTypeId { match self.ast_context[enum_id].kind { CDeclKind::Enum { integral_type: Some(integral_type), diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index a03f240ffe..7b9618bf2f 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -638,26 +638,35 @@ impl<'c> Translation<'c> { _ => mk().lit_expr(mk().int_unsuffixed_lit(1)), }; - let one_type_id = - if let CTypeKind::Pointer(..) = self.ast_context.resolve_type(arg_type.ctype).kind { - CQualTypeId::new( + let mut rhs_type_id = arg_type; + let mut compute_lhs_type_id = arg_type; + let mut compute_res_type_id = ty; + + match self.ast_context.resolve_type(arg_type.ctype).kind { + CTypeKind::Pointer(..) => { + rhs_type_id = CQualTypeId::new( self.ast_context .type_for_kind(&CTypeKind::Int) .ok_or_else(|| format_err!("couldn't find type for CTypeKind::Int"))?, - ) - } else { - arg_type - }; + ); + } + CTypeKind::Enum(enum_id) => { + rhs_type_id = self.enum_integral_type(enum_id); + compute_lhs_type_id = rhs_type_id; + compute_res_type_id = rhs_type_id; + } + _ => {} + }; self.convert_assignment_operator_with_rhs( ctx.used(), op, ty, arg, - one_type_id, + rhs_type_id, WithStmts::new_val(one), - Some(arg_type), - Some(ty), + Some(compute_lhs_type_id), + Some(compute_res_type_id), ) } From 05af591dea55dccaf14b0ab5ee960932a28f1441 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 17:37:55 +0200 Subject: [PATCH 07/22] transpile: Add `EnumMode` to select enum output type --- c2rust-transpile/src/lib.rs | 3 ++- c2rust-transpile/src/translator/enums.rs | 33 +++++++++++++++++------- c2rust-transpile/src/translator/mod.rs | 5 ++++ c2rust-transpile/tests/snapshots.rs | 4 +-- c2rust/src/bin/c2rust-transpile.rs | 3 ++- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/c2rust-transpile/src/lib.rs b/c2rust-transpile/src/lib.rs index 20fdc8cfea..a1db5911cc 100644 --- a/c2rust-transpile/src/lib.rs +++ b/c2rust-transpile/src/lib.rs @@ -37,7 +37,7 @@ use c2rust_ast_exporter as ast_exporter; use crate::build_files::{emit_build_files, get_build_dir, CrateConfig}; use crate::compile_cmds::get_compile_commands; -pub use crate::translator::ReplaceMode; +pub use crate::translator::{EnumMode, ReplaceMode}; use std::prelude::v1::Vec; type PragmaVec = Vec<(&'static str, Vec<&'static str>)>; @@ -108,6 +108,7 @@ pub struct TranspilerConfig { pub log_level: log::LevelFilter, pub edition: RustEdition, pub deny_unsafe_op_in_unsafe_fn: bool, + pub enum_mode: EnumMode, /// Run `c2rust-postprocess` after transpiling and potentially refactoring. pub postprocess: bool, diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index a5c15c3469..cdee2a08ec 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -5,7 +5,7 @@ use syn::Expr; use crate::c_ast::CUnOp; use crate::{ diagnostics::TranslationResult, - translator::{signed_int_expr, ConvertedDecl, ExprContext, Translation}, + translator::{signed_int_expr, ConvertedDecl, EnumMode, ExprContext, Translation}, with_stmts::WithStmts, CDeclKind, CEnumConstantId, CEnumId, CExprId, CExprKind, CLiteral, CQualTypeId, CTypeKind, ConstIntExpr, @@ -23,10 +23,15 @@ impl<'c> Translation<'c> { .borrow() .resolve_decl_name(enum_id) .expect("Enums should already be renamed"); - let ty = self.convert_type(integral_type.ctype)?; - Ok(ConvertedDecl::Item( - mk().span(span).pub_().type_item(enum_name, ty), - )) + let integral_type_rs = self.convert_type(integral_type.ctype)?; + let item = match self.tcfg.enum_mode { + EnumMode::Consts => mk() + .span(span) + .pub_() + .type_item(enum_name, integral_type_rs), + }; + + Ok(ConvertedDecl::Item(item)) } pub fn convert_enum_constant( @@ -163,11 +168,17 @@ impl<'c> Translation<'c> { let enum_integral_type = self.enum_integral_type(enum_id); let mut val = WithStmts::new_val(val); - let source_type_kind = &self.ast_context.resolve_type(source_cty.ctype).kind; - let enum_integral_type_kind = &self.ast_context.resolve_type(enum_integral_type.ctype).kind; - if source_type_kind != enum_integral_type_kind { - val = val.map(|val| self.enum_constructor_expr(enum_id, val)); + match self.tcfg.enum_mode { + EnumMode::Consts => { + let source_type_kind = &self.ast_context.resolve_type(source_cty.ctype).kind; + let enum_integral_type_kind = + &self.ast_context.resolve_type(enum_integral_type.ctype).kind; + + if source_type_kind != enum_integral_type_kind { + val = val.map(|val| self.enum_constructor_expr(enum_id, val)); + } + } } Ok(val) @@ -222,7 +233,9 @@ impl<'c> Translation<'c> { .unwrap(); self.add_import(enum_id, &enum_name); - mk().cast_expr(value, mk().ident_ty(enum_name)) + match self.tcfg.enum_mode { + EnumMode::Consts => mk().cast_expr(value, mk().ident_ty(enum_name)), + } } fn is_variant_of_enum(&self, enum_id: CEnumId, enum_constant_id: CEnumConstantId) -> bool { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 18402da15a..76ae88b53c 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -4364,3 +4364,8 @@ fn neg_expr(arg: Box) -> Box { fn wrapping_neg_expr(arg: Box) -> Box { mk().method_call_expr(arg, "wrapping_neg", vec![]) } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EnumMode { + Consts, +} diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 4f10e330ea..c796a3165e 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -11,8 +11,7 @@ use c2rust_rust_tools::RustEdition; use c2rust_rust_tools::RustEdition::Edition2021; use c2rust_rust_tools::RustEdition::Edition2024; use c2rust_transpile::renamer::RUST_KEYWORDS; -use c2rust_transpile::ReplaceMode; -use c2rust_transpile::TranspilerConfig; +use c2rust_transpile::{EnumMode, ReplaceMode, TranspilerConfig}; use itertools::Itertools; fn config(edition: RustEdition) -> TranspilerConfig { @@ -68,6 +67,7 @@ fn config(edition: RustEdition) -> TranspilerConfig { .unwrap() .to_path_buf(), ), + enum_mode: EnumMode::Consts, } } diff --git a/c2rust/src/bin/c2rust-transpile.rs b/c2rust/src/bin/c2rust-transpile.rs index 39f5ada8e7..f4a74bc84c 100644 --- a/c2rust/src/bin/c2rust-transpile.rs +++ b/c2rust/src/bin/c2rust-transpile.rs @@ -4,7 +4,7 @@ use regex::Regex; use std::{ffi::OsStr, fs, path::PathBuf}; use c2rust_rust_tools::RustEdition; -use c2rust_transpile::{Diagnostic, ReplaceMode, TranspilerConfig}; +use c2rust_transpile::{Diagnostic, EnumMode, ReplaceMode, TranspilerConfig}; #[derive(Debug, Parser)] #[clap( @@ -326,6 +326,7 @@ fn main() { log_level: args.log_level, edition: args.edition, deny_unsafe_op_in_unsafe_fn: args.deny_unsafe_op_in_unsafe_fn, + enum_mode: EnumMode::Consts, }; // binaries imply emit-build-files if !tcfg.binaries.is_empty() { From 2413faf72c8ea0d89f4e859f2f1f27bd57f5754c Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 19:59:32 +0200 Subject: [PATCH 08/22] transpile: Add `EnumMode::NewType` and make it the default --- c2rust-transpile/src/translator/enums.rs | 49 +++++++++++++++---- c2rust-transpile/src/translator/mod.rs | 4 +- c2rust-transpile/src/translator/pointers.rs | 11 ++++- c2rust-transpile/tests/snapshots.rs | 2 +- ...shots__transpile@exprs.c.2021.clang15.snap | 20 +++++--- ...shots__transpile@exprs.c.2024.clang15.snap | 20 +++++--- ...s__transpile@macrocase.c.2021.clang15.snap | 10 ++-- ...s__transpile@macrocase.c.2024.clang15.snap | 10 ++-- ...ots__transpile@records.c.2021.clang15.snap | 20 +++++--- ...ots__transpile@records.c.2024.clang15.snap | 20 +++++--- ..._transpile@scalar_init.c.2021.clang15.snap | 6 ++- ..._transpile@scalar_init.c.2024.clang15.snap | 6 ++- c2rust/src/bin/c2rust-transpile.rs | 2 +- tests/unit/enums/src/test_enums.rs | 4 +- tests/unit/misc/src/test_uninitialized.rs | 6 +-- 15 files changed, 127 insertions(+), 63 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index cdee2a08ec..1458090934 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -25,6 +25,15 @@ impl<'c> Translation<'c> { .expect("Enums should already be renamed"); let integral_type_rs = self.convert_type(integral_type.ctype)?; let item = match self.tcfg.enum_mode { + EnumMode::NewType => { + let field = mk().pub_().enum_field(integral_type_rs); + mk().span(span) + .call_attr("derive", vec!["Clone", "Copy"]) + .call_attr("repr", vec!["transparent"]) + .pub_() + .struct_item(enum_name, vec![field], true) + } + EnumMode::Consts => mk() .span(span) .pub_() @@ -51,16 +60,16 @@ impl<'c> Translation<'c> { .borrow() .resolve_decl_name(enum_id) .expect("Enums should already be renamed"); - self.add_import(enum_id, &enum_name); let ty = mk().ident_ty(enum_name); let val = match value { ConstIntExpr::I(value) => signed_int_expr(value), ConstIntExpr::U(value) => mk().lit_expr(mk().int_unsuffixed_lit(value as u128)), }; + let init = self.enum_constructor_expr(enum_id, val); Ok(ConvertedDecl::Item( - mk().span(span).pub_().const_item(name, ty, val), + mk().span(span).pub_().const_item(name, ty, init), )) } @@ -88,8 +97,14 @@ impl<'c> Translation<'c> { ctx: ExprContext, enum_id: CEnumId, target_cty: CQualTypeId, - val: Box, + mut val: Box, ) -> TranslationResult>> { + match self.tcfg.enum_mode { + // First extract the enum's inner type... + EnumMode::NewType => val = self.integer_from_enum(val), + EnumMode::Consts => {} + } + // Cast from the enum's integral type to the expected integral type. let source_cty = self.enum_integral_type(enum_id); self.convert_cast( @@ -103,12 +118,15 @@ impl<'c> Translation<'c> { ) } - /// Translate a cast where the target type is an `enum` type. - /// - /// When translating variable references to `EnumConstant`s, we always insert casts to the - /// expected type. In C, `EnumConstant`s have some integral type, _not_ the enum type. However, - /// if we then immediately have a cast to convert this variable back into an enum type, we would - /// like to produce Rust with _no_ casts. This function handles this simplification. + /// Gets the inner integral value of an enum value. + pub fn integer_from_enum(&self, val: Box) -> Box { + match self.tcfg.enum_mode { + EnumMode::NewType => mk().anon_field_expr(val, 0), + EnumMode::Consts => val, + } + } + + /// Translates a cast where the target type is an `enum` type. pub fn convert_cast_to_enum( &self, ctx: ExprContext, @@ -163,6 +181,12 @@ impl<'c> Translation<'c> { return Ok(WithStmts::new_val(val)); } + match self.tcfg.enum_mode { + // Enum-to-enum casts need to be translated via the inner value as an intermediate. + EnumMode::NewType => val = self.integer_from_enum(val), + EnumMode::Consts => {} + } + source_cty = self.enum_integral_type(source_enum_id); } @@ -170,6 +194,12 @@ impl<'c> Translation<'c> { let mut val = WithStmts::new_val(val); match self.tcfg.enum_mode { + EnumMode::NewType => { + val = + self.convert_cast(ctx, source_cty, enum_integral_type, val, None, None, None)?; + val = val.map(|val| self.enum_constructor_expr(enum_id, val)); + } + EnumMode::Consts => { let source_type_kind = &self.ast_context.resolve_type(source_cty.ctype).kind; let enum_integral_type_kind = @@ -234,6 +264,7 @@ impl<'c> Translation<'c> { self.add_import(enum_id, &enum_name); match self.tcfg.enum_mode { + EnumMode::NewType => mk().call_expr(mk().ident_expr(enum_name), vec![value]), EnumMode::Consts => mk().cast_expr(value, mk().ident_ty(enum_name)), } } diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 76ae88b53c..ed49f6163f 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3937,7 +3937,6 @@ impl<'c> Translation<'c> { field.map(|field| mk().struct_expr(vec![name], vec![field])) } - // Transmute the number `0` into the enum type CDeclKind::Enum { .. } => self.convert_enum_zero_initializer(decl_id), _ => { @@ -4042,7 +4041,7 @@ impl<'c> Translation<'c> { } let val = if ty.is_enum() { - mk().cast_expr(val, mk().path_ty(vec!["u64"])) + self.integer_from_enum(val) } else { val }; @@ -4367,5 +4366,6 @@ fn wrapping_neg_expr(arg: Box) -> Box { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EnumMode { + NewType, Consts, } diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 7e6adb50b7..bd63c4fbc7 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -246,6 +246,10 @@ impl<'c> Translation<'c> { let lhs_node_type = lhs_node .get_type() .ok_or_else(|| format_err!("lhs node bad type"))?; + let rhs_node_type = rhs_node + .get_type() + .ok_or_else(|| format_err!("rhs node bad type"))?; + if self .ast_context .resolve_type(lhs_node_type) @@ -259,7 +263,12 @@ impl<'c> Translation<'c> { } let rhs = self.convert_expr(ctx.used(), rhs, None)?; - rhs.and_then_try(|rhs| { + rhs.and_then_try(|mut rhs| { + // C allows enums to index arrays directly without inserting a numeric cast. + if let CTypeKind::Enum(..) = self.ast_context.resolve_type(rhs_node_type).kind { + rhs = self.integer_from_enum(rhs); + } + let simple_index_array = if ctx.needs_address() { // We can't necessarily index into an array if we're using // that element to compute an address. diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index c796a3165e..3f1e38d7d6 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -67,7 +67,7 @@ fn config(edition: RustEdition) -> TranspilerConfig { .unwrap() .to_path_buf(), ), - enum_mode: EnumMode::Consts, + enum_mode: EnumMode::NewType, } } 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 2f45733bbe..ead4f9b7fb 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 @@ -15,13 +15,17 @@ expression: cat tests/snapshots/exprs.2021.clang15.rs extern "C" { fn puts(str: *const ::core::ffi::c_char) -> ::core::ffi::c_int; } -pub type E = ::core::ffi::c_uint; -pub const EA: E = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct E(pub ::core::ffi::c_uint); +pub const EA: E = E(0); pub type int_t = ::core::ffi::c_int; -pub type C2Rust_Unnamed = ::core::ffi::c_uint; -pub const C: C2Rust_Unnamed = 2; -pub const B: C2Rust_Unnamed = 1; -pub const A: C2Rust_Unnamed = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint); +pub const C: C2Rust_Unnamed = C2Rust_Unnamed(2); +pub const B: C2Rust_Unnamed = C2Rust_Unnamed(1); +pub const A: C2Rust_Unnamed = C2Rust_Unnamed(0); unsafe extern "C" fn side_effect() -> ::core::ffi::c_int { puts(b"the return of side effect\0".as_ptr() as *const ::core::ffi::c_char); return 10 as ::core::ffi::c_int; @@ -60,7 +64,7 @@ pub unsafe extern "C" fn unsigned_compound_desugaring() { let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; let mut u: ::core::ffi::c_uint = 0 as ::core::ffi::c_uint; let mut e: E = EA; - e = e.wrapping_add(u); + e = E(e.0.wrapping_add(u)); i = (i as ::core::ffi::c_uint).wrapping_add(u) as ::core::ffi::c_int; } #[no_mangle] @@ -72,7 +76,7 @@ pub unsafe extern "C" fn cast_literals() { } #[no_mangle] pub unsafe extern "C" fn compound_literal() { - let mut i: ::core::ffi::c_int = B as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = B.0 as ::core::ffi::c_int; } #[no_mangle] pub unsafe extern "C" fn statement_expr() { 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 13e0833e62..94b05654a1 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 @@ -15,13 +15,17 @@ expression: cat tests/snapshots/exprs.2024.clang15.rs unsafe extern "C" { unsafe fn puts(str: *const ::core::ffi::c_char) -> ::core::ffi::c_int; } -pub type E = ::core::ffi::c_uint; -pub const EA: E = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct E(pub ::core::ffi::c_uint); +pub const EA: E = E(0); pub type int_t = ::core::ffi::c_int; -pub type C2Rust_Unnamed = ::core::ffi::c_uint; -pub const C: C2Rust_Unnamed = 2; -pub const B: C2Rust_Unnamed = 1; -pub const A: C2Rust_Unnamed = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint); +pub const C: C2Rust_Unnamed = C2Rust_Unnamed(2); +pub const B: C2Rust_Unnamed = C2Rust_Unnamed(1); +pub const A: C2Rust_Unnamed = C2Rust_Unnamed(0); unsafe extern "C" fn side_effect() -> ::core::ffi::c_int { puts(b"the return of side effect\0".as_ptr() as *const ::core::ffi::c_char); return 10 as ::core::ffi::c_int; @@ -60,7 +64,7 @@ pub unsafe extern "C" fn unsigned_compound_desugaring() { let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; let mut u: ::core::ffi::c_uint = 0 as ::core::ffi::c_uint; let mut e: E = EA; - e = e.wrapping_add(u); + e = E(e.0.wrapping_add(u)); i = (i as ::core::ffi::c_uint).wrapping_add(u) as ::core::ffi::c_int; } #[unsafe(no_mangle)] @@ -72,7 +76,7 @@ pub unsafe extern "C" fn cast_literals() { } #[unsafe(no_mangle)] pub unsafe extern "C" fn compound_literal() { - let mut i: ::core::ffi::c_int = B as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = B.0 as ::core::ffi::c_int; } #[unsafe(no_mangle)] pub unsafe extern "C" fn statement_expr() { diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap index 50ff8785d9..c41d837292 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap @@ -11,14 +11,16 @@ expression: cat tests/snapshots/macrocase.2021.clang15.rs unused_assignments, unused_mut )] -pub type ZSTD_dParameter = ::core::ffi::c_uint; -pub const ZSTD_d_experimentalParam1: ZSTD_dParameter = 1000; -pub const ZSTD_d_windowLogMax: ZSTD_dParameter = 100; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct ZSTD_dParameter(pub ::core::ffi::c_uint); +pub const ZSTD_d_experimentalParam1: ZSTD_dParameter = ZSTD_dParameter(1000); +pub const ZSTD_d_windowLogMax: ZSTD_dParameter = ZSTD_dParameter(100); pub const ZSTD_d_format: ::core::ffi::c_uint = 1000 as ::core::ffi::c_uint; #[no_mangle] pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> ::core::ffi::c_int { let mut bounds: ::core::ffi::c_int = 0 as ::core::ffi::c_int; - match dParam { + match dParam.0 { 100 => { bounds = 1 as ::core::ffi::c_int; return bounds; diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap index b4b6f37e89..e4cc4f1de0 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap @@ -12,14 +12,16 @@ expression: cat tests/snapshots/macrocase.2024.clang15.rs unused_assignments, unused_mut )] -pub type ZSTD_dParameter = ::core::ffi::c_uint; -pub const ZSTD_d_experimentalParam1: ZSTD_dParameter = 1000; -pub const ZSTD_d_windowLogMax: ZSTD_dParameter = 100; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct ZSTD_dParameter(pub ::core::ffi::c_uint); +pub const ZSTD_d_experimentalParam1: ZSTD_dParameter = ZSTD_dParameter(1000); +pub const ZSTD_d_windowLogMax: ZSTD_dParameter = ZSTD_dParameter(100); pub const ZSTD_d_format: ::core::ffi::c_uint = 1000 as ::core::ffi::c_uint; #[unsafe(no_mangle)] pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> ::core::ffi::c_int { let mut bounds: ::core::ffi::c_int = 0 as ::core::ffi::c_int; - match dParam { + match dParam.0 { 100 => { bounds = 1 as ::core::ffi::c_int; return bounds; diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2021.clang15.snap index d24f14826f..8cfbb62b5d 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2021.clang15.snap @@ -14,9 +14,11 @@ expression: cat tests/snapshots/records.2021.clang15.rs #[derive(Copy, Clone)] #[repr(C)] pub struct AnonEnumInStruct {} -pub type C2Rust_Unnamed = ::core::ffi::c_uint; -pub const VALUE2: C2Rust_Unnamed = 1; -pub const VALUE1: C2Rust_Unnamed = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint); +pub const VALUE2: C2Rust_Unnamed = C2Rust_Unnamed(1); +pub const VALUE1: C2Rust_Unnamed = C2Rust_Unnamed(0); #[derive(Copy, Clone)] #[repr(C)] pub struct AnonStructInStruct { @@ -40,9 +42,11 @@ pub struct InsideStruct { pub union AnonEnumInUnion { pub a: ::core::ffi::c_int, } -pub type C2Rust_Unnamed_1 = ::core::ffi::c_uint; -pub const VALUE4: C2Rust_Unnamed_1 = 1; -pub const VALUE3: C2Rust_Unnamed_1 = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_1(pub ::core::ffi::c_uint); +pub const VALUE4: C2Rust_Unnamed_1 = C2Rust_Unnamed_1(1); +pub const VALUE3: C2Rust_Unnamed_1 = C2Rust_Unnamed_1(0); #[derive(Copy, Clone)] #[repr(C)] pub union AnonStructInUnion { @@ -66,7 +70,7 @@ pub struct InsideUnion { } #[no_mangle] pub unsafe extern "C" fn struct_declaration() { - let mut value: ::core::ffi::c_int = VALUE2 as ::core::ffi::c_int; + let mut value: ::core::ffi::c_int = VALUE2.0 as ::core::ffi::c_int; let mut a: AnonEnumInStruct = AnonEnumInStruct {}; let mut b: AnonStructInStruct = AnonStructInStruct { c2rust_unnamed: C2Rust_Unnamed_0 { some_number: 0 }, @@ -77,7 +81,7 @@ pub unsafe extern "C" fn struct_declaration() { } #[no_mangle] pub unsafe extern "C" fn union_declaration() { - let mut value: ::core::ffi::c_int = VALUE4 as ::core::ffi::c_int; + let mut value: ::core::ffi::c_int = VALUE4.0 as ::core::ffi::c_int; let mut a: AnonEnumInUnion = AnonEnumInUnion { a: 0 }; let mut b: AnonStructInUnion = AnonStructInUnion { c2rust_unnamed: C2Rust_Unnamed_2 { some_number: 0 }, diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2024.clang15.snap index 4ee9a80c6a..46cf4abce5 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.2024.clang15.snap @@ -15,9 +15,11 @@ expression: cat tests/snapshots/records.2024.clang15.rs #[derive(Copy, Clone)] #[repr(C)] pub struct AnonEnumInStruct {} -pub type C2Rust_Unnamed = ::core::ffi::c_uint; -pub const VALUE2: C2Rust_Unnamed = 1; -pub const VALUE1: C2Rust_Unnamed = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint); +pub const VALUE2: C2Rust_Unnamed = C2Rust_Unnamed(1); +pub const VALUE1: C2Rust_Unnamed = C2Rust_Unnamed(0); #[derive(Copy, Clone)] #[repr(C)] pub struct AnonStructInStruct { @@ -41,9 +43,11 @@ pub struct InsideStruct { pub union AnonEnumInUnion { pub a: ::core::ffi::c_int, } -pub type C2Rust_Unnamed_1 = ::core::ffi::c_uint; -pub const VALUE4: C2Rust_Unnamed_1 = 1; -pub const VALUE3: C2Rust_Unnamed_1 = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_1(pub ::core::ffi::c_uint); +pub const VALUE4: C2Rust_Unnamed_1 = C2Rust_Unnamed_1(1); +pub const VALUE3: C2Rust_Unnamed_1 = C2Rust_Unnamed_1(0); #[derive(Copy, Clone)] #[repr(C)] pub union AnonStructInUnion { @@ -67,7 +71,7 @@ pub struct InsideUnion { } #[unsafe(no_mangle)] pub unsafe extern "C" fn struct_declaration() { - let mut value: ::core::ffi::c_int = VALUE2 as ::core::ffi::c_int; + let mut value: ::core::ffi::c_int = VALUE2.0 as ::core::ffi::c_int; let mut a: AnonEnumInStruct = AnonEnumInStruct {}; let mut b: AnonStructInStruct = AnonStructInStruct { c2rust_unnamed: C2Rust_Unnamed_0 { some_number: 0 }, @@ -78,7 +82,7 @@ pub unsafe extern "C" fn struct_declaration() { } #[unsafe(no_mangle)] pub unsafe extern "C" fn union_declaration() { - let mut value: ::core::ffi::c_int = VALUE4 as ::core::ffi::c_int; + let mut value: ::core::ffi::c_int = VALUE4.0 as ::core::ffi::c_int; let mut a: AnonEnumInUnion = AnonEnumInUnion { a: 0 }; let mut b: AnonStructInUnion = AnonStructInUnion { c2rust_unnamed: C2Rust_Unnamed_2 { some_number: 0 }, diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2021.clang15.snap index 983068e241..912ed190b6 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2021.clang15.snap @@ -12,8 +12,10 @@ expression: cat tests/snapshots/scalar_init.2021.clang15.rs unused_mut )] #![feature(raw_ref_op)] -pub type E = ::core::ffi::c_uint; -pub const A: E = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct E(pub ::core::ffi::c_uint); +pub const A: E = E(0); pub const true_0: ::core::ffi::c_int = 1 as ::core::ffi::c_int; #[no_mangle] pub unsafe extern "C" fn scalar_init() { diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2024.clang15.snap index be7f3a5d96..d6a4077d3a 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@scalar_init.c.2024.clang15.snap @@ -12,8 +12,10 @@ expression: cat tests/snapshots/scalar_init.2024.clang15.rs unused_assignments, unused_mut )] -pub type E = ::core::ffi::c_uint; -pub const A: E = 0; +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct E(pub ::core::ffi::c_uint); +pub const A: E = E(0); pub const true_0: ::core::ffi::c_int = 1 as ::core::ffi::c_int; #[unsafe(no_mangle)] pub unsafe extern "C" fn scalar_init() { diff --git a/c2rust/src/bin/c2rust-transpile.rs b/c2rust/src/bin/c2rust-transpile.rs index f4a74bc84c..34a272ac67 100644 --- a/c2rust/src/bin/c2rust-transpile.rs +++ b/c2rust/src/bin/c2rust-transpile.rs @@ -326,7 +326,7 @@ fn main() { log_level: args.log_level, edition: args.edition, deny_unsafe_op_in_unsafe_fn: args.deny_unsafe_op_in_unsafe_fn, - enum_mode: EnumMode::Consts, + enum_mode: EnumMode::NewType, }; // binaries imply emit-build-files if !tcfg.binaries.is_empty() { diff --git a/tests/unit/enums/src/test_enums.rs b/tests/unit/enums/src/test_enums.rs index 1e373fd354..c100fd91cc 100644 --- a/tests/unit/enums/src/test_enums.rs +++ b/tests/unit/enums/src/test_enums.rs @@ -35,8 +35,8 @@ const BUFFER_SIZE6: usize = 1; #[test] pub fn test_variants() { - assert_eq!(A as u32, 0); - assert_eq!(B as u32, 1); + assert_eq!(A.0 as u32, 0); + assert_eq!(B.0 as u32, 1); } #[test] diff --git a/tests/unit/misc/src/test_uninitialized.rs b/tests/unit/misc/src/test_uninitialized.rs index 87425c5b32..505aee8937 100644 --- a/tests/unit/misc/src/test_uninitialized.rs +++ b/tests/unit/misc/src/test_uninitialized.rs @@ -26,9 +26,9 @@ pub fn test_buffer() { #[test] pub fn test_types() { - assert_eq!(foo as u32, 1); - assert_eq!(bar as u32, 2); - assert_eq!(baz as u32, 3); + assert_eq!(foo.0 as u32, 1); + assert_eq!(bar.0 as u32, 2); + assert_eq!(baz.0 as u32, 3); // FIXME: union fields are private // let my_union = u { x: 32 }; From 385304dcbc11b9ced13d5e7a6ce4991eceda2844 Mon Sep 17 00:00:00 2001 From: Rua Date: Sun, 3 May 2026 10:37:57 +0200 Subject: [PATCH 09/22] transpile: Factor out `can_propagate_cast` --- c2rust-transpile/src/translator/mod.rs | 53 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index ed49f6163f..5ce340145f 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3097,28 +3097,15 @@ impl<'c> Translation<'c> { _ => {} } - let expr_kind = &self.ast_context[expr].kind; let target_ty = override_ty.unwrap_or(ty); // In general, if we are casting the result of an expression, then the inner // expression should be translated to whatever type it normally would. - // But for literals, if we don't absolutely have to cast, we would rather the - // literal is translated according to the type we're expecting, and then we can - // skip the cast entirely. - if !is_explicit { - let mut literal_expr_kind = expr_kind; - let mut is_negated = false; - - if let &CExprKind::Unary(_, CUnOp::Negate, subexpr_id, _) = literal_expr_kind { - literal_expr_kind = &self.ast_context[subexpr_id].kind; - is_negated = true; - } - - if let CExprKind::Literal(_, lit) = literal_expr_kind { - if self.literal_matches_ty(lit, target_ty, is_negated) { - return self.convert_expr(ctx, expr, Some(target_ty)); - } - } + // But for some expression types, if we don't absolutely have to cast, + // we would rather the expression is translated according to the type we're + // expecting, and then we can skip the cast entirely. + if self.can_propagate_cast(expr, target_ty, is_explicit) { + return self.convert_expr(ctx, expr, Some(target_ty)); } let mut val = self.convert_expr(ctx, expr, None)?; @@ -3588,6 +3575,36 @@ impl<'c> Translation<'c> { } } + fn can_propagate_cast( + &self, + expr_id: CExprId, + target_type_id: CQualTypeId, + is_explicit: bool, + ) -> bool { + // Always preserve explicit casts. + if is_explicit { + return false; + } + + let expr_kind = &self.ast_context[expr_id].kind; + let mut literal_expr_kind = expr_kind; + let mut is_negated = false; + + if let &CExprKind::Unary(_, CUnOp::Negate, subexpr_id, _) = literal_expr_kind { + literal_expr_kind = &self.ast_context[subexpr_id].kind; + is_negated = true; + } + + if let CExprKind::Literal(_, lit) = literal_expr_kind { + // Does the inner literal fit in the type we're casting to? + if self.literal_matches_ty(lit, target_type_id, is_negated) { + return true; + } + } + + false + } + pub fn convert_cast( &self, ctx: ExprContext, From d52348c72c24c78ca3a1cc72b3e36353800b369c Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 4 May 2026 13:27:12 +0200 Subject: [PATCH 10/22] transpile: Move ctx modifications down in cast handling code --- c2rust-transpile/src/translator/mod.rs | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 5ce340145f..53b576aaeb 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3078,14 +3078,26 @@ impl<'c> Translation<'c> { ImplicitCast(ty, expr, kind, opt_field_id, _) | ExplicitCast(ty, expr, kind, opt_field_id, _) => { let is_explicit = matches!(expr_kind, CExprKind::ExplicitCast(..)); - // A reference must be decayed if a bitcast is required. Const casts in - // LLVM 8 are now NoOp casts, so we need to include it as well. + let target_ty = override_ty.unwrap_or(ty); + + // In general, if we are casting the result of an expression, then the inner + // expression should be translated to whatever type it normally would. + // But for some expression types, if we don't absolutely have to cast, + // we would rather the expression is translated according to the type we're + // expecting, and then we can skip the cast entirely. + if self.can_propagate_cast(expr, target_ty, is_explicit) { + return self.convert_expr(ctx, expr, Some(target_ty)); + } + match kind { CastKind::IntegralToBoolean | CastKind::FloatingToBoolean | CastKind::PointerToBoolean => { return self.convert_condition(ctx, true, expr); } + + // A reference must be decayed if a bitcast is required. Const casts in + // LLVM 8 are now NoOp casts, so we need to include it as well. CastKind::BitCast | CastKind::PointerToIntegral | CastKind::NoOp => { ctx.decay_ref = DecayRef::Yes } @@ -3097,17 +3109,6 @@ impl<'c> Translation<'c> { _ => {} } - let target_ty = override_ty.unwrap_or(ty); - - // In general, if we are casting the result of an expression, then the inner - // expression should be translated to whatever type it normally would. - // But for some expression types, if we don't absolutely have to cast, - // we would rather the expression is translated according to the type we're - // expecting, and then we can skip the cast entirely. - if self.can_propagate_cast(expr, target_ty, is_explicit) { - return self.convert_expr(ctx, expr, Some(target_ty)); - } - let mut val = self.convert_expr(ctx, expr, None)?; if is_explicit { From 799cced6b557169ab5c231a9aebe5f31c50e308e Mon Sep 17 00:00:00 2001 From: Rua Date: Sun, 3 May 2026 12:41:52 +0200 Subject: [PATCH 11/22] transpile: Add `enums.c` snapshot test --- c2rust-transpile/tests/snapshots.rs | 5 ++ c2rust-transpile/tests/snapshots/enums.c | 90 +++++++++++++++++++ ...shots__transpile@enums.c.2021.clang15.snap | 59 ++++++++++++ ...shots__transpile@enums.c.2024.clang15.snap | 60 +++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 c2rust-transpile/tests/snapshots/enums.c create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 3f1e38d7d6..dd06ae4908 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -359,6 +359,11 @@ fn test_empty_init() { transpile("empty_init.c").run(); } +#[test] +fn test_enums() { + transpile("enums.c").run(); +} + #[test] fn test_exprs() { transpile("exprs.c").run(); diff --git a/c2rust-transpile/tests/snapshots/enums.c b/c2rust-transpile/tests/snapshots/enums.c new file mode 100644 index 0000000000..ba83188062 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/enums.c @@ -0,0 +1,90 @@ +typedef enum { Foo0, Foo1, Foo2, Foo3 } Foo; +enum Bar { BarN1 = -1, Bar0, Bar1, Bar2, Bar3 }; + +#define FOO1_MACRO Foo1 +#define BAR1_MACRO Bar1 + +void test_enums(void) { + Foo foo = Foo0; + enum Bar bar = Bar0; + foo = Foo1; + bar = BarN1; + + // Assign enum constant of wrong type + foo = Bar0; + bar = Foo0; + + // Assign integer that matches a constant + foo = 1; + bar = 1; + + // Assign integer that doesn't match any constant + foo = 3; + bar = 3; + + // Arithmetic + foo -= 2; + bar -= 2; + + Foo e = Foo1; + + // Compare to same type + int enum_enum = e == foo; + int enum_constant = e == Foo0; + + // Compare to wrong type + int wrong_enum_enum = e == bar; + int wrong_enum_constant = e == Bar0; + + switch (foo) { + case Foo0: + break; + + case FOO1_MACRO: + break; + + // Integer with matching variant + case 2: + break; + + // Wrong type + case Bar3: + break; + + // No matching variant + case 42: + break; + + // No matching variant, negative on unsigned enum + case -42: + break; + } + + switch (bar) { + case Bar0: + break; + + case BAR1_MACRO: + break; + + // Integer with matching variant + case 2: + break; + + // Wrong type + case Foo3: + break; + + // Negative variant + case BarN1: + break; + + // No matching variant + case 42: + break; + + // No matching variant, negative on signed enum + case -42: + break; + } +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap new file mode 100644 index 0000000000..d84b236069 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap @@ -0,0 +1,59 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/enums.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Foo(pub ::core::ffi::c_uint); +pub const Foo3: Foo = Foo(3); +pub const Foo2: Foo = Foo(2); +pub const Foo1: Foo = Foo(1); +pub const Foo0: Foo = Foo(0); +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Bar(pub ::core::ffi::c_int); +pub const Bar3: Bar = Bar(3); +pub const Bar2: Bar = Bar(2); +pub const Bar1: Bar = Bar(1); +pub const Bar0: Bar = Bar(0); +pub const BarN1: Bar = Bar(-1); +pub const FOO1_MACRO: ::core::ffi::c_uint = 1 as ::core::ffi::c_uint; +pub const BAR1_MACRO: ::core::ffi::c_int = 1; +#[no_mangle] +pub unsafe extern "C" fn test_enums() { + let mut foo: Foo = Foo0; + let mut bar: Bar = Bar0; + foo = Foo1; + bar = BarN1; + foo = Foo(Bar0.0 as ::core::ffi::c_uint); + bar = Bar(Foo0.0 as ::core::ffi::c_int); + foo = Foo1; + bar = Bar1; + foo = Foo3; + bar = Bar3; + foo = Foo(foo.0.wrapping_sub(2 as ::core::ffi::c_uint)); + bar = Bar(bar.0 - 2 as ::core::ffi::c_int); + let mut e: Foo = Foo1; + let mut enum_enum: ::core::ffi::c_int = (e.0 == foo.0) as ::core::ffi::c_int; + let mut enum_constant: ::core::ffi::c_int = + (e.0 == Foo0.0 as ::core::ffi::c_int as ::core::ffi::c_uint) as ::core::ffi::c_int; + let mut wrong_enum_enum: ::core::ffi::c_int = + (e.0 == bar.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; + let mut wrong_enum_constant: ::core::ffi::c_int = + (e.0 == Bar0.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; + match foo.0 { + 0 | 1 | 2 | 3 | 42 | 4294967254 | _ => {} + } + match bar.0 { + 0 | BAR1_MACRO | 2 | 3 | -1 | 42 | -42 | _ => {} + }; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap new file mode 100644 index 0000000000..2aaf45c807 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap @@ -0,0 +1,60 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/enums.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Foo(pub ::core::ffi::c_uint); +pub const Foo3: Foo = Foo(3); +pub const Foo2: Foo = Foo(2); +pub const Foo1: Foo = Foo(1); +pub const Foo0: Foo = Foo(0); +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Bar(pub ::core::ffi::c_int); +pub const Bar3: Bar = Bar(3); +pub const Bar2: Bar = Bar(2); +pub const Bar1: Bar = Bar(1); +pub const Bar0: Bar = Bar(0); +pub const BarN1: Bar = Bar(-1); +pub const FOO1_MACRO: ::core::ffi::c_uint = 1 as ::core::ffi::c_uint; +pub const BAR1_MACRO: ::core::ffi::c_int = 1; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn test_enums() { + let mut foo: Foo = Foo0; + let mut bar: Bar = Bar0; + foo = Foo1; + bar = BarN1; + foo = Foo(Bar0.0 as ::core::ffi::c_uint); + bar = Bar(Foo0.0 as ::core::ffi::c_int); + foo = Foo1; + bar = Bar1; + foo = Foo3; + bar = Bar3; + foo = Foo(foo.0.wrapping_sub(2 as ::core::ffi::c_uint)); + bar = Bar(bar.0 - 2 as ::core::ffi::c_int); + let mut e: Foo = Foo1; + let mut enum_enum: ::core::ffi::c_int = (e.0 == foo.0) as ::core::ffi::c_int; + let mut enum_constant: ::core::ffi::c_int = + (e.0 == Foo0.0 as ::core::ffi::c_int as ::core::ffi::c_uint) as ::core::ffi::c_int; + let mut wrong_enum_enum: ::core::ffi::c_int = + (e.0 == bar.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; + let mut wrong_enum_constant: ::core::ffi::c_int = + (e.0 == Bar0.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; + match foo.0 { + 0 | 1 | 2 | 3 | 42 | 4294967254 | _ => {} + } + match bar.0 { + 0 | BAR1_MACRO | 2 | 3 | -1 | 42 | -42 | _ => {} + }; +} From ec32c820eb423e93734632cd4df395215e72721c Mon Sep 17 00:00:00 2001 From: Rua Date: Sun, 3 May 2026 12:11:40 +0200 Subject: [PATCH 12/22] transpile: let `can_propagate_cast` handle casting `EnumConstant` to its own enum --- c2rust-transpile/src/c_ast/mod.rs | 8 ++++ c2rust-transpile/src/translator/enums.rs | 51 ++++++++++-------------- c2rust-transpile/src/translator/mod.rs | 18 ++++++++- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 3011f16632..273ab0e6d2 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -780,6 +780,14 @@ impl TypedAstContext { TypeOf(ty) => ty, Paren(ty) => ty, Auto(ty) => ty, + + // As an exception, resolve typedefs in `prenamed_decls`, because they do not + // get translated as type aliases in the final code. + Typedef(decl) if self.prenamed_decls.contains_key(&decl) => match self[decl].kind { + CDeclKind::Typedef { typ: ty, .. } => ty.ctype, + _ => panic!("Typedef decl did not point to a typedef"), + }, + _ => return typ, }; self.resolve_type_id_no_typedef(ty) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index 1458090934..8a8bfe6d88 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -7,8 +7,8 @@ use crate::{ diagnostics::TranslationResult, translator::{signed_int_expr, ConvertedDecl, EnumMode, ExprContext, Translation}, with_stmts::WithStmts, - CDeclKind, CEnumConstantId, CEnumId, CExprId, CExprKind, CLiteral, CQualTypeId, CTypeKind, - ConstIntExpr, + CDeclKind, CEnumConstantId, CEnumId, CExprId, CExprKind, CLiteral, CQualTypeId, CTypeId, + CTypeKind, ConstIntExpr, }; impl<'c> Translation<'c> { @@ -84,11 +84,17 @@ impl<'c> Translation<'c> { enum_constant_id: CEnumConstantId, target_type_id: CQualTypeId, ) -> TranslationResult>> { - let enum_id = self.ast_context.parents[&enum_constant_id]; - let val = self.enum_constant_expr(enum_constant_id); + let mut val = WithStmts::new_val(self.enum_constant_expr(enum_constant_id)); + + if !self.enum_constant_matches_type(target_type_id.ctype, enum_constant_id) { + // Add a cast to the expected integral type. + let enum_id = self.ast_context.parents[&enum_constant_id]; + val = val.and_then_try(|val| { + self.convert_cast_from_enum(ctx, enum_id, target_type_id, val) + })?; + } - // Add a cast to the expected integral type. - self.convert_cast_from_enum(ctx, enum_id, target_type_id, val) + Ok(val) } /// Translate a cast where the source type, but not the target type, is an `enum` type. @@ -137,23 +143,6 @@ impl<'c> Translation<'c> { ) -> TranslationResult>> { if let Some(expr) = expr { match self.ast_context[expr].kind { - // This is the case of finding a variable which is an `EnumConstant` of the same - // enum we are casting to. Here, we can just remove the extraneous cast instead of - // generating a new one. - CExprKind::DeclRef(_, enum_constant_id, _) - if self.is_variant_of_enum(enum_id, enum_constant_id) => - { - // `enum`s shouldn't need portable `override_ty`s. - let expr_is_macro = self.expr_is_expanded_macro(ctx, expr, None); - - // If this DeclRef expanded to a const macro, we actually need to insert a cast, - // because the translation of a const macro skips implicit casts in its context. - if !expr_is_macro { - val = self.enum_constant_expr(enum_constant_id); - return Ok(WithStmts::new_val(val)); - } - } - CExprKind::Literal(_, CLiteral::Integer(i, _)) => { val = self.enum_for_i64(enum_id, i as i64); return Ok(WithStmts::new_val(val)); @@ -269,13 +258,17 @@ impl<'c> Translation<'c> { } } - fn is_variant_of_enum(&self, enum_id: CEnumId, enum_constant_id: CEnumConstantId) -> bool { - let variants = match self.ast_context[enum_id].kind { - CDeclKind::Enum { ref variants, .. } => variants, - _ => panic!("{:?} does not point to an `enum` declaration", enum_id), + pub fn enum_constant_matches_type( + &self, + type_id: CTypeId, + enum_constant_id: CEnumConstantId, + ) -> bool { + let type_enum_id = match self.ast_context.resolve_type_no_typedef(type_id).kind { + CTypeKind::Enum(enum_id) => enum_id, + _ => return false, }; - - variants.contains(&enum_constant_id) + let constant_enum_id = self.ast_context.parents[&enum_constant_id]; + type_enum_id == constant_enum_id } pub fn enum_integral_type(&self, enum_id: CEnumId) -> CQualTypeId { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 53b576aaeb..5fbd5afbbf 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3351,7 +3351,11 @@ impl<'c> Translation<'c> { } if let CDeclKind::EnumConstant { .. } = decl { - return self.convert_enum_constant_decl_ref(ctx, decl_id, qual_ty); + return self.convert_enum_constant_decl_ref( + ctx, + decl_id, + override_ty.unwrap_or(qual_ty), + ); } let varname = decl.get_name().expect("expected variable name").to_owned(); @@ -3588,6 +3592,18 @@ impl<'c> Translation<'c> { } let expr_kind = &self.ast_context[expr_id].kind; + + if let &CExprKind::DeclRef(_, decl_id, _) = expr_kind { + if let CDeclKind::EnumConstant { .. } = self.ast_context[decl_id].kind { + // In C, `EnumConstant`s have some integral type, _not_ the enum type. + // However, if we then immediately have a cast to convert this variable back into + // the enum type, we would like to produce Rust with _no_ casts. + if self.enum_constant_matches_type(target_type_id.ctype, decl_id) { + return true; + } + } + } + let mut literal_expr_kind = expr_kind; let mut is_negated = false; From b564188748d29444e605c574c2647aaeba62bd82 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 4 May 2026 20:09:09 +0200 Subject: [PATCH 13/22] transpile: let `convert_literal` handle casting literal to enum --- c2rust-transpile/src/translator/enums.rs | 27 ++------------------- c2rust-transpile/src/translator/literals.rs | 13 ++++++++++ c2rust-transpile/src/translator/mod.rs | 6 ++--- c2rust-transpile/src/translator/pointers.rs | 3 +-- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index 8a8bfe6d88..d12065fb4f 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -2,13 +2,11 @@ use c2rust_ast_builder::mk; use proc_macro2::Span; use syn::Expr; -use crate::c_ast::CUnOp; use crate::{ diagnostics::TranslationResult, translator::{signed_int_expr, ConvertedDecl, EnumMode, ExprContext, Translation}, with_stmts::WithStmts, - CDeclKind, CEnumConstantId, CEnumId, CExprId, CExprKind, CLiteral, CQualTypeId, CTypeId, - CTypeKind, ConstIntExpr, + CDeclKind, CEnumConstantId, CEnumId, CQualTypeId, CTypeId, CTypeKind, ConstIntExpr, }; impl<'c> Translation<'c> { @@ -138,29 +136,8 @@ impl<'c> Translation<'c> { ctx: ExprContext, mut source_cty: CQualTypeId, enum_id: CEnumId, - expr: Option, mut val: Box, ) -> TranslationResult>> { - if let Some(expr) = expr { - match self.ast_context[expr].kind { - CExprKind::Literal(_, CLiteral::Integer(i, _)) => { - val = self.enum_for_i64(enum_id, i as i64); - return Ok(WithStmts::new_val(val)); - } - - CExprKind::Unary(_, CUnOp::Negate, subexpr_id, _) => { - if let &CExprKind::Literal(_, CLiteral::Integer(i, _)) = - &self.ast_context[subexpr_id].kind - { - val = self.enum_for_i64(enum_id, -(i as i64)); - return Ok(WithStmts::new_val(val)); - } - } - - _ => {} - } - } - // We could be casting from enum to enum... if let CTypeKind::Enum(source_enum_id) = self.ast_context.resolve_type(source_cty.ctype).kind @@ -205,7 +182,7 @@ impl<'c> Translation<'c> { /// Given an integer value this attempts to either generate the corresponding enum /// variant directly, otherwise it converts a number to the enum type. - fn enum_for_i64(&self, enum_id: CEnumId, value: i64) -> Box { + pub fn enum_for_i64(&self, enum_id: CEnumId, value: i64) -> Box { if let Some(enum_constant_id) = self.enum_variant_for_i64(enum_id, value) { return self.enum_constant_expr(enum_constant_id); } diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index e1bfeca9f3..f142caa545 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -16,6 +16,18 @@ impl<'c> Translation<'c> { base: IntBase, negative: bool, ) -> TranslationResult> { + let type_resolved_id = self.ast_context.resolve_type_id(ty.ctype); + + if let CTypeKind::Enum(enum_id) = self.ast_context[type_resolved_id].kind { + let mut val = val as i64; + + if negative { + val = -val; + } + + return Ok(self.enum_for_i64(enum_id, val)); + } + let lit = match base { IntBase::Dec => mk().int_unsuffixed_lit(val), IntBase::Hex => mk().float_unsuffixed_lit(&format!("0x{:x}", val)), @@ -35,6 +47,7 @@ impl<'c> Translation<'c> { pub fn literal_matches_ty(&self, lit: &CLiteral, ty: CQualTypeId, is_negated: bool) -> bool { let ty_kind = &self.ast_context.resolve_type(ty.ctype).kind; match *lit { + CLiteral::Integer(_, _) if ty_kind.is_enum() => true, CLiteral::Integer(value, _) | CLiteral::Character(value) if ty_kind.is_integral_type() && !ty_kind.is_bool() => { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 5fbd5afbbf..a8cf910018 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3660,7 +3660,7 @@ impl<'c> Translation<'c> { } CastKind::PointerToIntegral => { - self.convert_pointer_to_integral_cast(ctx, source_cty, target_cty, val, expr) + self.convert_pointer_to_integral_cast(ctx, source_cty, target_cty, val) } CastKind::IntegralCast @@ -3696,9 +3696,7 @@ impl<'c> Translation<'c> { { self.f128_cast_to(val, target_ty_kind) } else if let &CTypeKind::Enum(enum_id) = target_ty_kind { - val.and_then_try(|val| { - self.convert_cast_to_enum(ctx, source_cty, enum_id, expr, val) - }) + val.and_then_try(|val| self.convert_cast_to_enum(ctx, source_cty, enum_id, val)) } else if target_ty_kind.is_floating_type() && source_ty_kind.is_bool() { Ok(val.map(|val| { mk().cast_expr(mk().cast_expr(val, mk().path_ty(vec!["u8"])), target_ty) diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index bd63c4fbc7..bffb30efac 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -514,7 +514,6 @@ impl<'c> Translation<'c> { source_cty: CQualTypeId, target_cty: CQualTypeId, val: WithStmts>, - expr: Option, ) -> TranslationResult>> { if ctx.is_const { return Err(format_translation_err!( @@ -532,7 +531,7 @@ impl<'c> Translation<'c> { WithStmts::new_val(transmute_expr(source_ty, target_ty, val)).set_unsafe() })) } else if let &CTypeKind::Enum(enum_id) = target_ty_kind { - val.and_then_try(|val| self.convert_cast_to_enum(ctx, source_cty, enum_id, expr, val)) + val.and_then_try(|val| self.convert_cast_to_enum(ctx, source_cty, enum_id, val)) } else { Ok(val.map(|val| mk().cast_expr(val, target_ty))) } From 7e3c4e30641c725fa9c8a8d42649fd8f9b356009 Mon Sep 17 00:00:00 2001 From: Rua Date: Sun, 3 May 2026 12:43:47 +0200 Subject: [PATCH 14/22] transpile: Propagate cast of `EnumConstant` to its own integral type --- c2rust-transpile/src/translator/mod.rs | 12 ++++++++++++ .../snapshots__transpile@enums.c.2021.clang15.snap | 3 +-- .../snapshots__transpile@enums.c.2024.clang15.snap | 3 +-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index a8cf910018..213edeaf49 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3601,6 +3601,18 @@ impl<'c> Translation<'c> { if self.enum_constant_matches_type(target_type_id.ctype, decl_id) { return true; } + + let source_enum_id = self.ast_context.parents[&decl_id]; + let source_integral_type_id = self.enum_integral_type(source_enum_id); + let target_type_resolved_id = self + .ast_context + .resolve_type_id_no_typedef(target_type_id.ctype); + + // Likewise, if we are casting to the inner integral type of the enum, then + // translate the enum constant directly as that. + if target_type_resolved_id == source_integral_type_id.ctype { + return true; + } } } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap index d84b236069..0573afdf75 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap @@ -44,8 +44,7 @@ pub unsafe extern "C" fn test_enums() { bar = Bar(bar.0 - 2 as ::core::ffi::c_int); let mut e: Foo = Foo1; let mut enum_enum: ::core::ffi::c_int = (e.0 == foo.0) as ::core::ffi::c_int; - let mut enum_constant: ::core::ffi::c_int = - (e.0 == Foo0.0 as ::core::ffi::c_int as ::core::ffi::c_uint) as ::core::ffi::c_int; + let mut enum_constant: ::core::ffi::c_int = (e.0 == Foo0.0) as ::core::ffi::c_int; let mut wrong_enum_enum: ::core::ffi::c_int = (e.0 == bar.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; let mut wrong_enum_constant: ::core::ffi::c_int = diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap index 2aaf45c807..99b3a58b8f 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap @@ -45,8 +45,7 @@ pub unsafe extern "C" fn test_enums() { bar = Bar(bar.0 - 2 as ::core::ffi::c_int); let mut e: Foo = Foo1; let mut enum_enum: ::core::ffi::c_int = (e.0 == foo.0) as ::core::ffi::c_int; - let mut enum_constant: ::core::ffi::c_int = - (e.0 == Foo0.0 as ::core::ffi::c_int as ::core::ffi::c_uint) as ::core::ffi::c_int; + let mut enum_constant: ::core::ffi::c_int = (e.0 == Foo0.0) as ::core::ffi::c_int; let mut wrong_enum_enum: ::core::ffi::c_int = (e.0 == bar.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; let mut wrong_enum_constant: ::core::ffi::c_int = From a53243baebffc7ab3c4b7359c293f2f25d540c1d Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 12:49:02 +0200 Subject: [PATCH 15/22] transpile: In `Case` translation, do not propagate translation errors --- c2rust-transpile/src/cfg/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index 7d6a413a43..f534316d3a 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -1861,8 +1861,9 @@ impl CfgBuilder { let branch = match translator.ast_context.index(resolved).kind { CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => { match translator - .convert_expr(ctx.used(), resolved, None)? - .to_pure_expr() + .convert_expr(ctx.used(), resolved, None) + .ok() + .and_then(WithStmts::to_pure_expr) { Some(expr) => match *expr { Expr::Lit(lit) => Some(mk().lit_pat(lit.lit)), From a7811e50714bde377995561cbfdc974f6db550a9 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:26:59 +0200 Subject: [PATCH 16/22] transpile: Add `ExprContext::is_pattern`, error on unsupported exprs --- c2rust-transpile/src/cfg/mod.rs | 2 +- c2rust-transpile/src/translator/literals.rs | 6 +++++ c2rust-transpile/src/translator/macros.rs | 2 +- c2rust-transpile/src/translator/mod.rs | 30 ++++++++++++++++++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index f534316d3a..ab565feb14 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -1861,7 +1861,7 @@ impl CfgBuilder { let branch = match translator.ast_context.index(resolved).kind { CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => { match translator - .convert_expr(ctx.used(), resolved, None) + .convert_expr(ctx.const_().pattern().used(), resolved, None) .ok() .and_then(WithStmts::to_pure_expr) { diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index f142caa545..4bce41d1c4 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -124,6 +124,12 @@ impl<'c> Translation<'c> { } CLiteral::String(ref bytes, element_size) => { + if ctx.is_pattern { + return Err(TranslationError::generic( + "CLiteral::String is not supported in patterns", + )); + } + let bytes_padded = self.string_literal_bytes(ty.ctype, bytes, element_size); let len = bytes_padded.len(); let val = mk().lit_expr(bytes_padded); diff --git a/c2rust-transpile/src/translator/macros.rs b/c2rust-transpile/src/translator/macros.rs index 148e60a199..1c048baa2f 100644 --- a/c2rust-transpile/src/translator/macros.rs +++ b/c2rust-transpile/src/translator/macros.rs @@ -178,7 +178,7 @@ impl<'c> Translation<'c> { // We haven't tried to expand it yet. None => { - self.convert_decl(ctx, *macro_id)?; + self.convert_decl(ctx.not_pattern(), *macro_id)?; if let Some(Some(expansion)) = self.macro_expansions.borrow().get(macro_id) { expansion.ty } else { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 213edeaf49..5e7ee2ab84 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -127,6 +127,10 @@ pub struct ExprContext { /// translation. is_const: bool, + /// In a context where a pattern is expected, such as for `match` arms. + /// This restricts what kinds of expressions can be emitted. + is_pattern: bool, + /// Evaluating a C global/static variable. /// This is usually in a const context, but doesn't have to be, for example with initializers /// that are executed by the `c2rust_run_static_initializers` function. @@ -180,6 +184,18 @@ impl ExprContext { ..self } } + pub fn pattern(self) -> Self { + ExprContext { + is_pattern: true, + ..self + } + } + pub fn not_pattern(self) -> Self { + ExprContext { + is_pattern: false, + ..self + } + } pub fn not_static(self) -> Self { ExprContext { is_static: false, @@ -688,8 +704,9 @@ pub fn translate( let mut t = Translation::new(ast_context, tcfg, main_file); let ctx = ExprContext { used: true, - is_static: false, is_const: false, + is_pattern: false, + is_static: false, decay_ref: DecayRef::Default, is_bitfield_write: false, needs_address: false, @@ -2947,6 +2964,17 @@ impl<'c> Translation<'c> { } } + if ctx.is_pattern + && !matches!( + expr_kind, + CExprKind::Paren(..) | CExprKind::ConstantExpr(..) | CExprKind::Literal(..) + ) + { + return Err(TranslationError::generic( + "expr kind is not supported in patterns", + )); + } + use CExprKind::*; match *expr_kind { DesignatedInitExpr(..) => { From 5869679b907b265703676df350cbca0a0521845e Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:42:56 +0200 Subject: [PATCH 17/22] transpile: Do not emit casts for literals in patterns --- c2rust-transpile/src/translator/literals.rs | 30 ++++++++++++++------ c2rust-transpile/src/translator/mod.rs | 1 + c2rust-transpile/src/translator/operators.rs | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index 4bce41d1c4..ac934ede41 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -11,6 +11,7 @@ impl<'c> Translation<'c> { /// Generate an integer literal corresponding to the given type, value, and base. pub fn mk_int_lit( &self, + ctx: ExprContext, ty: CQualTypeId, val: u64, base: IntBase, @@ -39,8 +40,12 @@ impl<'c> Translation<'c> { expr = neg_expr(expr); } - let target_ty = self.convert_type(ty.ctype)?; - Ok(mk().cast_expr(expr, target_ty)) + if !ctx.is_pattern { + let target_ty = self.convert_type(ty.ctype)?; + expr = mk().cast_expr(expr, target_ty); + } + + Ok(expr) } /// Return whether the literal can be directly translated as this type. @@ -69,13 +74,18 @@ impl<'c> Translation<'c> { lit: &CLiteral, ) -> TranslationResult>> { match *lit { - CLiteral::Integer(val, base) => { - Ok(WithStmts::new_val(self.mk_int_lit(ty, val, base, false)?)) - } + CLiteral::Integer(val, base) => Ok(WithStmts::new_val( + self.mk_int_lit(ctx, ty, val, base, false)?, + )), CLiteral::Character(val) => { let val = val as u32; - let expr = match char::from_u32(val) { + let mut expr = match char::from_u32(val).filter(|_| { + // Always convert character literals as integers in patterns. + // Character literals have problems with typing that need to be resolved. See + // https://github.com/immunant/c2rust/issues/648 + !ctx.is_pattern + }) { Some(c) => mk().lit_expr(c), None => { // Fallback for characters outside of the valid Unicode range @@ -89,8 +99,12 @@ impl<'c> Translation<'c> { } }; - let type_rs = self.convert_type(ty.ctype)?; - Ok(WithStmts::new_val(mk().cast_expr(expr, type_rs))) + if !ctx.is_pattern { + let type_rs = self.convert_type(ty.ctype)?; + expr = mk().cast_expr(expr, type_rs); + } + + Ok(WithStmts::new_val(expr)) } CLiteral::Floating(val, ref c_str) => { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 5e7ee2ab84..77f34adcae 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3036,6 +3036,7 @@ impl<'c> Translation<'c> { OffsetOf(ty, ref kind) => match kind { OffsetOfKind::Constant(val) => Ok(WithStmts::new_val(self.mk_int_lit( + ctx, override_ty.unwrap_or(ty), *val, IntBase::Dec, diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 7b9618bf2f..30123ea63c 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -852,7 +852,7 @@ impl<'c> Translation<'c> { // If we are negating a literal, generate a negated literal directly. // This will create an expression like `-1 as ty` without parentheses, // rather than `-(1 as ty)`. - let val = self.mk_int_lit(expr_type_id, val, base, true)?; + let val = self.mk_int_lit(ctx, expr_type_id, val, base, true)?; Ok(WithStmts::new_val(val)) } else { let val = self.convert_expr(ctx.used(), arg_id, Some(expr_type_id))?; From fc99b68d7dd27ac09c6dfd4a20a89e653833b983 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:48:06 +0200 Subject: [PATCH 18/22] transpile: Use `convert_expr` for patterns in `ConstantExpr` --- c2rust-transpile/src/translator/mod.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 77f34adcae..1b8d87b549 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3024,7 +3024,11 @@ impl<'c> Translation<'c> { ConstantExpr(ty, child, value) => { if let Some(constant) = value { - self.convert_constant(constant).map(WithStmts::new_val) + if ctx.is_pattern { + self.convert_expr(ctx, child, override_ty) + } else { + self.convert_constant(constant).map(WithStmts::new_val) + } } else { self.convert_expr(ctx, child, Some(ty)) } @@ -3114,7 +3118,7 @@ impl<'c> Translation<'c> { // But for some expression types, if we don't absolutely have to cast, // we would rather the expression is translated according to the type we're // expecting, and then we can skip the cast entirely. - if self.can_propagate_cast(expr, target_ty, is_explicit) { + if self.can_propagate_cast(ctx, expr, target_ty, is_explicit) { return self.convert_expr(ctx, expr, Some(target_ty)); } @@ -3611,6 +3615,7 @@ impl<'c> Translation<'c> { fn can_propagate_cast( &self, + ctx: ExprContext, expr_id: CExprId, target_type_id: CQualTypeId, is_explicit: bool, @@ -3620,7 +3625,14 @@ impl<'c> Translation<'c> { return false; } - let expr_kind = &self.ast_context[expr_id].kind; + let mut expr_kind = &self.ast_context[expr_id].kind; + + // In patterns, skip over `ConstantExpr`s. + if ctx.is_pattern { + if let &CExprKind::ConstantExpr(_, expr_id, _) = expr_kind { + expr_kind = &self.ast_context[expr_id].kind; + } + } if let &CExprKind::DeclRef(_, decl_id, _) = expr_kind { if let CDeclKind::EnumConstant { .. } = self.ast_context[decl_id].kind { From 8ab3dc1938119b13cc05daeda91559f41e5f3252 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:49:44 +0200 Subject: [PATCH 19/22] transpile: Add `expr_to_pat` function for more thorough conversion --- c2rust-transpile/src/cfg/mod.rs | 191 +++++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 15 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index ab565feb14..c96200a138 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -34,7 +34,7 @@ use std::ops::Index; use std::rc::Rc; use std::{fmt, io}; use syn::Lit; -use syn::{spanned::Spanned, Arm, Expr, Pat, Stmt}; +use syn::{punctuated::Punctuated, spanned::Spanned, Arm, Expr, Pat, Stmt}; use failure::format_err; use indexmap::indexset; @@ -1859,20 +1859,11 @@ impl CfgBuilder { // Case let resolved = translator.ast_context.unwrap_cast_expr(case_expr); let branch = match translator.ast_context.index(resolved).kind { - CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => { - match translator - .convert_expr(ctx.const_().pattern().used(), resolved, None) - .ok() - .and_then(WithStmts::to_pure_expr) - { - Some(expr) => match *expr { - Expr::Lit(lit) => Some(mk().lit_pat(lit.lit)), - Expr::Path(path) => Some(mk().path_pat(path.path, path.qself)), - _ => None, - }, - _ => None, - } - } + CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => translator + .convert_expr(ctx.const_().pattern().used(), resolved, None) + .ok() + .and_then(WithStmts::to_pure_expr) + .and_then(|expr| expr_to_pat(*expr)), _ => None, }; @@ -2318,3 +2309,173 @@ impl Cfg { Ok(()) } } + +fn expr_to_pat(expr: Expr) -> Option { + use syn::{ + ExprArray, ExprCall, ExprLit, ExprParen, ExprPath, ExprReference, ExprStruct, ExprTuple, + ExprUnary, FieldPat, FieldValue, LitInt, PatLit, PatParen, PatPath, PatReference, PatSlice, + PatStruct, PatTuple, PatTupleStruct, UnOp, + }; + + match expr { + Expr::Array(ExprArray { + attrs, + bracket_token, + elems, + }) => { + let elems = punctuated_expr_to_pat(elems)?; + + Some(Pat::Slice(PatSlice { + attrs, + bracket_token, + elems, + })) + } + + Expr::Call(ExprCall { + attrs, + func, + paren_token, + args, + }) => { + let (qself, path) = match *func { + Expr::Path(ExprPath { qself, path, .. }) => (qself, path), + _ => return None, + }; + let elems = punctuated_expr_to_pat(args)?; + + Some(Pat::TupleStruct(PatTupleStruct { + attrs, + qself, + path, + paren_token, + elems, + })) + } + + Expr::Lit(ExprLit { attrs, lit }) => Some(Pat::Lit(PatLit { attrs, lit })), + + Expr::Paren(ExprParen { + attrs, + paren_token, + expr, + }) => { + let pat = Box::new(expr_to_pat(*expr)?); + Some(Pat::Paren(PatParen { + attrs, + paren_token, + pat, + })) + } + + Expr::Path(ExprPath { attrs, qself, path }) => { + Some(Pat::Path(PatPath { attrs, qself, path })) + } + + Expr::Range(range) => Some(Pat::Range(range)), + + Expr::Reference(ExprReference { + attrs, + and_token, + mutability, + expr, + }) => { + let pat = Box::new(expr_to_pat(*expr)?); + + Some(Pat::Reference(PatReference { + attrs, + and_token, + mutability, + pat, + })) + } + + Expr::Struct(ExprStruct { + attrs, + qself, + path, + brace_token, + fields, + dot2_token: None, + rest: None, + }) => { + let fields = fields + .into_iter() + .map(|field| { + let FieldValue { + attrs, + member, + colon_token, + expr, + } = field; + let pat = Box::new(expr_to_pat(expr)?); + + Some(FieldPat { + attrs, + member, + colon_token, + pat, + }) + }) + .collect::>()?; + + Some(Pat::Struct(PatStruct { + attrs, + qself, + path, + brace_token, + fields, + rest: None, + })) + } + + Expr::Tuple(ExprTuple { + attrs, + paren_token, + elems, + }) => { + let elems = punctuated_expr_to_pat(elems)?; + + Some(Pat::Tuple(PatTuple { + attrs, + paren_token, + elems, + })) + } + + // There is no equivalent `PatUnary`, but the negative sign can be folded into the literal. + Expr::Unary(ExprUnary { + attrs, + op: UnOp::Neg(_), + expr, + }) => { + let Expr::Lit(ExprLit { + attrs: _, + lit: Lit::Int(lit_int), + }) = *expr else { + return None + }; + + let repr = format!("-{}{}", lit_int.base10_digits(), lit_int.suffix()); + let lit = Lit::Int(LitInt::new(&repr, lit_int.span())); + Some(Pat::Lit(PatLit { attrs, lit })) + } + + _ => None, + } +} + +fn punctuated_expr_to_pat( + elems: Punctuated, +) -> Option> { + use syn::punctuated::Pair; + + elems + .into_pairs() + .map(|pair| { + let (expr, token) = pair.into_tuple(); + let pat = expr_to_pat(expr)?; + Some(Pair::new(pat, token)) + }) + .collect() +} From e8321788424c72fe6ca9dc23ccaf026ed79108b4 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 14:04:02 +0200 Subject: [PATCH 20/22] transpile: Treat cast failure in macro expansion as expansion failure --- c2rust-transpile/src/translator/macros.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/c2rust-transpile/src/translator/macros.rs b/c2rust-transpile/src/translator/macros.rs index 1c048baa2f..b8134d0fc9 100644 --- a/c2rust-transpile/src/translator/macros.rs +++ b/c2rust-transpile/src/translator/macros.rs @@ -194,7 +194,7 @@ impl<'c> Translation<'c> { self.add_import(*macro_id, &rust_name); - let val = WithStmts::new_val(mk().path_expr(vec![rust_name])); + let mut val = WithStmts::new_val(mk().path_expr(vec![rust_name])); let expr_kind = &self.ast_context[expr_id].kind; // TODO We'd like to get rid of this cast eventually (see #1321). @@ -204,7 +204,7 @@ impl<'c> Translation<'c> { // so we need to cast it to the `override_ty` here. let expr_ty = override_ty.or_else(|| expr_kind.get_qual_type()); if let Some(expr_ty) = expr_ty { - self.convert_cast( + let result = self.convert_cast( ctx, CQualTypeId::new(macro_ty), expr_ty, @@ -212,14 +212,18 @@ impl<'c> Translation<'c> { None, None, None, - ) - .map(Some) - } else { - Ok(Some(val)) + ); + + match result { + Ok(new_val) => val = new_val, + Err(_) => return Ok(None), + } } // TODO: May need to handle volatile reads here. // See `DeclRef` below. + + Ok(Some(val)) } /// Convert the expansion of a function-like macro. From 9beca5d0152b9ab7a97be66ca67fcec7731fd72a Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 14:21:06 +0200 Subject: [PATCH 21/22] transpile: Allow limited cast translation in patterns --- c2rust-transpile/src/translator/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 1b8d87b549..e836d25f2e 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -2967,7 +2967,11 @@ impl<'c> Translation<'c> { if ctx.is_pattern && !matches!( expr_kind, - CExprKind::Paren(..) | CExprKind::ConstantExpr(..) | CExprKind::Literal(..) + CExprKind::Paren(..) + | CExprKind::ConstantExpr(..) + | CExprKind::Literal(..) + | CExprKind::ImplicitCast(..) + | CExprKind::ExplicitCast(..) ) { return Err(TranslationError::generic( @@ -3703,6 +3707,12 @@ impl<'c> Translation<'c> { return Ok(val); } + if ctx.is_pattern && !matches!(kind, CastKind::ToVoid | CastKind::ConstCast) { + return Err(TranslationError::generic( + "cast kind is not supported in patterns", + )); + } + match kind { CastKind::BitCast | CastKind::NoOp => { self.convert_pointer_to_pointer_cast(source_cty, target_cty, val) From 698955d71a257a0f438b1893a22e8bd301537298 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 15:27:12 +0200 Subject: [PATCH 22/22] transpile: In `Case` translation, do not skip over initial cast --- c2rust-transpile/src/cfg/mod.rs | 23 +++++++------------ ...shots__transpile@enums.c.2021.clang15.snap | 2 +- ...shots__transpile@enums.c.2024.clang15.snap | 2 +- ...s__transpile@macrocase.c.2021.clang15.snap | 2 +- ...s__transpile@macrocase.c.2024.clang15.snap | 2 +- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index c96200a138..6c54cbaf61 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -1857,23 +1857,16 @@ impl CfgBuilder { self.add_wip_block(wip, Jump(this_label.clone())); // Case - let resolved = translator.ast_context.unwrap_cast_expr(case_expr); - let branch = match translator.ast_context.index(resolved).kind { - CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => translator - .convert_expr(ctx.const_().pattern().used(), resolved, None) - .ok() - .and_then(WithStmts::to_pure_expr) - .and_then(|expr| expr_to_pat(*expr)), - _ => None, - }; - - let pat = match branch { - Some(pat) => pat, - None => match cie { + let expr = translator + .convert_expr(ctx.const_().pattern().used(), case_expr, None) + .ok() + .and_then(WithStmts::to_pure_expr); + let pat = expr + .and_then(|expr| expr_to_pat(*expr)) + .unwrap_or_else(|| match cie { ConstIntExpr::U(n) => mk().lit_pat(mk().int_unsuffixed_lit(n)), ConstIntExpr::I(n) => mk().lit_pat(mk().int_unsuffixed_lit(n)), - }, - }; + }); self.switch_expr_cases .last_mut() diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap index 0573afdf75..5b44d45557 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2021.clang15.snap @@ -50,7 +50,7 @@ pub unsafe extern "C" fn test_enums() { let mut wrong_enum_constant: ::core::ffi::c_int = (e.0 == Bar0.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; match foo.0 { - 0 | 1 | 2 | 3 | 42 | 4294967254 | _ => {} + 0 | FOO1_MACRO | 2 | 3 | 42 | 4294967254 | _ => {} } match bar.0 { 0 | BAR1_MACRO | 2 | 3 | -1 | 42 | -42 | _ => {} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap index 99b3a58b8f..61f88c5576 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@enums.c.2024.clang15.snap @@ -51,7 +51,7 @@ pub unsafe extern "C" fn test_enums() { let mut wrong_enum_constant: ::core::ffi::c_int = (e.0 == Bar0.0 as ::core::ffi::c_uint) as ::core::ffi::c_int; match foo.0 { - 0 | 1 | 2 | 3 | 42 | 4294967254 | _ => {} + 0 | FOO1_MACRO | 2 | 3 | 42 | 4294967254 | _ => {} } match bar.0 { 0 | BAR1_MACRO | 2 | 3 | -1 | 42 | -42 | _ => {} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap index c41d837292..ae78e7e15c 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2021.clang15.snap @@ -25,7 +25,7 @@ pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> : bounds = 1 as ::core::ffi::c_int; return bounds; } - 1000 => { + ZSTD_d_format => { bounds = 5 as ::core::ffi::c_int; return bounds; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap index e4cc4f1de0..acda8e29ec 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.2024.clang15.snap @@ -26,7 +26,7 @@ pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> : bounds = 1 as ::core::ffi::c_int; return bounds; } - 1000 => { + ZSTD_d_format => { bounds = 5 as ::core::ffi::c_int; return bounds; }