use std::fmt;
use quote::ToTokens;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IntSize {
Char,
Short,
Int,
Long,
LongLong,
Int128,
}
#[derive(Debug, Clone)]
pub enum TypeSource {
Apidoc { raw: String, entry_name: String },
Bindings { raw: String },
Inferred,
Parsed,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum UnifiedType {
Void,
Bool,
Char { signed: Option<bool> },
Int { signed: bool, size: IntSize },
Float,
Double,
LongDouble,
Pointer {
inner: Box<UnifiedType>,
is_const: bool,
},
Array {
inner: Box<UnifiedType>,
size: Option<usize>,
},
Named(String),
FnPtr {
params: Vec<UnifiedType>,
ret: Box<UnifiedType>,
abi: Option<String>,
is_unsafe: bool,
is_optional: bool,
},
Verbatim(String),
Unknown,
}
#[derive(Debug, Clone)]
pub struct SourcedType {
pub ty: UnifiedType,
pub source: TypeSource,
}
impl UnifiedType {
pub fn from_c_str(s: &str) -> Self {
let trimmed = s.trim();
if trimmed.is_empty() {
return Self::Unknown;
}
if trimmed == "void" {
return Self::Void;
}
if trimmed == "bool" || trimmed == "_Bool" {
return Self::Bool;
}
if let Some(ptr_type) = Self::parse_c_pointer(trimmed) {
return ptr_type;
}
Self::parse_c_basic_type(trimmed)
}
fn parse_c_pointer(s: &str) -> Option<Self> {
let s = s.trim();
if let Some(star_pos) = s.rfind('*') {
let before_star = s[..star_pos].trim();
let after_star = s[star_pos + 1..].trim();
let _ptr_const = after_star == "const";
let (is_const, base_type) = if before_star.starts_with("const ") {
(true, before_star[6..].trim())
} else if before_star.ends_with(" const") {
(true, before_star[..before_star.len() - 6].trim())
} else {
(false, before_star)
};
let inner_type = if base_type.contains('*') {
Self::parse_c_pointer(base_type).unwrap_or_else(|| Self::parse_c_basic_type(base_type))
} else {
Self::parse_c_basic_type(base_type)
};
return Some(Self::Pointer {
inner: Box::new(inner_type),
is_const,
});
}
None
}
fn parse_c_basic_type(s: &str) -> Self {
let s = s.trim();
let s = s.strip_prefix("const ").unwrap_or(s);
let s = if s.ends_with(" const") {
&s[..s.len() - 6]
} else {
s
};
let s = s.trim();
let s = s
.strip_prefix("struct ")
.or_else(|| s.strip_prefix("union "))
.or_else(|| s.strip_prefix("enum "))
.unwrap_or(s);
let (is_unsigned, base) = if s == "unsigned" {
(true, "")
} else if s == "signed" {
(false, "")
} else if s.starts_with("unsigned ") {
(true, s[9..].trim())
} else if s.starts_with("signed ") {
(false, s[7..].trim())
} else {
(false, s)
};
match base {
"char" if is_unsigned => Self::Char { signed: Some(false) },
"char" => Self::Char { signed: None },
"short" | "short int" => Self::Int {
signed: !is_unsigned,
size: IntSize::Short,
},
"int" | "" => Self::Int {
signed: !is_unsigned,
size: IntSize::Int,
},
"long" | "long int" => Self::Int {
signed: !is_unsigned,
size: IntSize::Long,
},
"long long" | "long long int" => Self::Int {
signed: !is_unsigned,
size: IntSize::LongLong,
},
"__int128" | "__int128_t" => Self::Int {
signed: !is_unsigned,
size: IntSize::Int128,
},
"float" => Self::Float,
"double" => Self::Double,
"long double" => Self::LongDouble,
"void" => Self::Void,
"bool" | "_Bool" => Self::Bool,
"size_t" | "ssize_t" | "ptrdiff_t" => Self::Named(base.to_string()),
_ => {
if is_unsigned {
Self::Named(format!("unsigned {}", base))
} else {
Self::Named(base.to_string())
}
}
}
}
pub fn from_rust_str(s: &str) -> Self {
let normalized = s
.replace("* mut", "*mut")
.replace("* const", "*const")
.replace(" :: ", "::");
let trimmed = normalized.trim();
if trimmed.is_empty() {
return Self::Unknown;
}
if let Some(rest) = trimmed.strip_prefix("*mut ") {
return Self::Pointer {
inner: Box::new(Self::from_rust_str(rest)),
is_const: false,
};
}
if let Some(rest) = trimmed.strip_prefix("*const ") {
return Self::Pointer {
inner: Box::new(Self::from_rust_str(rest)),
is_const: true,
};
}
if let Some(rest) = trimmed.strip_prefix("*mut") {
return Self::Pointer {
inner: Box::new(Self::from_rust_str(rest.trim())),
is_const: false,
};
}
if let Some(rest) = trimmed.strip_prefix("*const") {
return Self::Pointer {
inner: Box::new(Self::from_rust_str(rest.trim())),
is_const: true,
};
}
if trimmed.starts_with('[') && trimmed.ends_with(']') {
let inner = &trimmed[1..trimmed.len() - 1];
let mut depth = 0i32;
let mut semi_pos: Option<usize> = None;
for (i, ch) in inner.char_indices() {
match ch {
'[' | '(' | '<' => depth += 1,
']' | ')' | '>' => depth -= 1,
';' if depth == 0 => {
semi_pos = Some(i);
break;
}
_ => {}
}
}
if let Some(pos) = semi_pos {
let elem = inner[..pos].trim();
let size_str = inner[pos + 1..].trim();
let size = size_str
.split_whitespace()
.next()
.and_then(|s| s.trim_end_matches("usize").parse::<usize>().ok());
return Self::Array {
inner: Box::new(Self::from_rust_str(elem)),
size,
};
}
}
Self::parse_rust_basic_type(trimmed)
}
pub fn from_syn_type(ty: &syn::Type) -> Self {
match ty {
syn::Type::Tuple(t) if t.elems.is_empty() => Self::Void,
syn::Type::Group(g) => Self::from_syn_type(&g.elem),
syn::Type::Paren(p) => Self::from_syn_type(&p.elem),
syn::Type::Ptr(p) => Self::Pointer {
inner: Box::new(Self::from_syn_type(&p.elem)),
is_const: p.const_token.is_some(),
},
syn::Type::Array(a) => Self::Array {
inner: Box::new(Self::from_syn_type(&a.elem)),
size: extract_array_size(&a.len),
},
syn::Type::BareFn(f) => from_bare_fn(f, false),
syn::Type::Path(tp) => from_type_path(tp),
_ => verbatim_of(ty),
}
}
fn parse_rust_basic_type(s: &str) -> Self {
let s = s
.strip_prefix("::")
.map(|s| s.trim_start())
.unwrap_or(s);
let s = s
.strip_prefix("std::")
.unwrap_or(s);
let s = s
.strip_prefix("ffi::")
.or_else(|| s.strip_prefix("os::raw::"))
.unwrap_or(s);
match s {
"()" => Self::Void,
"c_void" => Self::Void,
"bool" => Self::Bool,
"c_char" => Self::Char { signed: None },
"c_schar" => Self::Char { signed: Some(true) },
"c_uchar" => Self::Char { signed: Some(false) },
"c_short" => Self::Int { signed: true, size: IntSize::Short },
"c_ushort" => Self::Int { signed: false, size: IntSize::Short },
"c_int" => Self::Int { signed: true, size: IntSize::Int },
"c_uint" => Self::Int { signed: false, size: IntSize::Int },
"c_long" => Self::Int { signed: true, size: IntSize::Long },
"c_ulong" => Self::Int { signed: false, size: IntSize::Long },
"c_longlong" => Self::Int { signed: true, size: IntSize::LongLong },
"c_ulonglong" => Self::Int { signed: false, size: IntSize::LongLong },
"i8" => Self::Char { signed: Some(true) },
"u8" => Self::Char { signed: Some(false) },
"i16" => Self::Int { signed: true, size: IntSize::Short },
"u16" => Self::Int { signed: false, size: IntSize::Short },
"i32" => Self::Int { signed: true, size: IntSize::Int },
"u32" => Self::Int { signed: false, size: IntSize::Int },
"i64" => Self::Int { signed: true, size: IntSize::LongLong },
"u64" => Self::Int { signed: false, size: IntSize::LongLong },
"i128" => Self::Int { signed: true, size: IntSize::Int128 },
"u128" => Self::Int { signed: false, size: IntSize::Int128 },
"isize" => Self::Named("isize".to_string()),
"usize" => Self::Named("usize".to_string()),
"c_float" | "f32" => Self::Float,
"c_double" | "f64" => Self::Double,
_ => Self::Named(s.to_string()),
}
}
pub fn to_rust_string(&self) -> String {
match self {
Self::Void => "()".to_string(),
Self::Bool => "bool".to_string(),
Self::Char { signed: None } => "c_char".to_string(),
Self::Char { signed: Some(true) } => "c_schar".to_string(),
Self::Char { signed: Some(false) } => "c_uchar".to_string(),
Self::Int { signed, size } => {
match (signed, size) {
(true, IntSize::Char) => "c_schar".to_string(),
(false, IntSize::Char) => "c_uchar".to_string(),
(true, IntSize::Short) => "c_short".to_string(),
(false, IntSize::Short) => "c_ushort".to_string(),
(true, IntSize::Int) => "c_int".to_string(),
(false, IntSize::Int) => "c_uint".to_string(),
(true, IntSize::Long) => "c_long".to_string(),
(false, IntSize::Long) => "c_ulong".to_string(),
(true, IntSize::LongLong) => "c_longlong".to_string(),
(false, IntSize::LongLong) => "c_ulonglong".to_string(),
(true, IntSize::Int128) => "i128".to_string(),
(false, IntSize::Int128) => "u128".to_string(),
}
}
Self::Float => "c_float".to_string(),
Self::Double => "c_double".to_string(),
Self::LongDouble => "c_double".to_string(),
Self::Pointer { inner, is_const } => {
let inner_str = if matches!(inner.as_ref(), Self::Void) {
"c_void".to_string()
} else {
inner.to_rust_string()
};
if *is_const {
format!("*const {}", inner_str)
} else {
format!("*mut {}", inner_str)
}
}
Self::Array { inner, size } => {
let inner_str = inner.to_rust_string();
match size {
Some(n) => format!("[{}; {}]", inner_str, n),
None => format!("[{}]", inner_str),
}
}
Self::Named(name) => {
match name.as_str() {
"size_t" => "usize".to_string(),
"ssize_t" | "ptrdiff_t" => "isize".to_string(),
"off_t" | "off64_t" => "i64".to_string(),
_ => name.clone(),
}
}
Self::FnPtr { params, ret, abi, is_unsafe, is_optional } => {
let mut s = String::new();
if *is_unsafe {
s.push_str("unsafe ");
}
if let Some(abi_name) = abi {
s.push_str(&format!("extern {:?} ", abi_name));
}
s.push_str("fn(");
let param_strs: Vec<String> = params.iter().map(|p| p.to_rust_string()).collect();
s.push_str(¶m_strs.join(", "));
s.push(')');
let ret_str = ret.to_rust_string();
if ret_str != "()" {
s.push_str(" -> ");
s.push_str(&ret_str);
}
if *is_optional {
format!("Option<{}>", s)
} else {
s
}
}
Self::Verbatim(s) => s.clone(),
Self::Unknown => "UnknownType".to_string(),
}
}
pub fn equals_ignoring_const(&self, other: &Self) -> bool {
match (self, other) {
(Self::Void, Self::Void) => true,
(Self::Bool, Self::Bool) => true,
(Self::Char { signed: s1 }, Self::Char { signed: s2 }) => s1 == s2,
(
Self::Int { signed: s1, size: sz1 },
Self::Int { signed: s2, size: sz2 },
) => s1 == s2 && sz1 == sz2,
(Self::Float, Self::Float) => true,
(Self::Double, Self::Double) => true,
(Self::LongDouble, Self::LongDouble) => true,
(
Self::Pointer { inner: i1, .. },
Self::Pointer { inner: i2, .. },
) => i1.equals_ignoring_const(i2),
(
Self::Array { inner: i1, size: s1 },
Self::Array { inner: i2, size: s2 },
) => s1 == s2 && i1.equals_ignoring_const(i2),
(Self::Named(n1), Self::Named(n2)) => n1 == n2,
(Self::Unknown, Self::Unknown) => true,
_ => false,
}
}
pub fn equals_ignoring_case(&self, other: &Self) -> bool {
match (self, other) {
(Self::Named(n1), Self::Named(n2)) => n1.eq_ignore_ascii_case(n2),
(
Self::Pointer { inner: i1, is_const: c1 },
Self::Pointer { inner: i2, is_const: c2 },
) => c1 == c2 && i1.equals_ignoring_case(i2),
(
Self::Array { inner: i1, size: s1 },
Self::Array { inner: i2, size: s2 },
) => s1 == s2 && i1.equals_ignoring_case(i2),
_ => self == other,
}
}
pub fn is_pointer(&self) -> bool {
matches!(self, Self::Pointer { .. })
}
pub fn is_void_pointer(&self) -> bool {
match self {
Self::Pointer { inner, .. } => {
matches!(**inner, Self::Void) || matches!(**inner, Self::Named(ref n) if n == "c_void")
}
_ => false,
}
}
pub fn is_concrete_pointer(&self) -> bool {
self.is_pointer() && !self.is_void_pointer()
}
pub fn is_const_pointer(&self) -> bool {
matches!(self, Self::Pointer { is_const: true, .. })
}
pub fn is_float(&self) -> bool {
matches!(self, Self::Float | Self::Double | Self::LongDouble)
|| matches!(self, Self::Named(n) if n == "NV")
}
pub fn is_bool(&self) -> bool {
matches!(self, Self::Bool)
}
pub fn is_void(&self) -> bool {
matches!(self, Self::Void)
}
pub fn is_named(&self) -> bool {
matches!(self, Self::Named(_))
}
pub fn as_named(&self) -> Option<&str> {
match self {
Self::Named(name) => Some(name),
_ => None,
}
}
pub fn inner_type(&self) -> Option<&UnifiedType> {
match self {
Self::Pointer { inner, .. } => Some(inner),
Self::Array { inner, .. } => Some(inner),
_ => None,
}
}
pub fn is_fn_ptr(&self) -> bool {
matches!(self, Self::FnPtr { .. })
}
pub fn is_optional_fn_ptr(&self) -> bool {
matches!(self, Self::FnPtr { is_optional: true, .. })
}
pub fn is_verbatim(&self) -> bool {
matches!(self, Self::Verbatim(_))
}
}
fn extract_array_size(expr: &syn::Expr) -> Option<usize> {
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(li), .. }) = expr {
li.base10_parse::<usize>().ok()
} else {
None
}
}
fn from_bare_fn(f: &syn::TypeBareFn, is_optional: bool) -> UnifiedType {
let abi = f.abi.as_ref().map(|abi| {
abi.name
.as_ref()
.map(|n| n.value())
.unwrap_or_else(|| "C".to_string())
});
let params: Vec<UnifiedType> = f
.inputs
.iter()
.map(|arg| UnifiedType::from_syn_type(&arg.ty))
.collect();
let ret = match &f.output {
syn::ReturnType::Default => UnifiedType::Void,
syn::ReturnType::Type(_, t) => UnifiedType::from_syn_type(t),
};
UnifiedType::FnPtr {
params,
ret: Box::new(ret),
abi,
is_unsafe: f.unsafety.is_some(),
is_optional,
}
}
fn from_type_path(tp: &syn::TypePath) -> UnifiedType {
if tp.qself.is_some() {
return verbatim_of(&syn::Type::Path(tp.clone()));
}
let path = &tp.path;
if let Some(last) = path.segments.last() {
if last.ident == "Option" {
if let syn::PathArguments::AngleBracketed(ab) = &last.arguments {
if let Some(syn::GenericArgument::Type(inner)) = ab.args.first() {
return from_optional_inner(inner);
}
}
}
}
if let Some(ident) = single_ident_of(path) {
if let Some(prim) = primitive_from_ident(&ident) {
return prim;
}
return UnifiedType::Named(ident);
}
verbatim_of(&syn::Type::Path(tp.clone()))
}
fn from_optional_inner(inner: &syn::Type) -> UnifiedType {
match inner {
syn::Type::BareFn(f) => from_bare_fn(f, true),
syn::Type::Group(g) => from_optional_inner(&g.elem),
syn::Type::Paren(p) => from_optional_inner(&p.elem),
_ => {
let wrapped: syn::TypePath = syn::parse_quote!(::std::option::Option<#inner>);
verbatim_of(&syn::Type::Path(wrapped))
}
}
}
fn single_ident_of(path: &syn::Path) -> Option<String> {
let last = path.segments.last()?;
if !matches!(last.arguments, syn::PathArguments::None) {
return None;
}
Some(last.ident.to_string())
}
fn primitive_from_ident(name: &str) -> Option<UnifiedType> {
Some(match name {
"c_void" => UnifiedType::Void,
"bool" => UnifiedType::Bool,
"c_char" => UnifiedType::Char { signed: None },
"c_schar" => UnifiedType::Char { signed: Some(true) },
"c_uchar" => UnifiedType::Char { signed: Some(false) },
"c_short" => UnifiedType::Int { signed: true, size: IntSize::Short },
"c_ushort" => UnifiedType::Int { signed: false, size: IntSize::Short },
"c_int" => UnifiedType::Int { signed: true, size: IntSize::Int },
"c_uint" => UnifiedType::Int { signed: false, size: IntSize::Int },
"c_long" => UnifiedType::Int { signed: true, size: IntSize::Long },
"c_ulong" => UnifiedType::Int { signed: false, size: IntSize::Long },
"c_longlong" => UnifiedType::Int { signed: true, size: IntSize::LongLong },
"c_ulonglong" => UnifiedType::Int { signed: false, size: IntSize::LongLong },
"i8" => UnifiedType::Char { signed: Some(true) },
"u8" => UnifiedType::Char { signed: Some(false) },
"i16" => UnifiedType::Int { signed: true, size: IntSize::Short },
"u16" => UnifiedType::Int { signed: false, size: IntSize::Short },
"i32" => UnifiedType::Int { signed: true, size: IntSize::Int },
"u32" => UnifiedType::Int { signed: false, size: IntSize::Int },
"i64" => UnifiedType::Int { signed: true, size: IntSize::LongLong },
"u64" => UnifiedType::Int { signed: false, size: IntSize::LongLong },
"i128" => UnifiedType::Int { signed: true, size: IntSize::Int128 },
"u128" => UnifiedType::Int { signed: false, size: IntSize::Int128 },
"isize" | "usize" => UnifiedType::Named(name.to_string()),
"c_float" | "f32" => UnifiedType::Float,
"c_double" | "f64" => UnifiedType::Double,
_ => return None,
})
}
fn verbatim_of(ty: &syn::Type) -> UnifiedType {
UnifiedType::Verbatim(ty.to_token_stream().to_string())
}
impl fmt::Display for UnifiedType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_rust_string())
}
}
impl SourcedType {
pub fn new(ty: UnifiedType, source: TypeSource) -> Self {
Self { ty, source }
}
pub fn from_apidoc(raw: &str, entry_name: &str) -> Self {
Self {
ty: UnifiedType::from_c_str(raw),
source: TypeSource::Apidoc {
raw: raw.to_string(),
entry_name: entry_name.to_string(),
},
}
}
pub fn from_bindings(raw: &str) -> Self {
Self {
ty: UnifiedType::from_rust_str(raw),
source: TypeSource::Bindings {
raw: raw.to_string(),
},
}
}
pub fn raw_string(&self) -> Option<&str> {
match &self.source {
TypeSource::Apidoc { raw, .. } => Some(raw),
TypeSource::Bindings { raw } => Some(raw),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_c_str_basic_types() {
assert_eq!(UnifiedType::from_c_str("void"), UnifiedType::Void);
assert_eq!(UnifiedType::from_c_str("bool"), UnifiedType::Bool);
assert_eq!(
UnifiedType::from_c_str("int"),
UnifiedType::Int { signed: true, size: IntSize::Int }
);
assert_eq!(
UnifiedType::from_c_str("unsigned int"),
UnifiedType::Int { signed: false, size: IntSize::Int }
);
assert_eq!(
UnifiedType::from_c_str("long long"),
UnifiedType::Int { signed: true, size: IntSize::LongLong }
);
}
#[test]
fn test_from_c_str_pointer() {
assert_eq!(
UnifiedType::from_c_str("SV *"),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}
);
assert_eq!(
UnifiedType::from_c_str("const char *"),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Char { signed: None }),
is_const: true,
}
);
}
#[test]
fn test_from_c_str_double_pointer() {
let ty = UnifiedType::from_c_str("SV **");
assert_eq!(
ty,
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}),
is_const: false,
}
);
}
#[test]
fn test_from_rust_str_basic_types() {
assert_eq!(UnifiedType::from_rust_str("()"), UnifiedType::Void);
assert_eq!(UnifiedType::from_rust_str("bool"), UnifiedType::Bool);
assert_eq!(
UnifiedType::from_rust_str("c_int"),
UnifiedType::Int { signed: true, size: IntSize::Int }
);
assert_eq!(
UnifiedType::from_rust_str("c_uint"),
UnifiedType::Int { signed: false, size: IntSize::Int }
);
}
#[test]
fn test_from_rust_str_pointer() {
assert_eq!(
UnifiedType::from_rust_str("*mut SV"),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}
);
assert_eq!(
UnifiedType::from_rust_str("*const c_char"),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Char { signed: None }),
is_const: true,
}
);
}
#[test]
fn test_from_rust_str_double_pointer() {
let ty = UnifiedType::from_rust_str("*mut *mut SV");
assert_eq!(
ty,
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}),
is_const: false,
}
);
}
#[test]
fn test_from_rust_str_with_spaces() {
let ty = UnifiedType::from_rust_str("* mut * mut SV");
assert_eq!(
ty,
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}),
is_const: false,
}
);
}
#[test]
fn test_to_rust_string() {
assert_eq!(UnifiedType::Void.to_rust_string(), "()");
assert_eq!(
UnifiedType::Int { signed: true, size: IntSize::Int }.to_rust_string(),
"c_int"
);
assert_eq!(
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}.to_rust_string(),
"*mut SV"
);
assert_eq!(
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Char { signed: None }),
is_const: true,
}.to_rust_string(),
"*const c_char"
);
}
#[test]
fn test_roundtrip_c_to_rust() {
let cases = [
("void", "()"),
("int", "c_int"),
("unsigned int", "c_uint"),
("SV *", "*mut SV"),
("const char *", "*const c_char"),
("SV **", "*mut *mut SV"),
];
for (c_type, expected_rust) in cases {
let ty = UnifiedType::from_c_str(c_type);
assert_eq!(ty.to_rust_string(), expected_rust, "C type: {}", c_type);
}
}
#[test]
fn test_roundtrip_rust() {
let cases = [
"()",
"bool",
"c_int",
"c_uint",
"*mut SV",
"*const c_char",
"*mut *mut SV",
];
for rust_type in cases {
let ty = UnifiedType::from_rust_str(rust_type);
assert_eq!(ty.to_rust_string(), rust_type, "Rust type: {}", rust_type);
}
}
#[test]
fn test_equals_ignoring_const() {
let mut_sv = UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
};
let const_sv = UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: true,
};
assert!(mut_sv.equals_ignoring_const(&const_sv));
assert!(!mut_sv.eq(&const_sv)); }
#[test]
fn test_equals_ignoring_case() {
let sv_upper = UnifiedType::Named("SV".to_string());
let sv_lower = UnifiedType::Named("sv".to_string());
assert!(sv_upper.equals_ignoring_case(&sv_lower));
assert!(!sv_upper.eq(&sv_lower));
let ptr_sv_upper = UnifiedType::Pointer {
inner: Box::new(sv_upper),
is_const: false,
};
let ptr_sv_lower = UnifiedType::Pointer {
inner: Box::new(sv_lower),
is_const: false,
};
assert!(ptr_sv_upper.equals_ignoring_case(&ptr_sv_lower));
}
#[test]
fn test_const_value_type() {
assert_eq!(
UnifiedType::from_c_str("const U32"),
UnifiedType::Named("U32".to_string())
);
assert_eq!(
UnifiedType::from_c_str("const STRLEN"),
UnifiedType::Named("STRLEN".to_string())
);
assert_eq!(
UnifiedType::from_c_str("const int"),
UnifiedType::Int { signed: true, size: IntSize::Int }
);
assert_eq!(
UnifiedType::from_c_str("const bool"),
UnifiedType::Bool
);
}
#[test]
fn test_struct_prefix() {
assert_eq!(
UnifiedType::from_c_str("struct refcounted_he *"),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("refcounted_he".to_string())),
is_const: false,
}
);
assert_eq!(
UnifiedType::from_c_str("struct SV"),
UnifiedType::Named("SV".to_string())
);
}
#[test]
fn test_is_const_pointer() {
let mut_ptr = UnifiedType::from_rust_str("*mut SV");
let const_ptr = UnifiedType::from_rust_str("*const c_char");
let non_ptr = UnifiedType::from_rust_str("c_int");
assert!(!mut_ptr.is_const_pointer());
assert!(const_ptr.is_const_pointer());
assert!(!non_ptr.is_const_pointer());
}
#[test]
fn test_is_float() {
assert!(UnifiedType::Float.is_float());
assert!(UnifiedType::Double.is_float());
assert!(UnifiedType::LongDouble.is_float());
assert!(UnifiedType::Named("NV".to_string()).is_float());
assert!(!UnifiedType::Int { signed: true, size: IntSize::Int }.is_float());
assert!(!UnifiedType::Named("SV".to_string()).is_float());
}
#[test]
fn test_is_bool() {
assert!(UnifiedType::Bool.is_bool());
assert!(!UnifiedType::Int { signed: true, size: IntSize::Int }.is_bool());
assert!(!UnifiedType::Void.is_bool());
}
#[test]
fn test_is_void() {
assert!(UnifiedType::Void.is_void());
assert!(!UnifiedType::Bool.is_void());
assert!(!UnifiedType::Int { signed: true, size: IntSize::Int }.is_void());
}
#[test]
fn test_fn_ptr_void_no_args() {
let ty = UnifiedType::FnPtr {
params: vec![],
ret: Box::new(UnifiedType::Void),
abi: None,
is_unsafe: false,
is_optional: false,
};
assert!(ty.is_fn_ptr());
assert!(!ty.is_optional_fn_ptr());
assert!(!ty.is_pointer());
assert!(!ty.is_void());
assert_eq!(ty.to_rust_string(), "fn()");
}
#[test]
fn test_fn_ptr_extern_c_with_args() {
let ty = UnifiedType::FnPtr {
params: vec![
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("CV".to_string())),
is_const: false,
},
],
ret: Box::new(UnifiedType::Void),
abi: Some("C".to_string()),
is_unsafe: true,
is_optional: false,
};
assert_eq!(ty.to_rust_string(), "unsafe extern \"C\" fn(*mut CV)");
}
#[test]
fn test_fn_ptr_optional_with_return() {
let ty = UnifiedType::FnPtr {
params: vec![
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("CV".to_string())),
is_const: false,
},
],
ret: Box::new(UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}),
abi: Some("C".to_string()),
is_unsafe: true,
is_optional: true,
};
assert!(ty.is_fn_ptr());
assert!(ty.is_optional_fn_ptr());
assert_eq!(
ty.to_rust_string(),
"Option<unsafe extern \"C\" fn(*mut CV) -> *mut SV>"
);
}
#[test]
fn test_verbatim_emits_as_is() {
let raw = ":: std :: option :: Option < unsafe extern \"C\" fn (arg1 : * mut CV) >";
let ty = UnifiedType::Verbatim(raw.to_string());
assert!(ty.is_verbatim());
assert!(!ty.is_pointer());
assert!(!ty.is_fn_ptr());
assert_eq!(ty.to_rust_string(), raw);
}
fn parse_ty(s: &str) -> syn::Type {
syn::parse_str::<syn::Type>(s).expect("syn parse failed")
}
#[test]
fn test_syn_void_tuple() {
assert_eq!(UnifiedType::from_syn_type(&parse_ty("()")), UnifiedType::Void);
}
#[test]
fn test_syn_pointer_mut() {
let ty = parse_ty("*mut SV");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}
);
}
#[test]
fn test_syn_pointer_const_char() {
let ty = parse_ty("*const c_char");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Char { signed: None }),
is_const: true,
}
);
}
#[test]
fn test_syn_double_pointer() {
let ty = parse_ty("*mut *mut OP");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("OP".to_string())),
is_const: false,
}),
is_const: false,
}
);
}
#[test]
fn test_syn_array() {
let ty = parse_ty("[U32; 8]");
match UnifiedType::from_syn_type(&ty) {
UnifiedType::Array { inner, size } => {
assert_eq!(*inner, UnifiedType::Named("U32".to_string()));
assert_eq!(size, Some(8));
}
other => panic!("expected Array, got {:?}", other),
}
}
#[test]
fn test_syn_primitive_full_path() {
let ty = parse_ty(":: std :: os :: raw :: c_int");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Int { signed: true, size: IntSize::Int }
);
}
#[test]
fn test_syn_primitive_short_path() {
let ty = parse_ty("c_uchar");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Char { signed: Some(false) }
);
}
#[test]
fn test_syn_named_type() {
let ty = parse_ty("PerlInterpreter");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Named("PerlInterpreter".to_string())
);
}
#[test]
fn test_syn_option_extern_c_fn_ptr() {
let ty = parse_ty(
":: std :: option :: Option < unsafe extern \"C\" fn (arg1 : * mut CV) >",
);
let ut = UnifiedType::from_syn_type(&ty);
let expected = UnifiedType::FnPtr {
params: vec![UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("CV".to_string())),
is_const: false,
}],
ret: Box::new(UnifiedType::Void),
abi: Some("C".to_string()),
is_unsafe: true,
is_optional: true,
};
assert_eq!(ut, expected);
assert_eq!(
ut.to_rust_string(),
"Option<unsafe extern \"C\" fn(*mut CV)>"
);
}
#[test]
fn test_syn_option_fn_with_return() {
let ty = parse_ty(
"Option<unsafe extern \"C\" fn(my_perl: *mut PerlInterpreter, rx: *mut REGEXP) -> *mut SV>",
);
let ut = UnifiedType::from_syn_type(&ty);
assert!(ut.is_optional_fn_ptr());
assert_eq!(
ut.to_rust_string(),
"Option<unsafe extern \"C\" fn(*mut PerlInterpreter, *mut REGEXP) -> *mut SV>"
);
}
#[test]
fn test_syn_unsupported_falls_to_verbatim() {
let ty = parse_ty("Vec<u8>");
let ut = UnifiedType::from_syn_type(&ty);
assert!(ut.is_verbatim());
assert_eq!(ut.to_rust_string(), "Vec < u8 >");
}
#[test]
fn test_syn_paren_and_group_unwrap() {
let ty = parse_ty("(*mut SV)");
assert_eq!(
UnifiedType::from_syn_type(&ty),
UnifiedType::Pointer {
inner: Box::new(UnifiedType::Named("SV".to_string())),
is_const: false,
}
);
}
#[test]
fn test_fn_ptr_eq_hash() {
let a = UnifiedType::FnPtr {
params: vec![UnifiedType::Void],
ret: Box::new(UnifiedType::Void),
abi: Some("C".to_string()),
is_unsafe: true,
is_optional: true,
};
let b = a.clone();
assert_eq!(a, b);
let mut set = std::collections::HashSet::new();
set.insert(a.clone());
assert!(set.contains(&b));
}
}