From 012253736a95e3f4c5ab2b7ef986a6cddc1ae07c Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 28 Sep 2026 20:52:16 +0200 Subject: [PATCH 01/13] transpile: Pass expected and literal types to `convert_literal` separately --- c2rust-transpile/src/translator/literals.rs | 5 ++++- c2rust-transpile/src/translator/mod.rs | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index 7483a053dd..7944397856 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -62,9 +62,12 @@ impl<'c> Translation<'c> { pub fn convert_literal( &self, ctx: ExprContext, - ty: CQualTypeId, + expected_type_id: Option, + literal_type_id: CQualTypeId, lit: &CLiteral, ) -> TranslationResult>> { + let ty = expected_type_id.unwrap_or(literal_type_id); + match *lit { CLiteral::Integer(val, base) => Ok(WithStmts::new_val( self.mk_int_lit(ctx, ty, val, base, false)?, diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 8fb6f5b95c..6c718a84e6 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3633,7 +3633,9 @@ impl<'c> Translation<'c> { } }, - Literal(ty, ref kind) => self.convert_literal(ctx, override_ty.unwrap_or(ty), kind), + Literal(result_type_id, ref kind) => { + self.convert_literal(ctx, override_ty, result_type_id, kind) + } ImplicitCast(ty, expr, kind, opt_field_id, _) | ExplicitCast(ty, expr, kind, opt_field_id, _) => self.convert_cast( From b349d43e54ad44fc16802182290221e709144ea1 Mon Sep 17 00:00:00 2001 From: Rua Date: Sat, 26 Sep 2026 12:17:27 +0200 Subject: [PATCH 02/13] transpile: Split off `convert_floating_literal` --- c2rust-transpile/src/translator/literals.rs | 76 +++++++++++++-------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index 7944397856..eb1cb60fa2 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -4,7 +4,7 @@ use super::*; use failure::format_err; -use std::iter; +use std::{borrow::Cow, iter}; use syn::{Path, TypePath}; impl<'c> Translation<'c> { @@ -102,34 +102,8 @@ impl<'c> Translation<'c> { Ok(WithStmts::new_val(expr)) } - CLiteral::Floating(val, ref c_str) => { - let str = if c_str.is_empty() { - let mut buffer = dtoa::Buffer::new(); - buffer.format(val).to_string() - } else { - c_str.to_owned() - }; - let val = match self.ast_context.resolve_type(ty.ctype).kind { - CTypeKind::LongDouble | CTypeKind::Float128 => { - if ctx.is_const { - return Err(format_translation_err!( - None, - "f128 cannot be used in constants because `f128::f128::new` is not `const`", - )); - } - - self.use_crate(ExternCrate::F128); - - let fn_path = mk().abs_path_expr(vec!["f128", "f128", "new"]); - let args = vec![mk().lit_expr(mk().float_unsuffixed_lit(&str))]; - - mk().call_expr(fn_path, args) - } - CTypeKind::Double => mk().lit_expr(mk().float_lit(&str, "f64")), - CTypeKind::Float => mk().lit_expr(mk().float_lit(&str, "f32")), - ref k => panic!("Unsupported floating point literal type {:?}", k), - }; - Ok(WithStmts::new_val(val)) + CLiteral::Floating(value, ref string) => { + self.convert_floating_literal(ctx, expected_type_id, literal_type_id, value, string) } CLiteral::String(ref bytes, element_size) => { @@ -179,6 +153,50 @@ impl<'c> Translation<'c> { } } + fn convert_floating_literal( + &self, + ctx: ExprContext, + expected_type_id: Option, + mut literal_type_id: CQualTypeId, + value: f64, + string: &str, + ) -> TranslationResult>> { + let string: Cow<_> = if string.is_empty() { + let mut buffer = dtoa::Buffer::new(); + Cow::Owned(buffer.format(value).to_string()) + } else { + Cow::Borrowed(string) + }; + + literal_type_id = expected_type_id.unwrap_or(literal_type_id); + + let val = match self.ast_context.resolve_type(literal_type_id.ctype).kind { + CTypeKind::Float => mk().lit_expr(mk().float_lit(&string, "f32")), + + CTypeKind::Double => mk().lit_expr(mk().float_lit(&string, "f64")), + + CTypeKind::LongDouble | CTypeKind::Float128 => { + if ctx.is_const { + return Err(format_translation_err!( + None, + "f128 cannot be used in constants because `f128::f128::new` is not `const`", + )); + } + + self.use_crate(ExternCrate::F128); + + let fn_path = mk().abs_path_expr(vec!["f128", "f128", "new"]); + let args = vec![mk().lit_expr(mk().float_unsuffixed_lit(&string))]; + + mk().call_expr(fn_path, args) + } + + ref k => panic!("Unsupported floating point literal type {:?}", k), + }; + + Ok(WithStmts::new_val(val)) + } + /// Returns the bytes of a string literal, including any additional zero bytes to pad the /// literal to the expected size. pub fn string_literal_bytes(&self, ctype: CTypeId, bytes: &[u8], element_size: u8) -> Vec { From cfb9827f564eda2d5e5f4d52cf7b70ff5cc95875 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 28 Sep 2026 21:37:05 +0200 Subject: [PATCH 03/13] transpile: Use `convert_floating_literal` in `convert_indecrement_operator` --- c2rust-transpile/src/translator/literals.rs | 2 +- c2rust-transpile/src/translator/operators.rs | 52 +++++++++----------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index eb1cb60fa2..98768d0dd9 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -153,7 +153,7 @@ impl<'c> Translation<'c> { } } - fn convert_floating_literal( + pub(crate) fn convert_floating_literal( &self, ctx: ExprContext, expected_type_id: Option, diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 3c9dfccbf7..b0777ad702 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -603,35 +603,25 @@ impl<'c> Translation<'c> { .get_qual_type() .ok_or_else(|| format_err!("bad arg type"))?; - let one = match self.ast_context.resolve_type(arg_type.ctype).kind { - // TODO: If rust gets f16 support: - // CTypeKind::Half | - CTypeKind::Float | CTypeKind::Double => mk().lit_expr(mk().float_unsuffixed_lit("1.")), - CTypeKind::LongDouble | CTypeKind::Float128 => { - self.use_crate(ExternCrate::F128); - - let fn_path = mk().abs_path_expr(vec!["f128", "f128", "new"]); - let args = vec![mk().lit_expr(mk().float_unsuffixed_lit("1."))]; - - mk().call_expr(fn_path, args) - } - _ => mk().lit_expr(mk().int_unsuffixed_lit(1)), - }; - let mut one_type_id = arg_type; let mut compute_lhs_type_id = arg_type; let mut compute_res_type_id = result_type_id; - match self.ast_context.resolve_type(arg_type.ctype).kind { + let one = match self.ast_context.resolve_type(arg_type.ctype).kind { CTypeKind::Pointer(..) => { one_type_id = CQualTypeId::new(self.ast_context.type_for_kind(&CTypeKind::Int)); + mk().lit_expr(mk().int_unsuffixed_lit(1)).into() } CTypeKind::Enum(enum_id) => { one_type_id = self.enum_underlying_type(enum_id); compute_lhs_type_id = one_type_id; compute_res_type_id = one_type_id; + mk().lit_expr(mk().int_unsuffixed_lit(1)).into() } - _ => {} + ref type_kind if type_kind.is_floating_type() => { + self.convert_floating_literal(ctx, None, one_type_id, 1.0, "1.0")? + } + _ => mk().lit_expr(mk().int_unsuffixed_lit(1)).into(), }; // If we aren't going to be using the result, may as well do a simple pre-increment @@ -646,7 +636,7 @@ impl<'c> Translation<'c> { op, arg, one_type_id, - WithStmts::new_val(one), + one, Some(compute_lhs_type_id), Some(compute_res_type_id), ) @@ -663,18 +653,20 @@ impl<'c> Translation<'c> { WithStmts::new(vec![save_old_val], (lhs, old_val_expr)) }) .and_then_try(|(lhs, old_val_expr)| { - let val = self.make_assignment_operator( - ctx.unused(), - expected_type_id, - result_type_id, - op, - lhs, - arg_type, - one, - one_type_id, - compute_lhs_type_id, - compute_res_type_id, - )?; + let val = one.and_then_try(|one| { + self.make_assignment_operator( + ctx.unused(), + expected_type_id, + result_type_id, + op, + lhs, + arg_type, + one, + one_type_id, + compute_lhs_type_id, + compute_res_type_id, + ) + })?; // Replace the assignment result with the old value Ok(val.map(|_| old_val_expr)) From 39e55daea5b6be712ecfda85b166257a32350ff9 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 28 Sep 2026 22:20:00 +0200 Subject: [PATCH 04/13] transpile: Refactor `convert_floating_literal` to use `CFloatingKind` --- c2rust-transpile/src/translator/literals.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index 98768d0dd9..d3dc17d3b8 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -169,13 +169,17 @@ impl<'c> Translation<'c> { }; literal_type_id = expected_type_id.unwrap_or(literal_type_id); + let literal_type_kind = &self.ast_context.resolve_type(literal_type_id.ctype).kind; + let Some(literal_floating_kind) = literal_type_kind.floating_kind() else { + panic!("type of floating literal is not a floating type: {literal_type_kind:?}"); + }; - let val = match self.ast_context.resolve_type(literal_type_id.ctype).kind { - CTypeKind::Float => mk().lit_expr(mk().float_lit(&string, "f32")), + let val = match literal_floating_kind { + CFloatingKind::Float => mk().lit_expr(mk().float_lit(&string, "f32")), - CTypeKind::Double => mk().lit_expr(mk().float_lit(&string, "f64")), + CFloatingKind::Double => mk().lit_expr(mk().float_lit(&string, "f64")), - CTypeKind::LongDouble | CTypeKind::Float128 => { + CFloatingKind::LongDouble | CFloatingKind::Float128 => { if ctx.is_const { return Err(format_translation_err!( None, From bb0338af83a022c654f7ec3539650552758ea16e Mon Sep 17 00:00:00 2001 From: Rua Date: Sat, 26 Sep 2026 12:18:33 +0200 Subject: [PATCH 05/13] transpile: In `convert_floating_literal`, use `make_cast` to cast to `f128` --- c2rust-transpile/src/translator/literals.rs | 23 +++++++-------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index d3dc17d3b8..ff1c2f13dd 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -168,37 +168,30 @@ impl<'c> Translation<'c> { Cow::Borrowed(string) }; - literal_type_id = expected_type_id.unwrap_or(literal_type_id); + let target_type_id = expected_type_id.unwrap_or(literal_type_id); + literal_type_id = target_type_id; + let literal_type_kind = &self.ast_context.resolve_type(literal_type_id.ctype).kind; let Some(literal_floating_kind) = literal_type_kind.floating_kind() else { panic!("type of floating literal is not a floating type: {literal_type_kind:?}"); }; + // Rust only has `f32` and `f64` literals, so any other kinds must be translated as one of + // those two, and then cast. let val = match literal_floating_kind { CFloatingKind::Float => mk().lit_expr(mk().float_lit(&string, "f32")), CFloatingKind::Double => mk().lit_expr(mk().float_lit(&string, "f64")), CFloatingKind::LongDouble | CFloatingKind::Float128 => { - if ctx.is_const { - return Err(format_translation_err!( - None, - "f128 cannot be used in constants because `f128::f128::new` is not `const`", - )); - } - - self.use_crate(ExternCrate::F128); - - let fn_path = mk().abs_path_expr(vec!["f128", "f128", "new"]); - let args = vec![mk().lit_expr(mk().float_unsuffixed_lit(&string))]; - - mk().call_expr(fn_path, args) + literal_type_id.ctype = self.ast_context.type_for_kind(&CTypeKind::Double); + mk().lit_expr(mk().float_unsuffixed_lit(&string)) } ref k => panic!("Unsupported floating point literal type {:?}", k), }; - Ok(WithStmts::new_val(val)) + self.make_cast(ctx, literal_type_id, target_type_id, val.into()) } /// Returns the bytes of a string literal, including any additional zero bytes to pad the From 8b7fddab07f7312f54984b0e197895e22eb8f0b8 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 29 Sep 2026 18:03:02 +0200 Subject: [PATCH 06/13] transpile: In `convert_floating_literal`, accept any expected type --- c2rust-transpile/src/translator/literals.rs | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index ff1c2f13dd..ea59312648 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -51,9 +51,10 @@ impl<'c> Translation<'c> { CLiteral::Integer(value, _) | CLiteral::Character(value) => ty_kind .integer_kind() .is_some_and(|integer_kind| integer_kind.is_guaranteed_in_range(value, is_negated)), - CLiteral::Floating(value, _) => ty_kind - .floating_kind() - .is_some_and(|floating_kind| floating_kind.is_guaranteed_in_range(value)), + + // `convert_floating_literal` will handle incompatible types itself. + CLiteral::Floating(_, _) => true, + _ => false, } } @@ -169,13 +170,25 @@ impl<'c> Translation<'c> { }; let target_type_id = expected_type_id.unwrap_or(literal_type_id); - literal_type_id = target_type_id; let literal_type_kind = &self.ast_context.resolve_type(literal_type_id.ctype).kind; - let Some(literal_floating_kind) = literal_type_kind.floating_kind() else { + let Some(mut literal_floating_kind) = literal_type_kind.floating_kind() else { panic!("type of floating literal is not a floating type: {literal_type_kind:?}"); }; + if let Some(expected_type_id) = expected_type_id { + let expected_type_kind = &self.ast_context.resolve_type(expected_type_id.ctype).kind; + + if let Some(expected_floating_kind) = expected_type_kind.floating_kind() { + if literal_floating_kind == expected_floating_kind + || expected_floating_kind.is_guaranteed_in_range(value) + { + literal_type_id = expected_type_id; + literal_floating_kind = expected_floating_kind; + } + } + } + // Rust only has `f32` and `f64` literals, so any other kinds must be translated as one of // those two, and then cast. let val = match literal_floating_kind { From 55d78fb37f9cd492ccf66328d9d656fb01c547fe Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 16:37:41 +0100 Subject: [PATCH 07/13] transpile: Refactor pullback kind handling in `conversion.rs` --- c2rust-transpile/src/c_ast/conversion.rs | 123 +++++++++++++---------- 1 file changed, 68 insertions(+), 55 deletions(-) diff --git a/c2rust-transpile/src/c_ast/conversion.rs b/c2rust-transpile/src/c_ast/conversion.rs index de549e21d1..44e548a4a9 100644 --- a/c2rust-transpile/src/c_ast/conversion.rs +++ b/c2rust-transpile/src/c_ast/conversion.rs @@ -3,9 +3,9 @@ use crate::diagnostics::diag; use c2rust_ast_exporter::clang_ast::*; use failure::err_msg; use serde_bytes::ByteBuf; -use std::collections::HashMap; use std::rc::Rc; use std::vec::Vec; +use std::{collections::HashMap, ffi::OsStr}; use super::Located; use crate::diagnostics::{Diagnostic, TranslationError, TranslationErrorKind}; @@ -2224,35 +2224,64 @@ impl ConversionContext { let target_dependent_macro: Option = from_value(node.extras[2].clone()) .expect("Expected to find optional target-dependent macro name"); - typ = target_dependent_macro - .as_deref() - .and_then(|macro_name| { - let kind = match macro_name { - // Match names in the order Clang defines them. - "__INTMAX_TYPE__" => CTypeKind::IntMax, - "__UINTMAX_TYPE__" => CTypeKind::UIntMax, - "__PTRDIFF_TYPE__" => CTypeKind::PtrDiff, - "__INTPTR_TYPE__" => CTypeKind::IntPtr, - "__SIZE_TYPE__" => CTypeKind::Size, - "__WCHAR_TYPE__" => CTypeKind::WChar, - // __WINT_TYPE__ is defined by Clang but has no obvious translation - // __CHARn_TYPE__ for n ∈ {8, 16, 32} also lack obvious translation - "__UINTPTR_TYPE__" => CTypeKind::UIntPtr, - _ => { - log::debug!("Unknown target-dependent macro {macro_name}!"); - return None; - } - }; - log::trace!("Selected kind {kind} for typedef {name}"); - Some(CQualTypeId::new(self.typed_context.type_for_kind(&kind))) + let mut new_type_kind = None; + + fn type_kind_for_macro_name(macro_name: &str) -> Option { + Some(match macro_name { + // Match names in the order Clang defines them. + "__INTMAX_TYPE__" => CTypeKind::IntMax, + "__UINTMAX_TYPE__" => CTypeKind::UIntMax, + "__PTRDIFF_TYPE__" => CTypeKind::PtrDiff, + "__INTPTR_TYPE__" => CTypeKind::IntPtr, + "__SIZE_TYPE__" => CTypeKind::Size, + "__WCHAR_TYPE__" => CTypeKind::WChar, + // __WINT_TYPE__ is defined by Clang but has no obvious translation + // __CHARn_TYPE__ for n ∈ {8, 16, 32} also lack obvious translation + "__UINTPTR_TYPE__" => CTypeKind::UIntPtr, + _ => { + log::debug!("Unknown target-dependent macro {macro_name}!"); + return None; + } }) - .unwrap_or(typ); + } + + fn is_candidate_header(file_name: &OsStr, typedef_name: &str) -> bool { + file_name == "stdint.h" + || file_name == "types.h" + // glibc also declares `ssize_t` in these headers; whichever + // header a TU includes first provides the typedef. + || ((file_name == "stdio.h" || file_name == "unistd.h") + && typedef_name == "ssize_t") + || file_name + .to_str() + .map(|s| s.starts_with("__stddef_")) + .unwrap_or(false) + // for macos + || file_name == "_types.h" + || file_name + .to_str() + .map(|s| s.starts_with("_int") || s.starts_with("_uint")) + .unwrap_or(false) + } // Other fixed-size types are defined without special compiler involvement, by // standard headers and their transitive includes. In these contexts, we // recognize these typedefs by the name of the typedef type. - let id_for_name = |name| -> Option<_> { - let kind = match name { + let type_kind_for_typedef_name = |loc: &SrcSpan, + typedef_name: &str| + -> Option { + let file_name = self + .typed_context + .files + .get(self.typed_context.file_map[loc.fileid as usize]) + .and_then(|file| file.path.as_ref()) + .and_then(|path| path.file_name()); + + if file_name.map_or(true, |file| !is_candidate_header(file, typedef_name)) { + return None; + } + + Some(match typedef_name { "intmax_t" => CTypeKind::IntMax, "uintmax_t" => CTypeKind::UIntMax, "intptr_t" => CTypeKind::IntPtr, @@ -2285,37 +2314,21 @@ impl ConversionContext { log::debug!("Unknown fixed-size type typedef {name}!"); return None; } - }; - log::trace!("Selected kind {kind} for typedef {name}"); - Some(CQualTypeId::new(self.typed_context.type_for_kind(&kind))) + }) }; - let file = self - .typed_context - .files - .get(self.typed_context.file_map[node.loc.fileid as usize]); - let file = file.unwrap(); - if let Some(path) = file.path.as_ref() { - if let Some(filename) = path.file_name() { - if filename == "stdint.h" - || filename == "types.h" - // glibc also declares `ssize_t` in these headers; whichever - // header a TU includes first provides the typedef. - || ((filename == "stdio.h" || filename == "unistd.h") - && name == "ssize_t") - || filename - .to_str() - .map(|s| s.starts_with("__stddef_")) - .unwrap_or(false) - // for macos - || filename == "_types.h" - || filename - .to_str() - .map(|s| s.starts_with("_int") || s.starts_with("_uint")) - .unwrap_or(false) - { - typ = id_for_name(&*name).unwrap_or(typ); - } - } + + if let Some(kind) = target_dependent_macro + .as_deref() + .and_then(type_kind_for_macro_name) + { + new_type_kind = Some(kind); + } else if let Some(kind) = type_kind_for_typedef_name(&node.loc, &name) { + new_type_kind = Some(kind); + } + + if let Some(kind) = new_type_kind { + log::trace!("Selected kind {kind} for typedef {name}"); + typ = CQualTypeId::new(self.typed_context.type_for_kind(&kind)); } let typdef_decl = CDeclKind::Typedef { From 94ff740958275a9ed8120f7a7bfd3259fc3f3654 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 16:41:07 +0100 Subject: [PATCH 08/13] transpile: Move pullback kind replacement to a postprocessing step --- c2rust-transpile/src/c_ast/conversion.rs | 125 +-------------------- c2rust-transpile/src/c_ast/mod.rs | 137 +++++++++++++++++++++++ 2 files changed, 141 insertions(+), 121 deletions(-) diff --git a/c2rust-transpile/src/c_ast/conversion.rs b/c2rust-transpile/src/c_ast/conversion.rs index 44e548a4a9..6c411e7212 100644 --- a/c2rust-transpile/src/c_ast/conversion.rs +++ b/c2rust-transpile/src/c_ast/conversion.rs @@ -3,9 +3,9 @@ use crate::diagnostics::diag; use c2rust_ast_exporter::clang_ast::*; use failure::err_msg; use serde_bytes::ByteBuf; +use std::collections::HashMap; use std::rc::Rc; use std::vec::Vec; -use std::{collections::HashMap, ffi::OsStr}; use super::Located; use crate::diagnostics::{Diagnostic, TranslationError, TranslationErrorKind}; @@ -515,6 +515,8 @@ impl ConversionContext { } } + self.typed_context.replace_pullback_kinds(); + // Function declarations' types look through typedefs, but we want to use the types with // typedefs intact in some cases during translation. To ensure that these types exist in the // `TypedAstContext`, iterate over all function decls, compute their adjusted type using @@ -2208,129 +2210,10 @@ impl ConversionContext { let typ_old = node .type_id .expect("Expected to find type on typedef declaration"); - let mut typ = self.visit_qualified_type(typ_old); - - // Clang injects definitions of the form `#define __SIZE_TYPE__ unsigned int` into - // compilation units based on the target. (See lib/Frontend/InitPreprocessor.cpp - // in Clang). - // - // Later, headers contain defns like: `typedef __SIZE_TYPE__ size_t;` - // - // We detect these typedefs and alter them replacing the type to which the macro - // expands with a synthetic, portable `CTypeKind` chosen from the macro's name. - // - // This allows us to generate platform-independent Rust types like u64 or usize - // despite the C side internally working with a target-specific type. + let typ = self.visit_qualified_type(typ_old); let target_dependent_macro: Option = from_value(node.extras[2].clone()) .expect("Expected to find optional target-dependent macro name"); - let mut new_type_kind = None; - - fn type_kind_for_macro_name(macro_name: &str) -> Option { - Some(match macro_name { - // Match names in the order Clang defines them. - "__INTMAX_TYPE__" => CTypeKind::IntMax, - "__UINTMAX_TYPE__" => CTypeKind::UIntMax, - "__PTRDIFF_TYPE__" => CTypeKind::PtrDiff, - "__INTPTR_TYPE__" => CTypeKind::IntPtr, - "__SIZE_TYPE__" => CTypeKind::Size, - "__WCHAR_TYPE__" => CTypeKind::WChar, - // __WINT_TYPE__ is defined by Clang but has no obvious translation - // __CHARn_TYPE__ for n ∈ {8, 16, 32} also lack obvious translation - "__UINTPTR_TYPE__" => CTypeKind::UIntPtr, - _ => { - log::debug!("Unknown target-dependent macro {macro_name}!"); - return None; - } - }) - } - - fn is_candidate_header(file_name: &OsStr, typedef_name: &str) -> bool { - file_name == "stdint.h" - || file_name == "types.h" - // glibc also declares `ssize_t` in these headers; whichever - // header a TU includes first provides the typedef. - || ((file_name == "stdio.h" || file_name == "unistd.h") - && typedef_name == "ssize_t") - || file_name - .to_str() - .map(|s| s.starts_with("__stddef_")) - .unwrap_or(false) - // for macos - || file_name == "_types.h" - || file_name - .to_str() - .map(|s| s.starts_with("_int") || s.starts_with("_uint")) - .unwrap_or(false) - } - - // Other fixed-size types are defined without special compiler involvement, by - // standard headers and their transitive includes. In these contexts, we - // recognize these typedefs by the name of the typedef type. - let type_kind_for_typedef_name = |loc: &SrcSpan, - typedef_name: &str| - -> Option { - let file_name = self - .typed_context - .files - .get(self.typed_context.file_map[loc.fileid as usize]) - .and_then(|file| file.path.as_ref()) - .and_then(|path| path.file_name()); - - if file_name.map_or(true, |file| !is_candidate_header(file, typedef_name)) { - return None; - } - - Some(match typedef_name { - "intmax_t" => CTypeKind::IntMax, - "uintmax_t" => CTypeKind::UIntMax, - "intptr_t" => CTypeKind::IntPtr, - "uintptr_t" => CTypeKind::UIntPtr, - // unlike `size_t`, `ssize_t` does not have a clang-provided `#define`. - "ssize_t" => CTypeKind::SSize, - // macOS defines `ssize_t` from `__darwin_ssize_t` in many headers, - // so we should handle `__darwin_ssize_t` itself. - "__darwin_ssize_t" => CTypeKind::SSize, - "__uint8_t" => CTypeKind::UInt8, - "__uint16_t" => CTypeKind::UInt16, - "__uint32_t" => CTypeKind::UInt32, - "__uint64_t" => CTypeKind::UInt64, - "__uint128_t" => CTypeKind::UInt128, - "__int8_t" => CTypeKind::Int8, - "__int16_t" => CTypeKind::Int16, - "__int32_t" => CTypeKind::Int32, - "__int64_t" => CTypeKind::Int64, - "__int128_t" => CTypeKind::Int128, - // macOS stdint.h typedefs [u]intN_t directly: - "int8_t" => CTypeKind::Int8, - "int16_t" => CTypeKind::Int16, - "int32_t" => CTypeKind::Int32, - "int64_t" => CTypeKind::Int64, - "uint8_t" => CTypeKind::UInt8, - "uint16_t" => CTypeKind::UInt16, - "uint32_t" => CTypeKind::UInt32, - "uint64_t" => CTypeKind::UInt64, - _ => { - log::debug!("Unknown fixed-size type typedef {name}!"); - return None; - } - }) - }; - - if let Some(kind) = target_dependent_macro - .as_deref() - .and_then(type_kind_for_macro_name) - { - new_type_kind = Some(kind); - } else if let Some(kind) = type_kind_for_typedef_name(&node.loc, &name) { - new_type_kind = Some(kind); - } - - if let Some(kind) = new_type_kind { - log::trace!("Selected kind {kind} for typedef {name}"); - typ = CQualTypeId::new(self.typed_context.type_for_kind(&kind)); - } - let typdef_decl = CDeclKind::Typedef { name, typ, diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 0cbfdca7bd..0a2002d20d 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use std::cell::RefCell; use std::cmp::{Ordering, Reverse}; use std::collections::{HashMap, HashSet}; +use std::ffi::OsStr; use std::fmt::{self, Debug, Display}; use std::ops::Index; use std::path::{Path, PathBuf}; @@ -1307,6 +1308,142 @@ impl TypedAstContext { } } + fn replace_pullback_kinds(&mut self) { + let mut replacements: HashMap = HashMap::new(); + + for (&decl_id, decl) in self.iter_decls() { + let CDeclKind::Typedef { + ref name, + mut typ, + is_implicit, + ref target_dependent_macro, + } = decl.kind else { + continue + }; + + let mut new_type_kind = None; + + fn type_kind_for_macro_name(macro_name: &str) -> Option { + Some(match macro_name { + // Match names in the order Clang defines them. + "__INTMAX_TYPE__" => CTypeKind::IntMax, + "__UINTMAX_TYPE__" => CTypeKind::UIntMax, + "__PTRDIFF_TYPE__" => CTypeKind::PtrDiff, + "__INTPTR_TYPE__" => CTypeKind::IntPtr, + "__SIZE_TYPE__" => CTypeKind::Size, + "__WCHAR_TYPE__" => CTypeKind::WChar, + // __WINT_TYPE__ is defined by Clang but has no obvious translation + // __CHARn_TYPE__ for n ∈ {8, 16, 32} also lack obvious translation + "__UINTPTR_TYPE__" => CTypeKind::UIntPtr, + _ => { + log::debug!("Unknown target-dependent macro {macro_name}!"); + return None; + } + }) + } + + fn is_candidate_header(file_name: &OsStr, typedef_name: &str) -> bool { + file_name == "stdint.h" + || file_name == "types.h" + // glibc also declares `ssize_t` in these headers; whichever + // header a TU includes first provides the typedef. + || ((file_name == "stdio.h" || file_name == "unistd.h") + && typedef_name == "ssize_t") + || file_name + .to_str() + .map(|s| s.starts_with("__stddef_")) + .unwrap_or(false) + // for macos + || file_name == "_types.h" + || file_name + .to_str() + .map(|s| s.starts_with("_int") || s.starts_with("_uint")) + .unwrap_or(false) + } + + // Other fixed-size types are defined without special compiler involvement, by + // standard headers and their transitive includes. In these contexts, we + // recognize these typedefs by the name of the typedef type. + let type_kind_for_typedef_name = + |loc: &SrcSpan, typedef_name: &str| -> Option { + let file_name = self + .files + .get(self.file_map[loc.fileid as usize]) + .and_then(|file| file.path.as_ref()) + .and_then(|path| path.file_name()); + + if file_name.map_or(true, |file| !is_candidate_header(file, typedef_name)) { + return None; + } + + Some(match typedef_name { + "intmax_t" => CTypeKind::IntMax, + "uintmax_t" => CTypeKind::UIntMax, + "intptr_t" => CTypeKind::IntPtr, + "uintptr_t" => CTypeKind::UIntPtr, + // unlike `size_t`, `ssize_t` does not have a clang-provided `#define`. + "ssize_t" => CTypeKind::SSize, + // macOS defines `ssize_t` from `__darwin_ssize_t` in many headers, + // so we should handle `__darwin_ssize_t` itself. + "__darwin_ssize_t" => CTypeKind::SSize, + "__uint8_t" => CTypeKind::UInt8, + "__uint16_t" => CTypeKind::UInt16, + "__uint32_t" => CTypeKind::UInt32, + "__uint64_t" => CTypeKind::UInt64, + "__uint128_t" => CTypeKind::UInt128, + "__int8_t" => CTypeKind::Int8, + "__int16_t" => CTypeKind::Int16, + "__int32_t" => CTypeKind::Int32, + "__int64_t" => CTypeKind::Int64, + "__int128_t" => CTypeKind::Int128, + // macOS stdint.h typedefs [u]intN_t directly: + "int8_t" => CTypeKind::Int8, + "int16_t" => CTypeKind::Int16, + "int32_t" => CTypeKind::Int32, + "int64_t" => CTypeKind::Int64, + "uint8_t" => CTypeKind::UInt8, + "uint16_t" => CTypeKind::UInt16, + "uint32_t" => CTypeKind::UInt32, + "uint64_t" => CTypeKind::UInt64, + _ => { + log::debug!("Unknown fixed-size type typedef {name}!"); + return None; + } + }) + }; + + if let Some(kind) = target_dependent_macro + .as_deref() + .and_then(type_kind_for_macro_name) + { + new_type_kind = Some(kind); + } else if let Some(kind) = decl + .loc + .as_ref() + .and_then(|loc| type_kind_for_typedef_name(loc, name)) + { + new_type_kind = Some(kind); + } + + if let Some(kind) = new_type_kind { + log::trace!("Selected kind {kind} for typedef {name}"); + typ = CQualTypeId::new(self.type_for_kind(&kind)); + } + + let new_typedef_kind = CDeclKind::Typedef { + name: name.clone(), + typ, + is_implicit, + target_dependent_macro: target_dependent_macro.clone(), + }; + replacements.insert(decl_id, decl.with_kind(new_typedef_kind)); + } + + for (decl_id, decl) in replacements { + self.c_decls[&decl_id] = decl; + } + } + /// Eliminates typedefs in various ways. Currently does the following: /// - Typedefs that resolve to one of `PULLBACK_KINDS` will point directly to the target type, /// without intermediate (compiler-internal) typedefs if there are any. From cadb2ede1046032faf7929f5d1c9c8e42540fb4b Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 17:25:53 +0100 Subject: [PATCH 09/13] transpile: Store the original type kinds of replaced pullback kinds --- c2rust-transpile/src/c_ast/mod.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 0a2002d20d..44432b0cc6 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -122,6 +122,10 @@ pub struct TypedAstContext { pub va_list_kind: BuiltinVaListKind, pub target: String, + + /// Maps the `PULLBACK_KINDS` to the compiler-provided type that they had originally. + /// If a key is not present, it means that the compiler did not provide that type. + replaced_type_kinds: HashMap, } /// Information about a single macro invocation. @@ -1310,6 +1314,7 @@ impl TypedAstContext { fn replace_pullback_kinds(&mut self) { let mut replacements: HashMap = HashMap::new(); + let mut replaced_type_kinds: HashMap = HashMap::new(); for (&decl_id, decl) in self.iter_decls() { let CDeclKind::Typedef { @@ -1427,7 +1432,20 @@ impl TypedAstContext { if let Some(kind) = new_type_kind { log::trace!("Selected kind {kind} for typedef {name}"); - typ = CQualTypeId::new(self.type_for_kind(&kind)); + let new_type_id = CQualTypeId::new(self.type_for_kind(&kind)); + + use std::collections::hash_map::Entry; + let old_type_kind = self.resolve_type(typ.ctype).kind.clone(); + match replaced_type_kinds.entry(kind) { + Entry::Occupied(entry) => { + assert_eq!(*entry.into_mut(), old_type_kind); + } + Entry::Vacant(entry) => { + entry.insert(old_type_kind); + } + } + + typ = new_type_id; } let new_typedef_kind = CDeclKind::Typedef { @@ -1442,6 +1460,8 @@ impl TypedAstContext { for (decl_id, decl) in replacements { self.c_decls[&decl_id] = decl; } + + self.replaced_type_kinds = replaced_type_kinds; } /// Eliminates typedefs in various ways. Currently does the following: From 8e9b4fdf08fbcb5acbf8ebe89b60956c36bc4a70 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 17:54:13 +0100 Subject: [PATCH 10/13] transpile: Pass expected and result types to `convert_negate_operator` separately --- c2rust-transpile/src/translator/operators.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index b0777ad702..8650f3ad26 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -572,7 +572,9 @@ impl<'c> Translation<'c> { CUnOp::Deref => self.convert_deref(ctx, expr_type_id, arg), CUnOp::Plus => self.convert_expr(ctx, arg, expected_type_id), // promotion is explicit in the clang AST - CUnOp::Negate => self.convert_negate_operator(ctx, expr_type_id, arg), + CUnOp::Negate => { + self.convert_negate_operator(ctx, expected_type_id, result_type_id, arg) + } CUnOp::Complement => Ok(self .convert_expr(ctx, arg, expected_type_id)? .map(|a| mk().unary_expr(UnOp::Not(Default::default()), a))), @@ -677,9 +679,11 @@ impl<'c> Translation<'c> { fn convert_negate_operator( &self, ctx: ExprContext, - expr_type_id: CQualTypeId, + expected_type_id: Option, + result_type_id: CQualTypeId, arg_id: CExprId, ) -> TranslationResult>> { + let expr_type_id = expected_type_id.unwrap_or(result_type_id); let is_unsigned_integral_type = self .ast_context .resolve_type(expr_type_id.ctype) @@ -689,7 +693,7 @@ impl<'c> Translation<'c> { if let (&CExprKind::Literal(_, CLiteral::Integer(val, base)), false, false) = ( &self.ast_context.index_unwrap_parens(arg_id).kind, is_unsigned_integral_type, - self.expr_is_expanded_macro(ctx, arg_id, Some(expr_type_id)), + self.expr_is_expanded_macro(ctx, arg_id, expected_type_id), ) { // If we are negating a literal, generate a negated literal directly. // This will create an expression like `-1 as ty` without parentheses, @@ -697,7 +701,7 @@ impl<'c> Translation<'c> { 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, arg_id, Some(expr_type_id))?; + let val = self.convert_expr(ctx, arg_id, expected_type_id)?; let val = val.map(|val| { if is_unsigned_integral_type { wrapping_neg_expr(val) From a40b4ea9db023a05ae0ef5c3be56426c7c2585f8 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 17:54:26 +0100 Subject: [PATCH 11/13] transpile: Rename `mk_int_lit` and move it next to `convert_float_literal` --- c2rust-transpile/src/translator/literals.rs | 74 ++++++++++---------- c2rust-transpile/src/translator/mod.rs | 16 +++-- c2rust-transpile/src/translator/operators.rs | 2 +- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index ea59312648..688b23b1dc 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -8,42 +8,6 @@ use std::{borrow::Cow, iter}; use syn::{Path, TypePath}; 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, - negative: bool, - ) -> TranslationResult> { - let target_ty = self.convert_type(ty.ctype)?; - let suffix = numeric_literal_suffix(&target_ty); - let is_suffix = suffix.is_some(); - - let mut lit_str = match base { - IntBase::Dec => format!("{val}"), - IntBase::Hex => format!("0x{val:x}"), - IntBase::Oct => format!("0o{val:o}"), - }; - - if let Some(suffix) = suffix { - lit_str.push_str(&suffix); - } - - let mut expr = mk().lit_expr(mk().float_unsuffixed_lit(&lit_str)); - - if negative { - expr = neg_expr(expr); - } - - Ok(if is_suffix || ctx.is_pattern { - expr - } else { - mk().cast_expr(expr, target_ty) - }) - } - /// Return whether the literal can be directly translated as this type. 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; @@ -71,7 +35,7 @@ impl<'c> Translation<'c> { match *lit { CLiteral::Integer(val, base) => Ok(WithStmts::new_val( - self.mk_int_lit(ctx, ty, val, base, false)?, + self.convert_integer_literal(ctx, ty, val, base, false)?, )), CLiteral::Character(val) => { @@ -154,6 +118,42 @@ impl<'c> Translation<'c> { } } + /// Generate an integer literal corresponding to the given type, value, and base. + pub(crate) fn convert_integer_literal( + &self, + ctx: ExprContext, + ty: CQualTypeId, + val: u64, + base: IntBase, + negative: bool, + ) -> TranslationResult> { + let target_ty = self.convert_type(ty.ctype)?; + let suffix = numeric_literal_suffix(&target_ty); + let is_suffix = suffix.is_some(); + + let mut lit_str = match base { + IntBase::Dec => format!("{val}"), + IntBase::Hex => format!("0x{val:x}"), + IntBase::Oct => format!("0o{val:o}"), + }; + + if let Some(suffix) = suffix { + lit_str.push_str(&suffix); + } + + let mut expr = mk().lit_expr(mk().float_unsuffixed_lit(&lit_str)); + + if negative { + expr = neg_expr(expr); + } + + Ok(if is_suffix || ctx.is_pattern { + expr + } else { + mk().cast_expr(expr, target_ty) + }) + } + pub(crate) fn convert_floating_literal( &self, ctx: ExprContext, diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 6c718a84e6..b9340509ac 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3566,13 +3566,15 @@ impl<'c> Translation<'c> { .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( - ctx, - override_ty.unwrap_or(ty), - *val, - IntBase::Dec, - false, - )?)), + OffsetOfKind::Constant(val) => { + Ok(WithStmts::new_val(self.convert_integer_literal( + ctx, + override_ty.unwrap_or(ty), + *val, + IntBase::Dec, + false, + )?)) + } OffsetOfKind::Variable(qty, field_id, expr_id) => { self.use_crate(ExternCrate::Memoffset); diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 8650f3ad26..6d65bd42b0 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -698,7 +698,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(ctx, expr_type_id, val, base, true)?; + let val = self.convert_integer_literal(ctx, expr_type_id, val, base, true)?; Ok(WithStmts::new_val(val)) } else { let val = self.convert_expr(ctx, arg_id, expected_type_id)?; From 9b535213898954db3d7403f511ef90cde51e3af8 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 17:54:48 +0100 Subject: [PATCH 12/13] transpile: Change `convert_integer_literal` parameters and return type --- c2rust-transpile/src/translator/literals.rs | 43 ++++++++++++-------- c2rust-transpile/src/translator/mod.rs | 8 +--- c2rust-transpile/src/translator/operators.rs | 3 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index 688b23b1dc..daa587b626 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -34,9 +34,14 @@ impl<'c> Translation<'c> { let ty = expected_type_id.unwrap_or(literal_type_id); match *lit { - CLiteral::Integer(val, base) => Ok(WithStmts::new_val( - self.convert_integer_literal(ctx, ty, val, base, false)?, - )), + CLiteral::Integer(val, base) => self.convert_integer_literal( + ctx, + expected_type_id, + literal_type_id, + val, + base, + false, + ), CLiteral::Character(val) => { let val = val as u32; @@ -122,19 +127,21 @@ impl<'c> Translation<'c> { pub(crate) fn convert_integer_literal( &self, ctx: ExprContext, - ty: CQualTypeId, - val: u64, + expected_type_id: Option, + mut literal_type_id: CQualTypeId, + value: u64, base: IntBase, - negative: bool, - ) -> TranslationResult> { - let target_ty = self.convert_type(ty.ctype)?; - let suffix = numeric_literal_suffix(&target_ty); + is_negated: bool, + ) -> TranslationResult>> { + literal_type_id = expected_type_id.unwrap_or(literal_type_id); + let literal_type_rs = self.convert_type(literal_type_id.ctype)?; + let suffix = numeric_literal_suffix(&literal_type_rs); let is_suffix = suffix.is_some(); let mut lit_str = match base { - IntBase::Dec => format!("{val}"), - IntBase::Hex => format!("0x{val:x}"), - IntBase::Oct => format!("0o{val:o}"), + IntBase::Dec => format!("{value}"), + IntBase::Hex => format!("0x{value:x}"), + IntBase::Oct => format!("0o{value:o}"), }; if let Some(suffix) = suffix { @@ -143,15 +150,15 @@ impl<'c> Translation<'c> { let mut expr = mk().lit_expr(mk().float_unsuffixed_lit(&lit_str)); - if negative { + if is_negated { expr = neg_expr(expr); } - Ok(if is_suffix || ctx.is_pattern { - expr - } else { - mk().cast_expr(expr, target_ty) - }) + if !is_suffix && !ctx.is_pattern { + expr = mk().cast_expr(expr, literal_type_rs); + } + + Ok(expr.into()) } pub(crate) fn convert_floating_literal( diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index b9340509ac..ce9bf93f21 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3567,13 +3567,7 @@ impl<'c> Translation<'c> { OffsetOf(ty, ref kind) => match kind { OffsetOfKind::Constant(val) => { - Ok(WithStmts::new_val(self.convert_integer_literal( - ctx, - override_ty.unwrap_or(ty), - *val, - IntBase::Dec, - false, - )?)) + self.convert_integer_literal(ctx, override_ty, ty, *val, IntBase::Dec, false) } OffsetOfKind::Variable(qty, field_id, expr_id) => { self.use_crate(ExternCrate::Memoffset); diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 6d65bd42b0..354fde94c1 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -698,8 +698,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.convert_integer_literal(ctx, expr_type_id, val, base, true)?; - Ok(WithStmts::new_val(val)) + self.convert_integer_literal(ctx, expected_type_id, result_type_id, val, base, true) } else { let val = self.convert_expr(ctx, arg_id, expected_type_id)?; let val = val.map(|val| { From c29926487d3587e91a1078380be997809417d18c Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 2 Nov 2026 21:50:56 +0100 Subject: [PATCH 13/13] transpile: In `convert_integer_literal`, accept any expected type --- c2rust-transpile/src/c_ast/mod.rs | 18 ++++++++++ c2rust-transpile/src/translator/literals.rs | 35 +++++++++++++++++--- c2rust-transpile/src/translator/mod.rs | 11 +++--- c2rust-transpile/src/translator/operators.rs | 15 +++------ 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 44432b0cc6..91e9875e2b 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -1057,6 +1057,16 @@ impl TypedAstContext { } } + pub(crate) fn kind_has_replaced( + &self, + replacement_kind: &CTypeKind, + original_kind: &CTypeKind, + ) -> bool { + self.replaced_type_kinds + .get(replacement_kind) + .is_some_and(|kind| kind == original_kind) + } + /// Extract decl of referenced function. /// Looks for ImplicitCast(FunctionToPointerDecay, DeclRef(function_decl)) pub fn fn_declref_decl(&self, func_expr: CExprId) -> Option<&CDeclKind> { @@ -2378,6 +2388,14 @@ impl CastKind { } Some(match (source_ty_kind, target_ty_kind) { + (CTypeKind::Atomic(..), _) if !matches!(target_ty_kind, CTypeKind::Atomic(..)) => { + CastKind::AtomicToNonAtomic + } + + (_, CTypeKind::Atomic(..)) if !matches!(source_ty_kind, CTypeKind::Atomic(..)) => { + CastKind::NonAtomicToAtomic + } + (CTypeKind::VariableArray(..), CTypeKind::Pointer(..)) | (CTypeKind::ConstantArray(..), CTypeKind::Pointer(..)) | (CTypeKind::IncompleteArray(..), CTypeKind::Pointer(..)) => { diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index daa587b626..a07ef3c8ae 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -12,12 +12,13 @@ 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(value, _) | CLiteral::Character(value) => ty_kind + CLiteral::Character(value) => ty_kind .integer_kind() .is_some_and(|integer_kind| integer_kind.is_guaranteed_in_range(value, is_negated)), - // `convert_floating_literal` will handle incompatible types itself. - CLiteral::Floating(_, _) => true, + // `convert_integer_literal` and `convert_floating_literal` will handle incompatible + // types themselves. + CLiteral::Integer(_, _) | CLiteral::Floating(_, _) => true, _ => false, } @@ -133,7 +134,31 @@ impl<'c> Translation<'c> { base: IntBase, is_negated: bool, ) -> TranslationResult>> { - literal_type_id = expected_type_id.unwrap_or(literal_type_id); + let target_type_id = expected_type_id.unwrap_or(literal_type_id); + + let literal_type_kind = &self.ast_context.resolve_type(literal_type_id.ctype).kind; + let Some(literal_integer_kind) = literal_type_kind.integer_kind() else { + panic!("type of integer literal is not an integer type: {literal_type_kind:?}"); + }; + + if let Some(expected_type_id) = expected_type_id { + let expected_type_kind = &self.ast_context.resolve_type(expected_type_id.ctype).kind; + + if let Some(expected_integer_kind) = expected_type_kind.integer_kind() { + // If the value is guaranteed to be in range of the expected type, + // or the expected type has replaced the original literal type, + // then use the expected type as the literal type. + if literal_integer_kind == expected_integer_kind + || expected_integer_kind.is_guaranteed_in_range(value, is_negated) + || self + .ast_context + .kind_has_replaced(expected_type_kind, literal_type_kind) + { + literal_type_id = expected_type_id; + } + } + } + let literal_type_rs = self.convert_type(literal_type_id.ctype)?; let suffix = numeric_literal_suffix(&literal_type_rs); let is_suffix = suffix.is_some(); @@ -158,7 +183,7 @@ impl<'c> Translation<'c> { expr = mk().cast_expr(expr, literal_type_rs); } - Ok(expr.into()) + self.make_cast(ctx, literal_type_id, target_type_id, expr.into()) } pub(crate) fn convert_floating_literal( diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index ce9bf93f21..00bea304b7 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -4185,6 +4185,10 @@ impl<'c> Translation<'c> { return self.make_cast(ctx, source_ty.not_volatile(), target_ty, val); } + CastKind::NullToPointer => { + return Ok(WithStmts::new_val(self.null_ptr(target_ty.ctype)?)); + } + CastKind::IntegralToBoolean | CastKind::FloatingToBoolean | CastKind::PointerToBoolean => { @@ -4404,7 +4408,7 @@ impl<'c> Translation<'c> { } } - CastKind::IntegralToPointer => { + CastKind::IntegralToPointer | CastKind::NullToPointer => { self.convert_integral_to_pointer_cast(ctx, source_cty, target_cty, val) } @@ -4488,11 +4492,6 @@ impl<'c> Translation<'c> { self.convert_array_to_pointer_decay(ctx, source_cty, target_cty, val, expr) } - CastKind::NullToPointer => { - assert!(val.stmts().is_empty()); - Ok(WithStmts::new_val(self.null_ptr(target_cty.ctype)?)) - } - CastKind::ToUnion => self.convert_cast_to_union(val, opt_field_id), CastKind::IntegralToBoolean diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 354fde94c1..1c2b4c1b5f 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -683,16 +683,8 @@ impl<'c> Translation<'c> { result_type_id: CQualTypeId, arg_id: CExprId, ) -> TranslationResult>> { - let expr_type_id = expected_type_id.unwrap_or(result_type_id); - let is_unsigned_integral_type = self - .ast_context - .resolve_type(expr_type_id.ctype) - .kind - .is_unsigned_integral_type(); - - if let (&CExprKind::Literal(_, CLiteral::Integer(val, base)), false, false) = ( + if let (&CExprKind::Literal(_, CLiteral::Integer(val, base)), false) = ( &self.ast_context.index_unwrap_parens(arg_id).kind, - is_unsigned_integral_type, self.expr_is_expanded_macro(ctx, arg_id, expected_type_id), ) { // If we are negating a literal, generate a negated literal directly. @@ -700,9 +692,12 @@ impl<'c> Translation<'c> { // rather than `-(1 as ty)`. self.convert_integer_literal(ctx, expected_type_id, result_type_id, val, base, true) } else { + let result_type_id = expected_type_id.unwrap_or(result_type_id); + let result_type_kind = &self.ast_context.resolve_type(result_type_id.ctype).kind; + let val = self.convert_expr(ctx, arg_id, expected_type_id)?; let val = val.map(|val| { - if is_unsigned_integral_type { + if result_type_kind.is_unsigned_integral_type() { wrapping_neg_expr(val) } else { neg_expr(val)