From 0c7fffd2c32c483955bb8322de07c96fb7909ac9 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 12:49:02 +0200 Subject: [PATCH 01/14] transpile: In `Case` translation, do not propagate translation errors --- c2rust-transpile/src/cfg/mod.rs | 52 +++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index 9ba204b198..283b97e0ae 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -1935,30 +1935,38 @@ impl CfgBuilder { // Case let resolved = translator.ast_context.unwrap_cast_expr(case_expr); - let branch = match translator.ast_context.index_unwrap_parens(resolved).kind { - CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => { - match translator - .convert_expr(ctx.used(), resolved, None)? - .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, - } - } - _ => None, + let result = match translator.ast_context.index_unwrap_parens(resolved).kind { + CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => Ok(()), + _ => Err(("match", "wrong CExprKind".to_string())), }; + let pat = result + .and_then(|_| { + translator + .convert_expr(ctx.used(), resolved, None) + .map_err(|err| ("convert_expr", err.to_string())) + }) + .and_then(|val| { + val.to_pure_expr() + .ok_or_else(|| ("to_pure_expr", "".to_string())) + }) + .and_then(|expr| match *expr { + Expr::Lit(lit) => Ok(mk().lit_pat(lit.lit)), + Expr::Path(path) => Ok(mk().path_pat(path.path, path.qself)), + _ => Err(("match", "wrong Expr".to_string())), + }) + .unwrap_or_else(|(src, err)| { + log::trace!( + "Converting `case` {:?} failed in {}: {}", + case_expr, + src, + err + ); - let pat = match branch { - Some(pat) => pat, - None => match cie { - ConstIntExpr::U(n) => mk().lit_pat(mk().int_unsuffixed_lit(n)), - ConstIntExpr::I(n) => mk().lit_pat(mk().int_unsuffixed_lit(n)), - }, - }; + 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() From f2f136766e52ab8ae63f0db1927ea5fde0f7af19 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:26:59 +0200 Subject: [PATCH 02/14] 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 283b97e0ae..75f4f3aef5 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -1942,7 +1942,7 @@ impl CfgBuilder { let pat = result .and_then(|_| { translator - .convert_expr(ctx.used(), resolved, None) + .convert_expr(ctx.const_().pattern().used(), resolved, None) .map_err(|err| ("convert_expr", err.to_string())) }) .and_then(|val| { diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index b20ed49c2c..c244476a3d 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -123,6 +123,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 99645b2fb7..2bef317eab 100644 --- a/c2rust-transpile/src/translator/macros.rs +++ b/c2rust-transpile/src/translator/macros.rs @@ -188,7 +188,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 680d0685e5..bdfe698949 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -128,6 +128,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. @@ -181,6 +185,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, @@ -856,8 +872,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, @@ -3207,6 +3224,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 69679bac98d729e6da6faf3ffc0165b160075623 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:42:56 +0200 Subject: [PATCH 03/14] transpile: Do not emit casts for literals in patterns --- c2rust-transpile/src/translator/literals.rs | 24 ++++++++++++++------ c2rust-transpile/src/translator/mod.rs | 1 + c2rust-transpile/src/translator/operators.rs | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index c244476a3d..9f3f984b94 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, @@ -36,7 +37,7 @@ impl<'c> Translation<'c> { expr = neg_expr(expr); } - Ok(if is_suffix { + Ok(if is_suffix || ctx.is_pattern { expr } else { mk().cast_expr(expr, target_ty) @@ -68,13 +69,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 @@ -88,8 +94,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 bdfe698949..41a43cdd05 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3312,6 +3312,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 98c4c0fc45..1e8189a6f9 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -748,7 +748,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 d4b7f53c87c571fd126f5e0c52d096814319bccc Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:48:06 +0200 Subject: [PATCH 04/14] 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 41a43cdd05..56c3c9ba64 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3300,7 +3300,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(override_ty.unwrap_or(ty))) } @@ -3884,7 +3888,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)); } @@ -3951,6 +3955,7 @@ impl<'c> Translation<'c> { fn can_propagate_cast( &self, + ctx: ExprContext, expr_id: CExprId, target_type_id: CQualTypeId, is_explicit: bool, @@ -3960,7 +3965,14 @@ impl<'c> Translation<'c> { return false; } - let expr_kind = &self.ast_context.index_unwrap_parens(expr_id).kind; + let mut expr_kind = &self.ast_context.index_unwrap_parens(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.index_unwrap_parens(expr_id).kind; + } + } if let &CExprKind::DeclRef(_, decl_id, _) = expr_kind { if let CDeclKind::EnumConstant { .. } = self.ast_context[decl_id].kind { From 42ae027b41cd5676ed9493dd4c7b64d5f6f00720 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 13:49:44 +0200 Subject: [PATCH 05/14] transpile: Add `expr_to_pat` function for more thorough conversion --- c2rust-transpile/src/cfg/mod.rs | 178 ++++++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 6 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index 75f4f3aef5..1c3afdc73a 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; @@ -1949,11 +1949,7 @@ impl CfgBuilder { val.to_pure_expr() .ok_or_else(|| ("to_pure_expr", "".to_string())) }) - .and_then(|expr| match *expr { - Expr::Lit(lit) => Ok(mk().lit_pat(lit.lit)), - Expr::Path(path) => Ok(mk().path_pat(path.path, path.qself)), - _ => Err(("match", "wrong Expr".to_string())), - }) + .and_then(|expr| expr_to_pat(*expr).map_err(|err| ("expr_to_pat", err))) .unwrap_or_else(|(src, err)| { log::trace!( "Converting `case` {:?} failed in {}: {}", @@ -2423,3 +2419,173 @@ impl Cfg { Ok(()) } } + +fn expr_to_pat(expr: Expr) -> Result { + 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)?; + + Ok(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 Err("`ExprCall::func` is not an `ExprPath`".into()), + }; + let elems = punctuated_expr_to_pat(args)?; + + Ok(Pat::TupleStruct(PatTupleStruct { + attrs, + qself, + path, + paren_token, + elems, + })) + } + + Expr::Lit(ExprLit { attrs, lit }) => Ok(Pat::Lit(PatLit { attrs, lit })), + + Expr::Paren(ExprParen { + attrs, + paren_token, + expr, + }) => { + let pat = Box::new(expr_to_pat(*expr)?); + Ok(Pat::Paren(PatParen { + attrs, + paren_token, + pat, + })) + } + + Expr::Path(ExprPath { attrs, qself, path }) => { + Ok(Pat::Path(PatPath { attrs, qself, path })) + } + + Expr::Range(range) => Ok(Pat::Range(range)), + + Expr::Reference(ExprReference { + attrs, + and_token, + mutability, + expr, + }) => { + let pat = Box::new(expr_to_pat(*expr)?); + + Ok(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)?); + + Ok(FieldPat { + attrs, + member, + colon_token, + pat, + }) + }) + .collect::>()?; + + Ok(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)?; + + Ok(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 Err("`ExprUnary::expr` is not an `ExprLit` with `lit: Lit::Int`".into()); + }; + + let repr = format!("-{}{}", lit_int.base10_digits(), lit_int.suffix()); + let lit = Lit::Int(LitInt::new(&repr, lit_int.span())); + Ok(Pat::Lit(PatLit { attrs, lit })) + } + + _ => Err("`Expr` with no equivalent `Pat`".into()), + } +} + +fn punctuated_expr_to_pat( + elems: Punctuated, +) -> Result, String> { + use syn::punctuated::Pair; + + elems + .into_pairs() + .map(|pair| { + let (expr, token) = pair.into_tuple(); + let pat = expr_to_pat(expr)?; + Ok(Pair::new(pat, token)) + }) + .collect() +} From 14a2bffc007402601f9c78b922fa6fb53bb92514 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 14:04:02 +0200 Subject: [PATCH 06/14] transpile: Treat cast failure in macro expansion as expansion failure --- c2rust-transpile/src/translator/macros.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/c2rust-transpile/src/translator/macros.rs b/c2rust-transpile/src/translator/macros.rs index 2bef317eab..1a66b0c0d9 100644 --- a/c2rust-transpile/src/translator/macros.rs +++ b/c2rust-transpile/src/translator/macros.rs @@ -204,7 +204,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.index_unwrap_parens(expr_id).kind; // TODO We'd like to get rid of this cast eventually (see #1321). @@ -214,14 +214,24 @@ 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.make_cast(ctx, CQualTypeId::new(macro_ty), expr_ty, val) - .map(Some) - } else { - Ok(Some(val)) + match self.make_cast(ctx, CQualTypeId::new(macro_ty), expr_ty, val) { + Ok(new_val) => val = new_val, + Err(err) => { + info!( + "Could not convert cast of macro {} for {:?}: {}", + self.renamer.borrow_mut().get(macro_id).unwrap(), + expr_id, + 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 92fbf0b83dec0e427aabe7b0017d5e69ed70698a Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 14:21:06 +0200 Subject: [PATCH 07/14] 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 56c3c9ba64..55997bfd7a 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3227,7 +3227,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( @@ -4053,6 +4057,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 627d579deca1f4b0b24156ffebf1b0261b8038fc Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 5 May 2026 15:27:12 +0200 Subject: [PATCH 08/14] transpile: In `Case` translation, do not skip over initial cast --- c2rust-transpile/src/cfg/mod.rs | 14 +++----------- .../snapshots__transpile@enums.c.2021.clang15.snap | 2 +- .../snapshots__transpile@enums.c.2024.clang15.snap | 2 +- ...pshots__transpile@macrocase.c.2021.clang15.snap | 2 +- ...pshots__transpile@macrocase.c.2024.clang15.snap | 2 +- 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index 1c3afdc73a..de162952e4 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -1934,17 +1934,9 @@ impl CfgBuilder { self.add_wip_block(wip, Jump(this_label.clone())); // Case - let resolved = translator.ast_context.unwrap_cast_expr(case_expr); - let result = match translator.ast_context.index_unwrap_parens(resolved).kind { - CExprKind::Literal(..) | CExprKind::ConstantExpr(_, _, Some(_)) => Ok(()), - _ => Err(("match", "wrong CExprKind".to_string())), - }; - let pat = result - .and_then(|_| { - translator - .convert_expr(ctx.const_().pattern().used(), resolved, None) - .map_err(|err| ("convert_expr", err.to_string())) - }) + let pat = translator + .convert_expr(ctx.const_().pattern().used(), case_expr, None) + .map_err(|err| ("convert_expr", err.to_string())) .and_then(|val| { val.to_pure_expr() .ok_or_else(|| ("to_pure_expr", "".to_string())) 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 ccd13b8fbe..2f5a5ba8d4 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 @@ -58,7 +58,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 5bd9839bc9..bc4485ac11 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 @@ -59,7 +59,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; } From 19594885845af045469353719293071bd6ccb19d Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 21 May 2026 19:54:57 +0200 Subject: [PATCH 09/14] transpile: Allow `EnumConstant` `DeclRef`s and casts to enums in patterns --- c2rust-transpile/src/translator/enums.rs | 14 +++++++++++++- c2rust-transpile/src/translator/mod.rs | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index f682242bb7..8732e78201 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -4,7 +4,7 @@ use syn::Expr; use crate::{ diagnostics::TranslationResult, - translator::{signed_int_expr, ConvertedDecl, ExprContext, Translation}, + translator::{signed_int_expr, ConvertedDecl, ExprContext, Translation, TranslationError}, with_stmts::WithStmts, CDeclKind, CEnumConstantId, CEnumId, CQualTypeId, CTypeId, CTypeKind, ConstIntExpr, }; @@ -93,6 +93,12 @@ impl<'c> Translation<'c> { target_cty: CQualTypeId, mut val: Box, ) -> TranslationResult>> { + if ctx.is_pattern { + return Err(TranslationError::generic( + "cast from enum is not supported in patterns", + )); + } + // First extract the enum's inner type... val = self.integer_from_enum(val); @@ -123,6 +129,12 @@ impl<'c> Translation<'c> { return Ok(WithStmts::new_val(val)); } + if ctx.is_pattern { + return Err(TranslationError::generic( + "cast from enum is not supported in patterns", + )); + } + // Enum-to-enum casts need to be translated via the inner value as an intermediate. val = self.integer_from_enum(val); source_cty = self.enum_integral_type(source_enum_id); diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 55997bfd7a..193ecf56b9 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3229,6 +3229,7 @@ impl<'c> Translation<'c> { expr_kind, CExprKind::Paren(..) | CExprKind::ConstantExpr(..) + | CExprKind::DeclRef(..) | CExprKind::Literal(..) | CExprKind::ImplicitCast(..) | CExprKind::ExplicitCast(..) @@ -3639,6 +3640,12 @@ impl<'c> Translation<'c> { ); } + if ctx.is_pattern { + return Err(TranslationError::generic( + "non-EnumConstant DeclRefs are not supported in patterns", + )); + } + let varname = decl.get_name().expect("expected variable name").to_owned(); let rustname = self .renamer @@ -4057,7 +4064,12 @@ impl<'c> Translation<'c> { return Ok(val); } - if ctx.is_pattern && !matches!(kind, CastKind::ToVoid | CastKind::ConstCast) { + if ctx.is_pattern + && !matches!( + kind, + CastKind::ToVoid | CastKind::ConstCast | CastKind::IntegralCast + ) + { return Err(TranslationError::generic( "cast kind is not supported in patterns", )); @@ -4083,6 +4095,12 @@ impl<'c> Translation<'c> { | CastKind::BooleanToSignedIntegral => { let target_ty = self.convert_type(target_cty.ctype)?; + if ctx.is_pattern && !target_ty_kind.is_enum() { + return Err(TranslationError::generic( + "integral casts to non-enums are not supported in patterns", + )); + } + if let CTypeKind::LongDouble | CTypeKind::Float128 = target_ty_kind { if let CTypeKind::LongDouble | CTypeKind::Float128 = self.ast_context[source_cty.ctype].kind From d88f0fd1e2eab74cc6bfc2b22689f8e4197bb737 Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 21 May 2026 19:58:55 +0200 Subject: [PATCH 10/14] transpile: Add expected type for enums in `SwitchCases` --- c2rust-transpile/src/cfg/mod.rs | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index de162952e4..66f6f9be04 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -448,6 +448,8 @@ impl GenTerminator> { pub struct SwitchCases { cases: Vec<(Pat, Label)>, default: Option