1use 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#[derive(Debug, Error)]
27pub struct TranslationError {
28 #[source]
29 kind: TranslationErrorKind,
30 source: String,
31 span: BoxStr,
32}
33
34impl TranslationError {
35 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#[derive(Debug, Error, PartialEq, Eq)]
65pub(crate) enum TranslationErrorKind {
66 #[error("unsupported type")]
68 UnsupportedType,
69
70 #[error("references to non-primitive types are not allowed")]
72 NonPrimitiveReference,
73
74 #[error("variadics cannot be translated")]
76 HasVariadics,
77
78 #[error("lifetimes cannot be translated")]
80 HasLifetimes,
81
82 #[error(
84 "this type is not guaranteed to have a C compatible layout. See improper_ctypes_definitions lint"
85 )]
86 NotFfiCompatible,
87
88 #[error("invalid return type")]
90 InvalidReturn,
91}
92
93#[derive(Clone)]
94pub(crate) struct Translator<'a> {
96 ffi_items: &'a FfiItems,
97 generator: &'a TestGenerator,
98}
99
100impl<'a> Translator<'a> {
101 pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
103 Self {
104 ffi_items,
105 generator,
106 }
107 }
108
109 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 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 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 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 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 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 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 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 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
306fn translate_mut(mutability: Option<syn::Token![mut]>) -> Constness {
308 mutability
309 .map(|_| Constness::Mut)
310 .unwrap_or(Constness::Const)
311}
312
313pub(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 s => s.to_string(),
344 }
345}
346
347pub(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
373pub(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
412fn 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#[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}