use std::fmt;
use std::ops::{
Deref,
DerefMut,
};
use proc_macro2::Span;
use quote::ToTokens;
use syn::spanned::Spanned;
use thiserror::Error;
use crate::cdecl::Constness;
use crate::ffi_items::FfiItems;
use crate::{
BoxStr,
MapInput,
TestGenerator,
cdecl,
};
#[derive(Debug, Error)]
pub struct TranslationError {
#[source]
kind: TranslationErrorKind,
source: String,
span: BoxStr,
}
impl TranslationError {
pub(crate) fn new(kind: TranslationErrorKind, source: &str, span: Span) -> Self {
Self {
kind,
source: source.to_string(),
span: format!(
"{fname}:{line}:{col}",
fname = span.file(),
line = span.start().line,
col = span.start().column,
)
.into(),
}
}
}
impl From<TranslationError> for askama::Error {
fn from(err: TranslationError) -> Self {
askama::Error::Custom(err.into())
}
}
impl fmt::Display for TranslationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: `{}` at {}", self.kind, self.source, self.span)
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub(crate) enum TranslationErrorKind {
#[error("unsupported type")]
UnsupportedType,
#[error("references to non-primitive types are not allowed")]
NonPrimitiveReference,
#[error("variadics cannot be translated")]
HasVariadics,
#[error("lifetimes cannot be translated")]
HasLifetimes,
#[error(
"this type is not guaranteed to have a C compatible layout. See improper_ctypes_definitions lint"
)]
NotFfiCompatible,
#[error("invalid return type")]
InvalidReturn,
}
#[derive(Clone)]
pub(crate) struct Translator<'a> {
ffi_items: &'a FfiItems,
generator: &'a TestGenerator,
}
impl<'a> Translator<'a> {
pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self {
Self {
ffi_items,
generator,
}
}
pub(crate) fn translate_type(&self, ty: &syn::Type) -> Result<cdecl::CTy, TranslationError> {
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(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(cdecl::named("void", Constness::Mut)),
syn::Type::Slice(slice) => Err(TranslationError::new(
TranslationErrorKind::NotFfiCompatible,
&slice.to_token_stream().to_string(),
slice.span(),
)),
syn::Type::Paren(paren) => self.translate_type(&paren.elem),
syn::Type::Group(group) => self.translate_type(&group.elem),
ty => Err(TranslationError::new(
TranslationErrorKind::UnsupportedType,
&ty.to_token_stream().to_string(),
ty.span(),
)),
}
}
fn translate_reference(
&self,
reference: &syn::TypeReference,
) -> Result<cdecl::CTy, TranslationError> {
match reference.elem.deref() {
syn::Type::Path(path) => {
let last_segment = path.path.segments.last().unwrap();
let ident = last_segment.ident.to_string();
match ident.as_str() {
"str" => {
Err(TranslationError::new(
TranslationErrorKind::NotFfiCompatible,
"&str",
path.span(),
))
}
c if is_rust_primitive(c) => {
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,
&ident,
path.span(),
)),
}
}
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(),
reference.elem.span(),
)),
}
}
pub(crate) fn translate_bare_fn(
&self,
function: &syn::TypeBareFn,
) -> Result<cdecl::CTy, TranslationError> {
if function.lifetimes.is_some() {
return Err(TranslationError::new(
TranslationErrorKind::HasLifetimes,
&function.to_token_stream().to_string(),
function.span(),
));
}
if function.variadic.is_some() {
return Err(TranslationError::new(
TranslationErrorKind::HasVariadics,
&function.to_token_stream().to_string(),
function.span(),
));
}
let mut parameters = function
.inputs
.iter()
.map(|arg| self.translate_type(&arg.ty))
.collect::<Result<Vec<_>, TranslationError>>()?;
let return_type = match &function.output {
syn::ReturnType::Default => cdecl::named("void", Constness::Mut),
syn::ReturnType::Type(_, ty) => self.translate_type(ty)?,
};
if parameters.is_empty() {
parameters.push(cdecl::named("void", Constness::Mut));
}
Ok(cdecl::func_ptr(parameters, return_type))
}
fn translate_path(&self, path: &syn::TypePath) -> Result<cdecl::CTy, TranslationError> {
let last = path.path.segments.last().unwrap();
if let syn::PathArguments::AngleBracketed(args) = &last.arguments
&& let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap()
{
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(),
));
}
}
}
let name = last.ident.to_string();
let item = self.map_rust_name_to_c(&name);
Ok(cdecl::named(
&self.generator.rty_to_cty(item),
Constness::Mut,
))
}
fn translate_array(&self, array: &syn::TypeArray) -> Result<cdecl::CTy, TranslationError> {
Ok(cdecl::array(
self.translate_type(array.elem.deref())?,
Some(&translate_expr(&array.len)),
))
}
fn translate_ptr(&self, ptr: &syn::TypePtr) -> Result<cdecl::CTy, TranslationError> {
let inner_type = self.translate_type(ptr.elem.deref())?;
Ok(ptr_with_inner(inner_type, ptr.mutability))
}
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) = self.ffi_items.aliases().iter().find(|a| ident == a.ident())
{
return self.is_signed(&aliased.ty);
}
match translate_primitive_type(&ident.to_string()).as_str() {
"char" | "short" | "long" | "long long" | "size_t" | "ssize_t" => true,
s => {
s.starts_with("int")
|| s.starts_with("uint") | s.starts_with("signed ")
|| s.starts_with("unsigned ")
}
}
}
_ => false,
}
}
pub(crate) fn map_rust_name_to_c<'name>(&self, name: &'name str) -> MapInput<'name> {
if self.ffi_items.contains_struct(name) {
MapInput::StructType(name)
} else if self.ffi_items.contains_union(name) {
MapInput::UnionType(name)
} else if self.generator.c_enums.iter().any(|f| f(name)) {
MapInput::CEnumType(name)
} else {
MapInput::Type(name)
}
}
}
fn translate_mut(mutability: Option<syn::Token![mut]>) -> Constness {
mutability
.map(|_| Constness::Mut)
.unwrap_or(Constness::Const)
}
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(),
}
}
s => s.to_string(),
}
}
pub(crate) fn ptr_with_inner(
inner: cdecl::CTy,
mutability: Option<syn::Token![mut]>,
) -> 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,
},
}
}
pub(crate) fn translate_expr(expr: &syn::Expr) -> String {
match expr {
syn::Expr::Index(i) => {
let base = translate_expr(&i.expr);
let index = translate_expr(&i.index);
format!("{base}[{index}]")
}
syn::Expr::Lit(l) => match &l.lit {
syn::Lit::Int(i) => {
let suffix = translate_primitive_type(i.suffix());
let val = i.base10_digits().to_string();
if suffix.is_empty() {
val
} else {
format!("({suffix}){val}")
}
}
_ => l.to_token_stream().to_string(),
},
syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
syn::Expr::Cast(c) => {
let val = translate_expr(&c.expr);
let ty = translate_primitive_type(&c.ty.to_token_stream().to_string());
format!("({ty}){val}")
}
syn::Expr::Binary(b) => {
let left = translate_expr(&b.left);
let op = b.op.to_token_stream().to_string();
let right = translate_expr(&b.right);
format!("{left} {op} {right}")
}
expr => expr.to_token_stream().to_string(),
}
}
fn is_rust_primitive(ty: &str) -> bool {
let rustc_types = [
"usize", "u8", "u16", "u32", "u64", "u128", "isize", "i8", "i16", "i32", "i64", "i128",
"f32", "f64",
];
ty.starts_with("c_") || rustc_types.contains(&ty)
}
#[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 ".into(),
Some("system") if target.contains("i686-pc-windows") => "__stdcall ".into(),
Some("C") | Some("system") | None => None,
Some(a) => panic!("unknown ABI: {a}"),
}
}