Skip to main content

ctest/
translator.rs

1//! Translation of Rust types to C for test generation.
2//!
3//! Simple to semi complex types are supported only.
4
5use std::fmt;
6use std::ops::{
7    Deref,
8    DerefMut,
9};
10
11use proc_macro2::Span;
12use quote::ToTokens;
13use syn::spanned::Spanned;
14use thiserror::Error;
15
16use crate::cdecl::Constness;
17use crate::ffi_items::FfiItems;
18use crate::{
19    BoxStr,
20    MapInput,
21    TestGenerator,
22    cdecl,
23};
24
25/// An error that occurs during translation, detailing cause and location.
26#[derive(Debug, Error)]
27pub struct TranslationError {
28    #[source]
29    kind: TranslationErrorKind,
30    source: String,
31    span: BoxStr,
32}
33
34impl TranslationError {
35    /// Create a new translation error.
36    pub(crate) fn new(kind: TranslationErrorKind, source: &str, span: Span) -> Self {
37        Self {
38            kind,
39            source: source.to_string(),
40            span: format!(
41                "{fname}:{line}:{col}",
42                fname = span.file(),
43                line = span.start().line,
44                col = span.start().column,
45            )
46            .into(),
47        }
48    }
49}
50
51impl From<TranslationError> for askama::Error {
52    fn from(err: TranslationError) -> Self {
53        askama::Error::Custom(err.into())
54    }
55}
56
57impl fmt::Display for TranslationError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}: `{}` at {}", self.kind, self.source, self.span)
60    }
61}
62
63/// Errors that can occur during the translation of a type.
64#[derive(Debug, Error, PartialEq, Eq)]
65pub(crate) enum TranslationErrorKind {
66    /// The provided type is unknown or unrecognized.
67    #[error("unsupported type")]
68    UnsupportedType,
69
70    /// A reference to a non-primitive type was encountered, which is not supported.
71    #[error("references to non-primitive types are not allowed")]
72    NonPrimitiveReference,
73
74    /// Variadic functions or parameters were found, which cannot be handled.
75    #[error("variadics cannot be translated")]
76    HasVariadics,
77
78    /// Lifetimes were found in the type or function signature, which are not supported.
79    #[error("lifetimes cannot be translated")]
80    HasLifetimes,
81
82    /// A type that is not ffi compatible was found.
83    #[error(
84        "this type is not guaranteed to have a C compatible layout. See improper_ctypes_definitions lint"
85    )]
86    NotFfiCompatible,
87
88    /// An array or function was attempted to be returned by a function.
89    #[error("invalid return type")]
90    InvalidReturn,
91}
92
93#[derive(Clone)]
94/// A Rust to C/Cxx translator.
95pub(crate) struct Translator<'a> {
96    ffi_items: &'a FfiItems,
97    generator: &'a TestGenerator,
98}
99
100impl<'a> Translator<'a> {
101    /// Create a new translator.
102    pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
103        Self {
104            ffi_items,
105            generator,
106        }
107    }
108
109    /// Translate a Rust type into its equivalent C type.
110    pub(crate) fn translate_type(&self, ty: &syn::Type) -> Result<cdecl::CTy, TranslationError> {
111        match ty {
112            syn::Type::Ptr(ptr) => self.translate_ptr(ptr),
113            syn::Type::Path(path) => self.translate_path(path),
114            syn::Type::Tuple(tuple) if tuple.elems.is_empty() => {
115                Ok(cdecl::named("void", Constness::Mut))
116            }
117            syn::Type::Array(array) => self.translate_array(array),
118            syn::Type::Reference(reference) => self.translate_reference(reference),
119            syn::Type::BareFn(function) => self.translate_bare_fn(function),
120            syn::Type::Never(_) => Ok(cdecl::named("void", Constness::Mut)),
121            syn::Type::Slice(slice) => Err(TranslationError::new(
122                TranslationErrorKind::NotFfiCompatible,
123                &slice.to_token_stream().to_string(),
124                slice.span(),
125            )),
126            syn::Type::Paren(paren) => self.translate_type(&paren.elem),
127            syn::Type::Group(group) => self.translate_type(&group.elem),
128            ty => Err(TranslationError::new(
129                TranslationErrorKind::UnsupportedType,
130                &ty.to_token_stream().to_string(),
131                ty.span(),
132            )),
133        }
134    }
135
136    /// Translate a Rust reference to its C equivalent.
137    fn translate_reference(
138        &self,
139        reference: &syn::TypeReference,
140    ) -> Result<cdecl::CTy, TranslationError> {
141        match reference.elem.deref() {
142            syn::Type::Path(path) => {
143                let last_segment = path.path.segments.last().unwrap();
144                let ident = last_segment.ident.to_string();
145
146                match ident.as_str() {
147                    "str" => {
148                        // &str is not ABI safe and should not be supported.
149                        Err(TranslationError::new(
150                            TranslationErrorKind::NotFfiCompatible,
151                            "&str",
152                            path.span(),
153                        ))
154                    }
155                    c if is_rust_primitive(c) => {
156                        let type_name = translate_primitive_type(&last_segment.ident.to_string());
157                        Ok(ptr_with_inner(
158                            cdecl::named(&type_name, Constness::Mut),
159                            reference.mutability,
160                        ))
161                    }
162                    _ => Err(TranslationError::new(
163                        TranslationErrorKind::NonPrimitiveReference,
164                        &ident,
165                        path.span(),
166                    )),
167                }
168            }
169            syn::Type::Reference(_)
170            | syn::Type::Ptr(_)
171            | syn::Type::Array(_)
172            | syn::Type::BareFn(_) => {
173                let ty = self.translate_type(reference.elem.deref())?;
174                Ok(ptr_with_inner(ty, reference.mutability))
175            }
176
177            _ => Err(TranslationError::new(
178                TranslationErrorKind::UnsupportedType,
179                &reference.elem.to_token_stream().to_string(),
180                reference.elem.span(),
181            )),
182        }
183    }
184
185    /// Translate a Rust function pointer type to its C equivalent.
186    pub(crate) fn translate_bare_fn(
187        &self,
188        function: &syn::TypeBareFn,
189    ) -> Result<cdecl::CTy, TranslationError> {
190        if function.lifetimes.is_some() {
191            return Err(TranslationError::new(
192                TranslationErrorKind::HasLifetimes,
193                &function.to_token_stream().to_string(),
194                function.span(),
195            ));
196        }
197        if function.variadic.is_some() {
198            return Err(TranslationError::new(
199                TranslationErrorKind::HasVariadics,
200                &function.to_token_stream().to_string(),
201                function.span(),
202            ));
203        }
204
205        let mut parameters = function
206            .inputs
207            .iter()
208            .map(|arg| self.translate_type(&arg.ty))
209            .collect::<Result<Vec<_>, TranslationError>>()?;
210
211        let return_type = match &function.output {
212            syn::ReturnType::Default => cdecl::named("void", Constness::Mut),
213            syn::ReturnType::Type(_, ty) => self.translate_type(ty)?,
214        };
215
216        if parameters.is_empty() {
217            parameters.push(cdecl::named("void", Constness::Mut));
218        }
219
220        Ok(cdecl::func_ptr(parameters, return_type))
221    }
222
223    /// Translate a Rust path into its C equivalent.
224    fn translate_path(&self, path: &syn::TypePath) -> Result<cdecl::CTy, TranslationError> {
225        let last = path.path.segments.last().unwrap();
226        if let syn::PathArguments::AngleBracketed(args) = &last.arguments
227            && let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap()
228        {
229            // Option<T> is ONLY ffi-safe if it contains a function pointer, or a reference.
230            match inner_ty {
231                syn::Type::Reference(_) | syn::Type::BareFn(_) => {
232                    return self.translate_type(inner_ty);
233                }
234                _ => {
235                    return Err(TranslationError::new(
236                        TranslationErrorKind::NotFfiCompatible,
237                        &path.to_token_stream().to_string(),
238                        inner_ty.span(),
239                    ));
240                }
241            }
242        }
243
244        let name = last.ident.to_string();
245        let item = self.map_rust_name_to_c(&name);
246
247        Ok(cdecl::named(
248            &self.generator.rty_to_cty(item),
249            Constness::Mut,
250        ))
251    }
252
253    /// Translate a Rust array declaration into its C equivalent.
254    fn translate_array(&self, array: &syn::TypeArray) -> Result<cdecl::CTy, TranslationError> {
255        Ok(cdecl::array(
256            self.translate_type(array.elem.deref())?,
257            Some(&translate_expr(&array.len)),
258        ))
259    }
260
261    /// Translate a Rust pointer into its equivalent C pointer.
262    fn translate_ptr(&self, ptr: &syn::TypePtr) -> Result<cdecl::CTy, TranslationError> {
263        let inner_type = self.translate_type(ptr.elem.deref())?;
264        Ok(ptr_with_inner(inner_type, ptr.mutability))
265    }
266
267    /// Determine whether a C type is a signed type.
268    ///
269    /// For primitive types it checks against a known list of signed types, but for aliases
270    /// which are the only thing other than primitives that can be signed, it recursively checks
271    /// the underlying type of the alias.
272    pub(crate) fn is_signed(&self, ty: &syn::Type) -> bool {
273        match ty {
274            syn::Type::Path(path) => {
275                let ident = path.path.segments.last().unwrap().ident.clone();
276                if let Some(aliased) = self.ffi_items.aliases().iter().find(|a| ident == a.ident())
277                {
278                    return self.is_signed(&aliased.ty);
279                }
280                match translate_primitive_type(&ident.to_string()).as_str() {
281                    "char" | "short" | "long" | "long long" | "size_t" | "ssize_t" => true,
282                    s => {
283                        s.starts_with("int")
284                            || s.starts_with("uint") | s.starts_with("signed ")
285                            || s.starts_with("unsigned ")
286                    }
287                }
288            }
289            _ => false,
290        }
291    }
292
293    pub(crate) fn map_rust_name_to_c<'name>(&self, name: &'name str) -> MapInput<'name> {
294        if self.ffi_items.contains_struct(name) {
295            MapInput::StructType(name)
296        } else if self.ffi_items.contains_union(name) {
297            MapInput::UnionType(name)
298        } else if self.generator.c_enums.iter().any(|f| f(name)) {
299            MapInput::CEnumType(name)
300        } else {
301            MapInput::Type(name)
302        }
303    }
304}
305
306/// Translate mutability from Rust to C.
307fn translate_mut(mutability: Option<syn::Token![mut]>) -> Constness {
308    mutability
309        .map(|_| Constness::Mut)
310        .unwrap_or(Constness::Const)
311}
312
313/// Translate a Rust primitive type into its C equivalent.
314pub(crate) fn translate_primitive_type(ty: &str) -> String {
315    match ty {
316        "usize" => "size_t".to_string(),
317        "isize" => "ssize_t".to_string(),
318        "u8" => "uint8_t".to_string(),
319        "u16" => "uint16_t".to_string(),
320        "u32" => "uint32_t".to_string(),
321        "u64" => "uint64_t".to_string(),
322        "u128" => "unsigned __int128".to_string(),
323        "i8" => "int8_t".to_string(),
324        "i16" => "int16_t".to_string(),
325        "i32" => "int32_t".to_string(),
326        "i64" => "int64_t".to_string(),
327        "i128" => "__int128".to_string(),
328        "f32" => "float".to_string(),
329        "f64" => "double".to_string(),
330        "()" => "void".to_string(),
331
332        "c_longdouble" | "c_long_double" => "long double".to_string(),
333        ty if ty.starts_with("c_") => {
334            let ty = &ty[2..].replace("long", " long");
335            match ty.as_str() {
336                "short" => "short".to_string(),
337                s if s.starts_with('u') => format!("unsigned {}", &s[1..]),
338                s if s.starts_with('s') => format!("signed {}", &s[1..]),
339                s => s.to_string(),
340            }
341        }
342        // Pass typedefs as is.
343        s => s.to_string(),
344    }
345}
346
347/// Construct a CTy and modify the constness of the inner type.
348///
349/// Basically, `syn` always gives us the `constness` of the inner type of a pointer.
350/// However `cdecl::ptr` wants the `constness` of the pointer. So we just modify
351/// the way it is built so that `cdecl::ptr` takes the `constness` of the inner type.
352pub(crate) fn ptr_with_inner(
353    inner: cdecl::CTy,
354    mutability: Option<syn::Token![mut]>,
355) -> cdecl::CTy {
356    let constness = translate_mut(mutability);
357    let mut ty = Box::new(inner);
358    match ty.deref_mut() {
359        cdecl::CTy::Named { name: _, qual } => qual.constness = constness,
360        cdecl::CTy::Ptr { ty: _, qual } => qual.constness = constness,
361        _ => (),
362    }
363    cdecl::CTy::Ptr {
364        ty,
365        qual: cdecl::Qual {
366            constness: Constness::Mut,
367            volatile: false,
368            restrict: false,
369        },
370    }
371}
372
373/// Translate a simple Rust expression to C.
374///
375/// This function will just pass the expression as is in most cases. In more complex cases it can
376/// convert `Type as u8 + 5` to `(uint8_t)CType + 5`.
377pub(crate) fn translate_expr(expr: &syn::Expr) -> String {
378    match expr {
379        syn::Expr::Index(i) => {
380            let base = translate_expr(&i.expr);
381            let index = translate_expr(&i.index);
382            format!("{base}[{index}]")
383        }
384        syn::Expr::Lit(l) => match &l.lit {
385            syn::Lit::Int(i) => {
386                let suffix = translate_primitive_type(i.suffix());
387                let val = i.base10_digits().to_string();
388                if suffix.is_empty() {
389                    val
390                } else {
391                    format!("({suffix}){val}")
392                }
393            }
394            _ => l.to_token_stream().to_string(),
395        },
396        syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
397        syn::Expr::Cast(c) => {
398            let val = translate_expr(&c.expr);
399            let ty = translate_primitive_type(&c.ty.to_token_stream().to_string());
400            format!("({ty}){val}")
401        }
402        syn::Expr::Binary(b) => {
403            let left = translate_expr(&b.left);
404            let op = b.op.to_token_stream().to_string();
405            let right = translate_expr(&b.right);
406            format!("{left} {op} {right}")
407        }
408        expr => expr.to_token_stream().to_string(),
409    }
410}
411
412/// Return whether a type is a Rust primitive type.
413fn is_rust_primitive(ty: &str) -> bool {
414    let rustc_types = [
415        "usize", "u8", "u16", "u32", "u64", "u128", "isize", "i8", "i16", "i32", "i64", "i128",
416        "f32", "f64",
417    ];
418    ty.starts_with("c_") || rustc_types.contains(&ty)
419}
420
421/// Translate ABI of a rust extern function to its C equivalent.
422#[expect(unused)]
423pub(crate) fn translate_abi(abi: &syn::Abi, target: &str) -> Option<&'static str> {
424    let abi_name = abi.name.as_ref().map(|lit| lit.value());
425
426    match abi_name.as_deref() {
427        Some("stdcall") => "__stdcall ".into(),
428        Some("system") if target.contains("i686-pc-windows") => "__stdcall ".into(),
429        Some("C") | Some("system") | None => None,
430        Some(a) => panic!("unknown ABI: {a}"),
431    }
432}