From 46da1b83e889eaa1a661a7a4c28457634a6a1255 Mon Sep 17 00:00:00 2001 From: mbyx Date: Tue, 5 Aug 2025 11:42:17 +0500 Subject: [PATCH] ctest: improve translation backend --- ctest-next/src/cdecl.rs | 135 ++++---- ctest-next/src/generator.rs | 3 +- ctest-next/src/template.rs | 189 +++-------- ctest-next/src/tests.rs | 100 +++--- ctest-next/src/translator.rs | 293 +++++++++--------- ctest-next/tests/input/macro.out.c | 8 +- .../tests/input/simple.out.with-renames.c | 8 +- .../tests/input/simple.out.with-skips.c | 8 +- 8 files changed, 328 insertions(+), 416 deletions(-) diff --git a/ctest-next/src/cdecl.rs b/ctest-next/src/cdecl.rs index b5071e2bf202d..26a4e49d0a609 100644 --- a/ctest-next/src/cdecl.rs +++ b/ctest-next/src/cdecl.rs @@ -4,7 +4,6 @@ use std::fmt::Write; type BoxStr = Box; -#[cfg_attr(not(test), expect(dead_code))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum Constness { Const, @@ -15,7 +14,6 @@ pub(crate) enum Constness { use Constness::{Const, Mut}; /// A basic representation of C's types. -#[cfg_attr(not(test), expect(dead_code))] #[derive(Clone, Debug)] pub(crate) enum CTy { /// `int`, `struct foo`, etc. There is only ever one basic type per decl. @@ -85,7 +83,6 @@ pub(crate) struct InvalidReturn; /// value to allow reusing allocations. /// /// If needed, `name` can be empty (e.g. for function arguments). -#[cfg_attr(not(test), expect(dead_code))] pub(crate) fn cdecl(cty: &CTy, mut name: String) -> Result { cdecl_impl(cty, &mut name, None)?; Ok(name) @@ -178,6 +175,74 @@ fn space_if(yes: bool, s: &mut String) { } } +pub(crate) fn named(name: &str, constness: Constness) -> CTy { + CTy::Named { + name: name.into(), + qual: Qual { + constness, + volatile: false, + restrict: false, + }, + } +} + +#[cfg_attr(not(test), expect(unused))] +pub(crate) fn named_qual(name: &str, qual: Qual) -> CTy { + CTy::Named { + name: name.into(), + qual, + } +} + +pub(crate) fn ptr(inner: CTy, constness: Constness) -> CTy { + ptr_qual( + inner, + Qual { + constness, + volatile: false, + restrict: false, + }, + ) +} + +pub(crate) fn ptr_qual(inner: CTy, qual: Qual) -> CTy { + CTy::Ptr { + ty: Box::new(inner), + qual, + } +} + +pub(crate) fn array(inner: CTy, len: Option<&str>) -> CTy { + CTy::Array { + ty: Box::new(inner), + len: len.map(Into::into), + } +} + +/// Function type (not a pointer) +#[cfg_attr(not(test), expect(unused))] +pub(crate) fn func(args: Vec, ret: CTy) -> CTy { + CTy::Fn { + args, + ret: Box::new(ret), + } +} + +/// Function pointer +pub(crate) fn func_ptr(args: Vec, ret: CTy) -> CTy { + CTy::Ptr { + ty: Box::new(CTy::Fn { + args, + ret: Box::new(ret), + }), + qual: Qual { + constness: Constness::Mut, + volatile: false, + restrict: false, + }, + } +} + /// Checked with . #[cfg(test)] mod tests { @@ -210,68 +275,6 @@ mod tests { named("int", Const) } - fn named(name: &str, constness: Constness) -> CTy { - CTy::Named { - name: name.into(), - qual: Qual { - constness, - volatile: false, - restrict: false, - }, - } - } - - fn named_qual(name: &str, qual: Qual) -> CTy { - CTy::Named { - name: name.into(), - qual, - } - } - - fn ptr(inner: CTy, constness: Constness) -> CTy { - ptr_qual( - inner, - Qual { - constness, - volatile: false, - restrict: false, - }, - ) - } - - fn ptr_qual(inner: CTy, qual: Qual) -> CTy { - CTy::Ptr { - ty: Box::new(inner), - qual, - } - } - - fn array(inner: CTy, len: Option<&str>) -> CTy { - CTy::Array { - ty: Box::new(inner), - len: len.map(Into::into), - } - } - - /// Function type (not a pointer) - fn func(args: Vec, ret: CTy) -> CTy { - CTy::Fn { - args, - ret: Box::new(ret), - } - } - - /// Function pointer - fn func_ptr(args: Vec, ret: CTy) -> CTy { - ptr( - CTy::Fn { - args, - ret: Box::new(ret), - }, - Mut, - ) - } - #[test] fn basic() { assert_decl(&const_int(), "const int foo"); @@ -350,7 +353,7 @@ mod tests { // Can't return an array assert!(cdecl(&func(vec![], array(mut_int(), None)), "foo".to_owned(),).is_err(),); // Can't return a function - assert!(cdecl(&func(vec![], func(vec![], mut_int()),), "foo".to_owned(),).is_err(),); + assert!(cdecl(&func(vec![], func(vec![], mut_int())), "foo".to_owned(),).is_err(),); } #[test] diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs index eee3b366d066c..a9177a7aed284 100644 --- a/ctest-next/src/generator.rs +++ b/ctest-next/src/generator.rs @@ -9,6 +9,7 @@ use thiserror::Error; use crate::ffi_items::FfiItems; use crate::template::{CTestTemplate, RustTestTemplate}; +use crate::translator::translate_primitive_type; use crate::{ Const, Field, MapInput, Parameter, Result, Static, Struct, TranslationError, Type, Union, VolatileItemKind, expand, @@ -962,7 +963,7 @@ impl TestGenerator { MapInput::UnionType(ty) => format!("union {ty}"), MapInput::StructFieldType(_, f) => f.ident().to_string(), MapInput::UnionFieldType(_, f) => f.ident().to_string(), - MapInput::Type(ty) => ty.to_string(), + MapInput::Type(ty) => translate_primitive_type(ty), } } } diff --git a/ctest-next/src/template.rs b/ctest-next/src/template.rs index c7281c86a2fd2..955e862b2cd27 100644 --- a/ctest-next/src/template.rs +++ b/ctest-next/src/template.rs @@ -1,12 +1,13 @@ -use std::ops::Deref; - use askama::Template; +use proc_macro2::Span; use quote::ToTokens; use syn::spanned::Spanned; use crate::ffi_items::FfiItems; -use crate::translator::{TranslationErrorKind, Translator, translate_abi, translate_expr}; -use crate::{BoxStr, Field, MapInput, Result, TestGenerator, TranslationError, VolatileItemKind}; +use crate::translator::Translator; +use crate::{ + BoxStr, Field, MapInput, Result, TestGenerator, TranslationError, VolatileItemKind, cdecl, +}; /// Represents the Rust side of the generated testing suite. #[derive(Template, Clone)] @@ -65,11 +66,7 @@ impl TestTemplate { ffi_items: &FfiItems, generator: &TestGenerator, ) -> Result { - let helper = TranslateHelper { - ffi_items, - generator, - translator: Translator::new(), - }; + let helper = TranslateHelper::new(ffi_items, generator); let mut template = Self::default(); template.populate_const_and_cstr_tests(&helper)?; @@ -173,9 +170,7 @@ impl TestTemplate { .as_ref() .is_some_and(|skip| skip(alias.ident())); - if !helper.translator.is_signed(helper.ffi_items, &alias.ty) - || should_skip_signededness_test - { + if !helper.translator.is_signed(&alias.ty) || should_skip_signededness_test { continue; } let item = TestSignededness { @@ -355,12 +350,21 @@ impl TestTemplate { }); for (id, field, c_ty, c_field, volatile_keyword) in struct_fields.chain(union_fields) { - let field_return_type = helper - .make_cdecl( - &format!("ctest_field_ty__{}__{}", id, field.ident()), - &field.ty, - )? - .into_boxed_str(); + let field_return_type = cdecl::cdecl( + &cdecl::ptr( + helper.translator.translate_type(&field.ty)?, + cdecl::Constness::Mut, + ), + format!("ctest_field_ty__{}__{}", id, field.ident()), + ) + .map_err(|_| { + TranslationError::new( + crate::translator::TranslationErrorKind::InvalidReturn, + &field.ty.to_token_stream().to_string(), + field.ty.span(), + ) + })? + .into_boxed_str(); let item = TestFieldPtr { test_name: field_ptr_test_ident(id, field.ident()), id: id.into(), @@ -485,17 +489,16 @@ fn roundtrip_test_ident(ident: &str) -> BoxStr { pub(crate) struct TranslateHelper<'a> { ffi_items: &'a FfiItems, generator: &'a TestGenerator, - translator: Translator, + translator: Translator<'a>, } impl<'a> TranslateHelper<'a> { /// Create a new translation helper. - #[cfg_attr(not(test), expect(unused))] pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self { Self { ffi_items, generator, - translator: Translator::new(), + translator: Translator::new(ffi_items, generator), } } @@ -517,9 +520,9 @@ impl<'a> TranslateHelper<'a> { // inside of `Fn` when parsed. MapInput::Fn(_) => unimplemented!(), // For structs/unions/aliases, their type is the same as their identifier. - MapInput::Alias(a) => (a.ident(), a.ident().to_string()), - MapInput::Struct(s) => (s.ident(), s.ident().to_string()), - MapInput::Union(u) => (u.ident(), u.ident().to_string()), + MapInput::Alias(a) => (a.ident(), cdecl::named(a.ident(), cdecl::Constness::Mut)), + MapInput::Struct(s) => (s.ident(), cdecl::named(s.ident(), cdecl::Constness::Mut)), + MapInput::Union(u) => (u.ident(), cdecl::named(u.ident(), cdecl::Constness::Mut)), MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"), MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"), @@ -528,6 +531,14 @@ impl<'a> TranslateHelper<'a> { MapInput::Type(_) => panic!("MapInput::Type is not allowed!"), }; + let ty = cdecl::cdecl(&ty, "".to_string()).map_err(|_| { + TranslationError::new( + crate::translator::TranslationErrorKind::InvalidReturn, + ident, + Span::call_site(), + ) + })?; + let item = if self.ffi_items.contains_struct(ident) { MapInput::StructType(&ty) } else if self.ffi_items.contains_union(ident) { @@ -538,134 +549,4 @@ impl<'a> TranslateHelper<'a> { Ok(self.generator.rty_to_cty(item)) } - - /// Get the properly mapped type for some `syn::Type`, recursing for pointer types as needed. - /// - /// This method is meant to only be used to translate simpler types, such as primitives or - /// pointers/references to primitives. It will also add struct/union keywords as needed. - fn basic_c_type(&self, ty: &syn::Type) -> Result { - let type_name = match ty { - syn::Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(), - syn::Type::Ptr(p) => self.basic_c_type(&p.elem)?, - syn::Type::Reference(r) => self.basic_c_type(&r.elem)?, - _ => ty.to_token_stream().to_string(), - }; - - let unmapped_c_type = self.translator.translate_type(ty)?; - let item = if self.ffi_items.contains_struct(&type_name) { - MapInput::StructType(&unmapped_c_type) - } else if self.ffi_items.contains_union(&type_name) { - MapInput::UnionType(&unmapped_c_type) - } else { - MapInput::Type(&unmapped_c_type) - }; - - Ok(self.generator.rty_to_cty(item)) - } - - /// Partially translate a Rust bare function type into its equivalent C type. - /// - /// It returns the translated return type, translated argument types, and whether - /// it is variadic as a tuple. - fn translate_signature_partial( - &self, - signature: &syn::TypeBareFn, - ) -> Result<(String, Vec, bool), TranslationError> { - let args = signature - .inputs - .iter() - .map(|arg| self.basic_c_type(&arg.ty)) - .collect::, TranslationError>>()?; - let return_type = match &signature.output { - syn::ReturnType::Default => "void".to_string(), - syn::ReturnType::Type(_, ty) => match ty.deref() { - syn::Type::Never(_) => "void".to_string(), - syn::Type::Tuple(tuple) if tuple.elems.is_empty() => "void".to_string(), - _ => self.basic_c_type(ty.deref())?, - }, - }; - Ok((return_type, args, signature.variadic.is_some())) - } - - /// Modify function signatures to properly return pointer types in C. - /// - /// In C, function pointers and arrays have a different syntax to return them, - /// and this translation is done by this method. - pub(crate) fn make_cdecl( - &self, - name: &str, - ty: &syn::Type, - ) -> Result { - match ty { - syn::Type::Path(p) => { - let last = p.path.segments.last().unwrap(); - let ident = last.ident.to_string(); - if ident != "Option" { - let mapped_type = self.basic_c_type(ty)?; - return Ok(format!("{mapped_type}* {name}")); - } - if let syn::PathArguments::AngleBracketed(args) = &last.arguments { - if let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap() { - // Option is ONLY ffi-safe if it contains a function pointer, or a reference. - match inner_ty { - syn::Type::Reference(_) | syn::Type::BareFn(_) => { - return self.make_cdecl(name, inner_ty); - } - _ => { - return Err(TranslationError::new( - TranslationErrorKind::NotFfiCompatible, - &p.to_token_stream().to_string(), - inner_ty.span(), - )); - } - } - } - } - } - syn::Type::BareFn(f) => { - let (ret, mut args, variadic) = self.translate_signature_partial(f)?; - let abi = if let Some(abi) = &f.abi { - let target = self - .generator - .target - .clone() - .or_else(|| std::env::var("TARGET").ok()) - .or_else(|| std::env::var("TARGET_PLATFORM").ok()) - .unwrap(); - translate_abi(abi, &target) - } else { - "" - }; - - if variadic { - args.push("...".to_string()); - } else if args.is_empty() { - args.push("void".to_string()); - } - - return Ok(format!("{} ({}**{})({})", ret, abi, name, args.join(", "))); - } - // Arrays are supported only up to 2D arrays. - syn::Type::Array(outer) => { - let elem = outer.elem.deref(); - let len_outer = translate_expr(&outer.len); - - if let syn::Type::Array(inner) = elem { - let inner_type = self.basic_c_type(inner.elem.deref())?; - let len_inner = translate_expr(&inner.len); - return Ok(format!("{inner_type} (*{name})[{len_outer}][{len_inner}]",)); - } else { - let elem_type = self.basic_c_type(elem)?; - return Ok(format!("{elem_type} (*{name})[{len_outer}]")); - } - } - _ => { - let elem_type = self.basic_c_type(ty)?; - return Ok(format!("{elem_type} *{name}")); - } - } - - let mapped_type = self.basic_c_type(ty)?; - Ok(format!("{mapped_type}* {name}")) - } } diff --git a/ctest-next/src/tests.rs b/ctest-next/src/tests.rs index bf6748e774fb2..088f404b69f71 100644 --- a/ctest-next/src/tests.rs +++ b/ctest-next/src/tests.rs @@ -1,9 +1,9 @@ +use syn::spanned::Spanned; use syn::visit::Visit; use crate::ffi_items::FfiItems; -use crate::template::TranslateHelper; use crate::translator::Translator; -use crate::{Result, TestGenerator, TranslationError}; +use crate::{Result, TestGenerator, TranslationError, cdecl}; const ALL_ITEMS: &str = r#" use std::os::raw::c_void; @@ -37,21 +37,20 @@ macro_rules! collect_idents { }; } -/// Translate a Rust type to C. -fn ty(s: &str) -> Result { - let translator = Translator {}; - let ty: syn::Type = syn::parse_str(s).unwrap(); - translator.translate_type(&ty) -} - /// Translate a Rust type into a c typedef declaration. -fn cdecl(s: &str) -> Result { - let ty: syn::Type = syn::parse_str(s).unwrap(); +fn r2cdecl(s: &str, name: &str) -> Result { let ffi_items = FfiItems::new(); let generator = TestGenerator::new(); - let helper = TranslateHelper::new(&ffi_items, &generator); - - helper.make_cdecl("test_make_cdecl", &ty) + let translator = Translator::new(&ffi_items, &generator); + let ty: syn::Type = syn::parse_str(s).unwrap(); + let translated = translator.translate_type(&ty)?; + cdecl::cdecl(&translated, name.to_string()).map_err(|_| { + TranslationError::new( + crate::translator::TranslationErrorKind::InvalidReturn, + s, + ty.span(), + ) + }) } #[test] @@ -72,72 +71,83 @@ fn test_extraction_ffi_items() { #[test] fn test_translation_type_ptr() { assert_eq!( - ty("*const *mut i32").unwrap(), - "int32_t * const*".to_string() + r2cdecl("*const *mut i32", "").unwrap(), + "int32_t *const *".to_string() ); assert_eq!( - ty("*const [u128; 2 + 3]").unwrap(), - "unsigned __int128 (*const) [2 + 3]".to_string() + r2cdecl("*const [u128; 2 + 3]", "").unwrap(), + "unsigned __int128 (*)[2 + 3]".to_string() + ); + assert_eq!( + r2cdecl("*const *mut [u8; 5]", "").unwrap(), + "uint8_t (*const *)[5]".to_string() ); - // FIXME(ctest): While not a valid C type, it will be used to - // generate a valid test in the future. - // assert_eq!( - // ty("*const *mut [u8; 5]").unwrap(), - // "uint8_t (*const *) [5]".to_string() - // ); } #[test] fn test_translation_type_reference() { - assert_eq!(ty("&u8").unwrap(), "const uint8_t*".to_string()); - assert_eq!(ty("&&u8").unwrap(), "const uint8_t* const*".to_string()); - assert_eq!(ty("*mut &u8").unwrap(), "const uint8_t* *".to_string()); - assert_eq!(ty("& &mut u8").unwrap(), "uint8_t* const*".to_string()); + assert_eq!(r2cdecl("&u8", "").unwrap(), "const uint8_t *".to_string()); + assert_eq!( + r2cdecl("&&u8", "").unwrap(), + "const uint8_t *const *".to_string() + ); + assert_eq!( + r2cdecl("*mut &u8", "").unwrap(), + "const uint8_t **".to_string() + ); + assert_eq!( + r2cdecl("& &mut u8", "").unwrap(), + "uint8_t *const *".to_string() + ); } #[test] fn test_translation_type_bare_fn() { assert_eq!( - ty("fn(*mut u8, i16) -> *const char").unwrap(), - "char const*(*)(uint8_t *, int16_t)".to_string() + r2cdecl("fn(*mut u8, i16) -> *const char", "").unwrap(), + "const char *(*)(uint8_t *, int16_t)".to_string() ); assert_eq!( - ty("*const fn(*mut u8, &mut [u8; 16]) -> &mut *mut u8").unwrap(), - "uint8_t * *(*const)(uint8_t *, uint8_t (*) [16])".to_string() + r2cdecl("*const fn(*mut u8, &mut [u8; 16]) -> &mut *mut u8", "").unwrap(), + "uint8_t **(*const *)(uint8_t *, uint8_t (*)[16])".to_string() ); } #[test] fn test_translation_type_array() { assert_eq!( - ty("[&u8; 2 + 2]").unwrap(), - "const uint8_t*[2 + 2]".to_string() + r2cdecl("[&u8; 2 + 2]", "").unwrap(), + "const uint8_t *[2 + 2]".to_string() ); } #[test] fn test_translation_fails_for_unsupported() { - assert!(ty("[&str; 2 + 2]").is_err()); - assert!(ty("fn(*mut [u8], i16) -> *const char").is_err()); + assert!(r2cdecl("[&str; 2 + 2]", "").is_err()); + assert!(r2cdecl("fn(*mut [u8], i16) -> *const char", "").is_err()); } #[test] fn test_translate_helper_function_pointer() { assert_eq!( - cdecl("extern \"C\" fn(c_int) -> *const c_void").unwrap(), - "void const* (**test_make_cdecl)(int)" - ); - assert_eq!( - cdecl("Option u8>").unwrap(), - "uint8_t (__stdcall **test_make_cdecl)(char const*, uint32_t[16])" + r2cdecl("extern \"C\" fn(c_int) -> *const c_void", "test_make_cdecl").unwrap(), + "const void *(*test_make_cdecl)(int)" ); + // FIXME(ctest): Reimplement support for ABI in a more robust way. + // assert_eq!( + // cdecl("Option u8>").unwrap(), + // "uint8_t (__stdcall **test_make_cdecl)(const char *, uint32_t [16])" + // ); } #[test] fn test_translate_helper_array_1d_2d() { - assert_eq!(cdecl("[u8; 10]").unwrap(), "uint8_t (*test_make_cdecl)[10]"); assert_eq!( - cdecl("[[u8; 64]; 32]").unwrap(), - "uint8_t (*test_make_cdecl)[32][64]" + r2cdecl("[u8; 10]", "test_make_cdecl").unwrap(), + "uint8_t test_make_cdecl[10]", + ); + assert_eq!( + r2cdecl("[[u8; 64]; 32]", "test_make_cdecl").unwrap(), + "uint8_t test_make_cdecl[32][64]" ); } diff --git a/ctest-next/src/translator.rs b/ctest-next/src/translator.rs index f5abe1bd2efa0..f2c8feb03f148 100644 --- a/ctest-next/src/translator.rs +++ b/ctest-next/src/translator.rs @@ -3,15 +3,16 @@ //! Simple to semi complex types are supported only. use std::fmt; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use proc_macro2::Span; use quote::ToTokens; use syn::spanned::Spanned; use thiserror::Error; -use crate::BoxStr; +use crate::cdecl::Constness; use crate::ffi_items::FfiItems; +use crate::{BoxStr, MapInput, TestGenerator, cdecl}; /// An error that occurs during translation, detailing cause and location. #[derive(Debug, Error)] @@ -78,33 +79,40 @@ pub(crate) enum TranslationErrorKind { "this type is not guaranteed to have a C compatible layout. See improper_ctypes_definitions lint" )] NotFfiCompatible, + + /// An array or function was attempted to be returned by a function. + #[error("invalid return type")] + InvalidReturn, } -#[derive(Clone, Debug, Default)] +#[derive(Clone)] /// A Rust to C/Cxx translator. -pub(crate) struct Translator {} +pub(crate) struct Translator<'a> { + ffi_items: &'a FfiItems, + generator: &'a TestGenerator, +} -impl Translator { +impl<'a> Translator<'a> { /// Create a new translator. - pub(crate) fn new() -> Self { - Self::default() - } - - /// Translate mutability from Rust to C. - fn translate_mut(&self, mutability: Option) -> String { - mutability.map(|_| "").unwrap_or("const").to_string() + pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self { + Self { + ffi_items, + generator, + } } /// Translate a Rust type into its equivalent C type. - pub(crate) fn translate_type(&self, ty: &syn::Type) -> Result { + pub(crate) fn translate_type(&self, ty: &syn::Type) -> Result { match ty { syn::Type::Ptr(ptr) => self.translate_ptr(ptr), syn::Type::Path(path) => self.translate_path(path), - syn::Type::Tuple(tuple) if tuple.elems.is_empty() => Ok("void".to_string()), + syn::Type::Tuple(tuple) if tuple.elems.is_empty() => { + Ok(cdecl::named("void", Constness::Mut)) + } syn::Type::Array(array) => self.translate_array(array), syn::Type::Reference(reference) => self.translate_reference(reference), syn::Type::BareFn(function) => self.translate_bare_fn(function), - syn::Type::Never(_) => Ok("void".to_string()), + syn::Type::Never(_) => Ok(cdecl::named("void", Constness::Mut)), syn::Type::Slice(slice) => Err(TranslationError::new( TranslationErrorKind::NotFfiCompatible, &slice.to_token_stream().to_string(), @@ -124,9 +132,7 @@ impl Translator { fn translate_reference( &self, reference: &syn::TypeReference, - ) -> Result { - let modifier = self.translate_mut(reference.mutability); - + ) -> Result { match reference.elem.deref() { syn::Type::Path(path) => { let last_segment = path.path.segments.last().unwrap(); @@ -142,8 +148,11 @@ impl Translator { )) } c if is_rust_primitive(c) => { - let base_type = self.translate_primitive_type(&last_segment.ident); - Ok(format!("{modifier} {base_type}*").trim().to_string()) + let type_name = translate_primitive_type(&last_segment.ident.to_string()); + Ok(ptr_with_inner( + cdecl::named(&type_name, Constness::Mut), + reference.mutability, + )) } _ => Err(TranslationError::new( TranslationErrorKind::NonPrimitiveReference, @@ -152,33 +161,14 @@ impl Translator { )), } } - syn::Type::Array(arr) => { - let len = translate_expr(&arr.len); - let ty = self.translate_type(arr.elem.deref())?; - let inner_type = format!("{ty} (*) [{len}]"); - Ok(inner_type - .replacen("(*)", &format!("(*{modifier})"), 1) - .trim() - .to_string()) - } - syn::Type::BareFn(_) => { - let inner_type = self.translate_type(reference.elem.deref())?; - Ok(inner_type - .replacen("(*)", &format!("(*{modifier})"), 1) - .trim() - .to_string()) - } - syn::Type::Reference(_) | syn::Type::Ptr(_) => { - let inner_type = self.translate_type(reference.elem.deref())?; - if inner_type.contains("(*)") { - Ok(inner_type - .replacen("(*)", &format!("(*{modifier})"), 1) - .trim() - .to_string()) - } else { - Ok(format!("{inner_type} {modifier}*").trim().to_string()) - } + syn::Type::Reference(_) + | syn::Type::Ptr(_) + | syn::Type::Array(_) + | syn::Type::BareFn(_) => { + let ty = self.translate_type(reference.elem.deref())?; + Ok(ptr_with_inner(ty, reference.mutability)) } + _ => Err(TranslationError::new( TranslationErrorKind::UnsupportedType, &reference.elem.to_token_stream().to_string(), @@ -188,7 +178,10 @@ impl Translator { } /// Translate a Rust function pointer type to its C equivalent. - fn translate_bare_fn(&self, function: &syn::TypeBareFn) -> Result { + fn translate_bare_fn( + &self, + function: &syn::TypeBareFn, + ) -> Result { if function.lifetimes.is_some() { return Err(TranslationError::new( TranslationErrorKind::HasLifetimes, @@ -211,109 +204,64 @@ impl Translator { .collect::, TranslationError>>()?; let return_type = match &function.output { - syn::ReturnType::Default => "void".to_string(), + syn::ReturnType::Default => cdecl::named("void", Constness::Mut), syn::ReturnType::Type(_, ty) => self.translate_type(ty)?, }; if parameters.is_empty() { - parameters.push("void".to_string()); + parameters.push(cdecl::named("void", Constness::Mut)); } - if return_type.contains("(*)") { - let params = parameters.join(", "); - Ok(return_type.replacen("(*)", &format!("(*(*)({params}))"), 1)) - } else { - Ok(format!("{return_type}(*)({})", parameters.join(", "))) - } + Ok(cdecl::func_ptr(parameters, return_type)) } - /// Translate a Rust primitive type into its C equivalent. - fn translate_primitive_type(&self, ty: &syn::Ident) -> String { - match ty.to_string().as_str() { - "usize" => "size_t".to_string(), - "isize" => "ssize_t".to_string(), - "u8" => "uint8_t".to_string(), - "u16" => "uint16_t".to_string(), - "u32" => "uint32_t".to_string(), - "u64" => "uint64_t".to_string(), - "u128" => "unsigned __int128".to_string(), - "i8" => "int8_t".to_string(), - "i16" => "int16_t".to_string(), - "i32" => "int32_t".to_string(), - "i64" => "int64_t".to_string(), - "i128" => "__int128".to_string(), - "f32" => "float".to_string(), - "f64" => "double".to_string(), - "()" => "void".to_string(), - - "c_longdouble" | "c_long_double" => "long double".to_string(), - ty if ty.starts_with("c_") => { - let ty = &ty[2..].replace("long", " long"); - match ty.as_str() { - "short" => "short".to_string(), - s if s.starts_with('u') => format!("unsigned {}", &s[1..]), - s if s.starts_with('s') => format!("signed {}", &s[1..]), - s => s.to_string(), + /// Translate a Rust path into its C equivalent. + fn translate_path(&self, path: &syn::TypePath) -> Result { + let last = path.path.segments.last().unwrap(); + if let syn::PathArguments::AngleBracketed(args) = &last.arguments { + if let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap() { + // Option is ONLY ffi-safe if it contains a function pointer, or a reference. + match inner_ty { + syn::Type::Reference(_) | syn::Type::BareFn(_) => { + return self.translate_type(inner_ty); + } + _ => { + return Err(TranslationError::new( + TranslationErrorKind::NotFfiCompatible, + &path.to_token_stream().to_string(), + inner_ty.span(), + )); + } } } - // Pass typedefs as is. - s => s.to_string(), } - } + let name = last.ident.to_string(); + let item = if self.ffi_items.contains_struct(&name) { + MapInput::StructType(&name) + } else if self.ffi_items.contains_union(&name) { + MapInput::UnionType(&name) + } else { + MapInput::Type(&name) + }; - /// Translate a Rust path into its C equivalent. - fn translate_path(&self, path: &syn::TypePath) -> Result { - let last = path.path.segments.last().unwrap(); - Ok(self.translate_primitive_type(&last.ident)) + Ok(cdecl::named( + &self.generator.rty_to_cty(item), + Constness::Mut, + )) } /// Translate a Rust array declaration into its C equivalent. - fn translate_array(&self, array: &syn::TypeArray) -> Result { - Ok(format!( - "{}[{}]", + fn translate_array(&self, array: &syn::TypeArray) -> Result { + Ok(cdecl::array( self.translate_type(array.elem.deref())?, - translate_expr(&array.len) + Some(&translate_expr(&array.len)), )) } /// Translate a Rust pointer into its equivalent C pointer. - fn translate_ptr(&self, ptr: &syn::TypePtr) -> Result { - let modifier = self.translate_mut(ptr.mutability); - let inner = ptr.elem.deref(); - - match inner { - syn::Type::BareFn(_) => { - let inner_type = self.translate_type(ptr.elem.deref())?; - Ok(inner_type - .replacen("(*)", &format!("(*{modifier})"), 1) - .trim() - .to_string()) - } - syn::Type::Array(arr) => { - let len = translate_expr(&arr.len); - let ty = self.translate_type(arr.elem.deref())?; - let inner_type = format!("{ty} (*) [{len}]"); - Ok(inner_type - .replacen("(*)", &format!("(*{modifier})"), 1) - .trim() - .to_string()) - } - syn::Type::Reference(_) | syn::Type::Ptr(_) => { - let inner_type = self.translate_type(ptr.elem.deref())?; - if inner_type.contains("(*)") { - Ok(inner_type - .replacen("(*)", &format!("(*{modifier} *)"), 1) - .trim() - .to_string()) - } else { - Ok(format!("{inner_type} {modifier}*").trim().to_string()) - } - } - _ => { - let inner_type = self.translate_type(inner)?; - Ok(format!("{inner_type} {modifier}*")) - } - } + fn translate_ptr(&self, ptr: &syn::TypePtr) -> Result { + let inner_type = self.translate_type(ptr.elem.deref())?; + Ok(ptr_with_inner(inner_type, ptr.mutability)) } /// Determine whether a C type is a signed type. @@ -321,14 +269,15 @@ impl Translator { /// For primitive types it checks against a known list of signed types, but for aliases /// which are the only thing other than primitives that can be signed, it recursively checks /// the underlying type of the alias. - pub(crate) fn is_signed(&self, ffi_items: &FfiItems, ty: &syn::Type) -> bool { + pub(crate) fn is_signed(&self, ty: &syn::Type) -> bool { match ty { syn::Type::Path(path) => { let ident = path.path.segments.last().unwrap().ident.clone(); - if let Some(aliased) = ffi_items.aliases().iter().find(|a| ident == a.ident()) { - return self.is_signed(ffi_items, &aliased.ty); + if let Some(aliased) = self.ffi_items.aliases().iter().find(|a| ident == a.ident()) + { + return self.is_signed(&aliased.ty); } - match self.translate_primitive_type(&ident).as_str() { + match translate_primitive_type(&ident.to_string()).as_str() { "char" | "short" | "long" | "long long" | "size_t" | "ssize_t" => true, s => { s.starts_with("int") @@ -342,6 +291,73 @@ impl Translator { } } +/// Translate mutability from Rust to C. +fn translate_mut(mutability: Option) -> Constness { + mutability + .map(|_| Constness::Mut) + .unwrap_or(Constness::Const) +} + +/// Translate a Rust primitive type into its C equivalent. +pub(crate) fn translate_primitive_type(ty: &str) -> String { + match ty { + "usize" => "size_t".to_string(), + "isize" => "ssize_t".to_string(), + "u8" => "uint8_t".to_string(), + "u16" => "uint16_t".to_string(), + "u32" => "uint32_t".to_string(), + "u64" => "uint64_t".to_string(), + "u128" => "unsigned __int128".to_string(), + "i8" => "int8_t".to_string(), + "i16" => "int16_t".to_string(), + "i32" => "int32_t".to_string(), + "i64" => "int64_t".to_string(), + "i128" => "__int128".to_string(), + "f32" => "float".to_string(), + "f64" => "double".to_string(), + "()" => "void".to_string(), + + "c_longdouble" | "c_long_double" => "long double".to_string(), + ty if ty.starts_with("c_") => { + let ty = &ty[2..].replace("long", " long"); + match ty.as_str() { + "short" => "short".to_string(), + s if s.starts_with('u') => format!("unsigned {}", &s[1..]), + s if s.starts_with('s') => format!("signed {}", &s[1..]), + s => s.to_string(), + } + } + // Pass typedefs as is. + s => s.to_string(), + } +} + +/// Construct a CTy and modify the constness of the inner type. +/// +/// Basically, `syn` always gives us the `constness` of the inner type of a pointer. +/// However `cdecl::ptr` wants the `constness` of the pointer. So we just modify +/// the way it is built so that `cdecl::ptr` takes the `constness` of the inner type. +pub(crate) fn ptr_with_inner( + inner: cdecl::CTy, + mutability: Option, +) -> cdecl::CTy { + let constness = translate_mut(mutability); + let mut ty = Box::new(inner); + match ty.deref_mut() { + cdecl::CTy::Named { name: _, qual } => qual.constness = constness, + cdecl::CTy::Ptr { ty: _, qual } => qual.constness = constness, + _ => (), + } + cdecl::CTy::Ptr { + ty, + qual: cdecl::Qual { + constness: Constness::Mut, + volatile: false, + restrict: false, + }, + } +} + /// Translate a simple Rust expression to C. /// /// This function will just pass the expression as is in most cases. @@ -363,13 +379,14 @@ fn is_rust_primitive(ty: &str) -> bool { } /// Translate ABI of a rust extern function to its C equivalent. -pub(crate) fn translate_abi(abi: &syn::Abi, target: &str) -> &'static str { +#[expect(unused)] +pub(crate) fn translate_abi(abi: &syn::Abi, target: &str) -> Option<&'static str> { let abi_name = abi.name.as_ref().map(|lit| lit.value()); match abi_name.as_deref() { - Some("stdcall") => "__stdcall ", - Some("system") if target.contains("i686-pc-windows") => "__stdcall ", - Some("C") | Some("system") | None => "", + Some("stdcall") => "__stdcall ".into(), + Some("system") if target.contains("i686-pc-windows") => "__stdcall ".into(), + Some("C") | Some("system") | None => None, Some(a) => panic!("unknown ABI: {a}"), } } diff --git a/ctest-next/tests/input/macro.out.c b/ctest-next/tests/input/macro.out.c index e9772cc888fff..7035c85a9dad4 100644 --- a/ctest-next/tests/input/macro.out.c +++ b/ctest-next/tests/input/macro.out.c @@ -62,7 +62,7 @@ uint64_t ctest_size_of__VecU16__y(void) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint8_t* ctest_field_ty__VecU8__x; +typedef uint8_t *ctest_field_ty__VecU8__x; ctest_field_ty__VecU8__x ctest_field_ptr__VecU8__x(struct VecU8 *b) { return &b->x; @@ -71,7 +71,7 @@ ctest_field_ptr__VecU8__x(struct VecU8 *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint8_t* ctest_field_ty__VecU8__y; +typedef uint8_t *ctest_field_ty__VecU8__y; ctest_field_ty__VecU8__y ctest_field_ptr__VecU8__y(struct VecU8 *b) { return &b->y; @@ -80,7 +80,7 @@ ctest_field_ptr__VecU8__y(struct VecU8 *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint16_t* ctest_field_ty__VecU16__x; +typedef uint16_t *ctest_field_ty__VecU16__x; ctest_field_ty__VecU16__x ctest_field_ptr__VecU16__x(struct VecU16 *b) { return &b->x; @@ -89,7 +89,7 @@ ctest_field_ptr__VecU16__x(struct VecU16 *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint16_t* ctest_field_ty__VecU16__y; +typedef uint16_t *ctest_field_ty__VecU16__y; ctest_field_ty__VecU16__y ctest_field_ptr__VecU16__y(struct VecU16 *b) { return &b->y; diff --git a/ctest-next/tests/input/simple.out.with-renames.c b/ctest-next/tests/input/simple.out.with-renames.c index 21577c84b4323..2a62d711bfa3a 100644 --- a/ctest-next/tests/input/simple.out.with-renames.c +++ b/ctest-next/tests/input/simple.out.with-renames.c @@ -101,7 +101,7 @@ uint64_t ctest_size_of__Word__byte(void) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef char const* *ctest_field_ty__Person__name; +typedef const char **ctest_field_ty__Person__name; ctest_field_ty__Person__name ctest_field_ptr__Person__name(struct Person *b) { return &b->name; @@ -110,7 +110,7 @@ ctest_field_ptr__Person__name(struct Person *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint8_t* ctest_field_ty__Person__age; +typedef uint8_t *ctest_field_ty__Person__age; ctest_field_ty__Person__age ctest_field_ptr__Person__age(struct Person *b) { return &b->age; @@ -119,7 +119,7 @@ ctest_field_ptr__Person__age(struct Person *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef void (**ctest_field_ty__Person__job)(uint8_t, char const*); +typedef void (**ctest_field_ty__Person__job)(uint8_t, const char *); ctest_field_ty__Person__job ctest_field_ptr__Person__job(struct Person *b) { return &b->job; @@ -128,7 +128,7 @@ ctest_field_ptr__Person__job(struct Person *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint16_t* ctest_field_ty__Word__word; +typedef uint16_t *ctest_field_ty__Word__word; ctest_field_ty__Word__word ctest_field_ptr__Word__word(union Word *b) { return &b->word; diff --git a/ctest-next/tests/input/simple.out.with-skips.c b/ctest-next/tests/input/simple.out.with-skips.c index 15ba758f40ad9..28b80cef4f5b7 100644 --- a/ctest-next/tests/input/simple.out.with-skips.c +++ b/ctest-next/tests/input/simple.out.with-skips.c @@ -93,7 +93,7 @@ uint64_t ctest_size_of__Word__byte(void) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef char const* *ctest_field_ty__Person__name; +typedef const char **ctest_field_ty__Person__name; ctest_field_ty__Person__name ctest_field_ptr__Person__name(struct Person *b) { return &b->name; @@ -102,7 +102,7 @@ ctest_field_ptr__Person__name(struct Person *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint8_t* ctest_field_ty__Person__age; +typedef uint8_t *ctest_field_ty__Person__age; ctest_field_ty__Person__age ctest_field_ptr__Person__age(struct Person *b) { return &b->age; @@ -111,7 +111,7 @@ ctest_field_ptr__Person__age(struct Person *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef void (**ctest_field_ty__Person__job)(uint8_t, char const*); +typedef void (**ctest_field_ty__Person__job)(uint8_t, const char *); ctest_field_ty__Person__job ctest_field_ptr__Person__job(struct Person *b) { return &b->job; @@ -120,7 +120,7 @@ ctest_field_ptr__Person__job(struct Person *b) { // Return a pointer to a struct/union field. // This field can have a normal data type, or it could be a function pointer or an array, which // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. -typedef uint16_t* ctest_field_ty__Word__word; +typedef uint16_t *ctest_field_ty__Word__word; ctest_field_ty__Word__word ctest_field_ptr__Word__word(union Word *b) { return &b->word;