1use std::fmt;
6
7use quote::ToTokens;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum IntSize {
12 Char,
14 Short,
16 Int,
18 Long,
20 LongLong,
22 Int128,
24}
25
26#[derive(Debug, Clone)]
28pub enum TypeSource {
29 Apidoc { raw: String, entry_name: String },
31 Bindings { raw: String },
33 Inferred,
35 Parsed,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub enum UnifiedType {
45 Void,
47 Bool,
49 Char { signed: Option<bool> },
51 Int { signed: bool, size: IntSize },
53 Float,
55 Double,
57 LongDouble,
59
60 Pointer {
62 inner: Box<UnifiedType>,
63 is_const: bool,
64 },
65
66 Array {
68 inner: Box<UnifiedType>,
69 size: Option<usize>,
70 },
71
72 Named(String),
74
75 FnPtr {
82 params: Vec<UnifiedType>,
83 ret: Box<UnifiedType>,
84 abi: Option<String>,
86 is_unsafe: bool,
88 is_optional: bool,
90 },
91
92 Verbatim(String),
99
100 Unknown,
102}
103
104#[derive(Debug, Clone)]
106pub struct SourcedType {
107 pub ty: UnifiedType,
108 pub source: TypeSource,
109}
110
111impl UnifiedType {
112 pub fn from_c_str(s: &str) -> Self {
119 let trimmed = s.trim();
120
121 if trimmed.is_empty() {
122 return Self::Unknown;
123 }
124
125 if trimmed == "void" {
127 return Self::Void;
128 }
129
130 if trimmed == "bool" || trimmed == "_Bool" {
132 return Self::Bool;
133 }
134
135 if let Some(ptr_type) = Self::parse_c_pointer(trimmed) {
137 return ptr_type;
138 }
139
140 Self::parse_c_basic_type(trimmed)
142 }
143
144 fn parse_c_pointer(s: &str) -> Option<Self> {
146 let s = s.trim();
147
148 if let Some(star_pos) = s.rfind('*') {
150 let before_star = s[..star_pos].trim();
151 let after_star = s[star_pos + 1..].trim();
152
153 let _ptr_const = after_star == "const";
155
156 let (is_const, base_type) = if before_star.starts_with("const ") {
160 (true, before_star[6..].trim())
161 } else if before_star.ends_with(" const") {
162 (true, before_star[..before_star.len() - 6].trim())
163 } else {
164 (false, before_star)
165 };
166
167 let inner_type = if base_type.contains('*') {
169 Self::parse_c_pointer(base_type).unwrap_or_else(|| Self::parse_c_basic_type(base_type))
170 } else {
171 Self::parse_c_basic_type(base_type)
172 };
173
174 return Some(Self::Pointer {
175 inner: Box::new(inner_type),
176 is_const,
177 });
178 }
179
180 None
181 }
182
183 fn parse_c_basic_type(s: &str) -> Self {
185 let s = s.trim();
186
187 let s = s.strip_prefix("const ").unwrap_or(s);
189 let s = if s.ends_with(" const") {
190 &s[..s.len() - 6]
191 } else {
192 s
193 };
194 let s = s.trim();
195
196 let s = s
198 .strip_prefix("struct ")
199 .or_else(|| s.strip_prefix("union "))
200 .or_else(|| s.strip_prefix("enum "))
201 .unwrap_or(s);
202
203 let (is_unsigned, base) = if s == "unsigned" {
205 (true, "")
207 } else if s == "signed" {
208 (false, "")
210 } else if s.starts_with("unsigned ") {
211 (true, s[9..].trim())
212 } else if s.starts_with("signed ") {
213 (false, s[7..].trim())
214 } else {
215 (false, s)
216 };
217
218 match base {
220 "char" if is_unsigned => Self::Char { signed: Some(false) },
222 "char" => Self::Char { signed: None },
223
224 "short" | "short int" => Self::Int {
226 signed: !is_unsigned,
227 size: IntSize::Short,
228 },
229
230 "int" | "" => Self::Int {
232 signed: !is_unsigned,
233 size: IntSize::Int,
234 },
235
236 "long" | "long int" => Self::Int {
238 signed: !is_unsigned,
239 size: IntSize::Long,
240 },
241
242 "long long" | "long long int" => Self::Int {
244 signed: !is_unsigned,
245 size: IntSize::LongLong,
246 },
247
248 "__int128" | "__int128_t" => Self::Int {
250 signed: !is_unsigned,
251 size: IntSize::Int128,
252 },
253
254 "float" => Self::Float,
256 "double" => Self::Double,
257 "long double" => Self::LongDouble,
258
259 "void" => Self::Void,
261
262 "bool" | "_Bool" => Self::Bool,
264
265 "size_t" | "ssize_t" | "ptrdiff_t" => Self::Named(base.to_string()),
268
269 _ => {
271 if is_unsigned {
272 Self::Named(format!("unsigned {}", base))
274 } else {
275 Self::Named(base.to_string())
276 }
277 }
278 }
279 }
280
281 pub fn from_rust_str(s: &str) -> Self {
288 let normalized = s
290 .replace("* mut", "*mut")
291 .replace("* const", "*const")
292 .replace(" :: ", "::");
293 let trimmed = normalized.trim();
294
295 if trimmed.is_empty() {
296 return Self::Unknown;
297 }
298
299 if let Some(rest) = trimmed.strip_prefix("*mut ") {
301 return Self::Pointer {
302 inner: Box::new(Self::from_rust_str(rest)),
303 is_const: false,
304 };
305 }
306 if let Some(rest) = trimmed.strip_prefix("*const ") {
307 return Self::Pointer {
308 inner: Box::new(Self::from_rust_str(rest)),
309 is_const: true,
310 };
311 }
312 if let Some(rest) = trimmed.strip_prefix("*mut") {
314 return Self::Pointer {
315 inner: Box::new(Self::from_rust_str(rest.trim())),
316 is_const: false,
317 };
318 }
319 if let Some(rest) = trimmed.strip_prefix("*const") {
320 return Self::Pointer {
321 inner: Box::new(Self::from_rust_str(rest.trim())),
322 is_const: true,
323 };
324 }
325 if trimmed.starts_with('[') && trimmed.ends_with(']') {
327 let inner = &trimmed[1..trimmed.len() - 1];
328 let mut depth = 0i32;
330 let mut semi_pos: Option<usize> = None;
331 for (i, ch) in inner.char_indices() {
332 match ch {
333 '[' | '(' | '<' => depth += 1,
334 ']' | ')' | '>' => depth -= 1,
335 ';' if depth == 0 => {
336 semi_pos = Some(i);
337 break;
338 }
339 _ => {}
340 }
341 }
342 if let Some(pos) = semi_pos {
343 let elem = inner[..pos].trim();
344 let size_str = inner[pos + 1..].trim();
345 let size = size_str
346 .split_whitespace()
347 .next()
348 .and_then(|s| s.trim_end_matches("usize").parse::<usize>().ok());
349 return Self::Array {
350 inner: Box::new(Self::from_rust_str(elem)),
351 size,
352 };
353 }
354 }
355
356 Self::parse_rust_basic_type(trimmed)
358 }
359
360 pub fn from_syn_type(ty: &syn::Type) -> Self {
369 match ty {
370 syn::Type::Tuple(t) if t.elems.is_empty() => Self::Void,
372
373 syn::Type::Group(g) => Self::from_syn_type(&g.elem),
375 syn::Type::Paren(p) => Self::from_syn_type(&p.elem),
376
377 syn::Type::Ptr(p) => Self::Pointer {
379 inner: Box::new(Self::from_syn_type(&p.elem)),
380 is_const: p.const_token.is_some(),
381 },
382
383 syn::Type::Array(a) => Self::Array {
385 inner: Box::new(Self::from_syn_type(&a.elem)),
386 size: extract_array_size(&a.len),
387 },
388
389 syn::Type::BareFn(f) => from_bare_fn(f, false),
391
392 syn::Type::Path(tp) => from_type_path(tp),
394
395 _ => verbatim_of(ty),
397 }
398 }
399
400 fn parse_rust_basic_type(s: &str) -> Self {
402 let s = s
405 .strip_prefix("::")
406 .map(|s| s.trim_start())
407 .unwrap_or(s);
408 let s = s
409 .strip_prefix("std::")
410 .unwrap_or(s);
411 let s = s
412 .strip_prefix("ffi::")
413 .or_else(|| s.strip_prefix("os::raw::"))
414 .unwrap_or(s);
415
416 match s {
417 "()" => Self::Void,
418 "c_void" => Self::Void,
419 "bool" => Self::Bool,
420
421 "c_char" => Self::Char { signed: None },
423 "c_schar" => Self::Char { signed: Some(true) },
424 "c_uchar" => Self::Char { signed: Some(false) },
425
426 "c_short" => Self::Int { signed: true, size: IntSize::Short },
428 "c_ushort" => Self::Int { signed: false, size: IntSize::Short },
429 "c_int" => Self::Int { signed: true, size: IntSize::Int },
430 "c_uint" => Self::Int { signed: false, size: IntSize::Int },
431 "c_long" => Self::Int { signed: true, size: IntSize::Long },
432 "c_ulong" => Self::Int { signed: false, size: IntSize::Long },
433 "c_longlong" => Self::Int { signed: true, size: IntSize::LongLong },
434 "c_ulonglong" => Self::Int { signed: false, size: IntSize::LongLong },
435
436 "i8" => Self::Char { signed: Some(true) },
438 "u8" => Self::Char { signed: Some(false) },
439 "i16" => Self::Int { signed: true, size: IntSize::Short },
440 "u16" => Self::Int { signed: false, size: IntSize::Short },
441 "i32" => Self::Int { signed: true, size: IntSize::Int },
442 "u32" => Self::Int { signed: false, size: IntSize::Int },
443 "i64" => Self::Int { signed: true, size: IntSize::LongLong },
444 "u64" => Self::Int { signed: false, size: IntSize::LongLong },
445 "i128" => Self::Int { signed: true, size: IntSize::Int128 },
446 "u128" => Self::Int { signed: false, size: IntSize::Int128 },
447 "isize" => Self::Named("isize".to_string()),
452 "usize" => Self::Named("usize".to_string()),
453
454 "c_float" | "f32" => Self::Float,
456 "c_double" | "f64" => Self::Double,
457
458 _ => Self::Named(s.to_string()),
460 }
461 }
462
463 pub fn to_rust_string(&self) -> String {
465 match self {
466 Self::Void => "()".to_string(),
467 Self::Bool => "bool".to_string(),
468
469 Self::Char { signed: None } => "c_char".to_string(),
470 Self::Char { signed: Some(true) } => "c_schar".to_string(),
471 Self::Char { signed: Some(false) } => "c_uchar".to_string(),
472
473 Self::Int { signed, size } => {
474 match (signed, size) {
475 (true, IntSize::Char) => "c_schar".to_string(),
476 (false, IntSize::Char) => "c_uchar".to_string(),
477 (true, IntSize::Short) => "c_short".to_string(),
478 (false, IntSize::Short) => "c_ushort".to_string(),
479 (true, IntSize::Int) => "c_int".to_string(),
480 (false, IntSize::Int) => "c_uint".to_string(),
481 (true, IntSize::Long) => "c_long".to_string(),
482 (false, IntSize::Long) => "c_ulong".to_string(),
483 (true, IntSize::LongLong) => "c_longlong".to_string(),
484 (false, IntSize::LongLong) => "c_ulonglong".to_string(),
485 (true, IntSize::Int128) => "i128".to_string(),
486 (false, IntSize::Int128) => "u128".to_string(),
487 }
488 }
489
490 Self::Float => "c_float".to_string(),
491 Self::Double => "c_double".to_string(),
492 Self::LongDouble => "c_double".to_string(), Self::Pointer { inner, is_const } => {
495 let inner_str = if matches!(inner.as_ref(), Self::Void) {
500 "c_void".to_string()
501 } else {
502 inner.to_rust_string()
503 };
504 if *is_const {
505 format!("*const {}", inner_str)
506 } else {
507 format!("*mut {}", inner_str)
508 }
509 }
510
511 Self::Array { inner, size } => {
512 let inner_str = inner.to_rust_string();
513 match size {
514 Some(n) => format!("[{}; {}]", inner_str, n),
515 None => format!("[{}]", inner_str),
516 }
517 }
518
519 Self::Named(name) => {
520 match name.as_str() {
522 "size_t" => "usize".to_string(),
523 "ssize_t" | "ptrdiff_t" => "isize".to_string(),
524 "off_t" | "off64_t" => "i64".to_string(),
525 _ => name.clone(),
526 }
527 }
528
529 Self::FnPtr { params, ret, abi, is_unsafe, is_optional } => {
530 let mut s = String::new();
531 if *is_unsafe {
532 s.push_str("unsafe ");
533 }
534 if let Some(abi_name) = abi {
535 s.push_str(&format!("extern {:?} ", abi_name));
537 }
538 s.push_str("fn(");
539 let param_strs: Vec<String> = params.iter().map(|p| p.to_rust_string()).collect();
540 s.push_str(¶m_strs.join(", "));
541 s.push(')');
542 let ret_str = ret.to_rust_string();
543 if ret_str != "()" {
544 s.push_str(" -> ");
545 s.push_str(&ret_str);
546 }
547 if *is_optional {
548 format!("Option<{}>", s)
549 } else {
550 s
551 }
552 }
553
554 Self::Verbatim(s) => s.clone(),
555
556 Self::Unknown => "UnknownType".to_string(),
557 }
558 }
559
560 pub fn equals_ignoring_const(&self, other: &Self) -> bool {
562 match (self, other) {
563 (Self::Void, Self::Void) => true,
564 (Self::Bool, Self::Bool) => true,
565 (Self::Char { signed: s1 }, Self::Char { signed: s2 }) => s1 == s2,
566 (
567 Self::Int { signed: s1, size: sz1 },
568 Self::Int { signed: s2, size: sz2 },
569 ) => s1 == s2 && sz1 == sz2,
570 (Self::Float, Self::Float) => true,
571 (Self::Double, Self::Double) => true,
572 (Self::LongDouble, Self::LongDouble) => true,
573
574 (
576 Self::Pointer { inner: i1, .. },
577 Self::Pointer { inner: i2, .. },
578 ) => i1.equals_ignoring_const(i2),
579
580 (
581 Self::Array { inner: i1, size: s1 },
582 Self::Array { inner: i2, size: s2 },
583 ) => s1 == s2 && i1.equals_ignoring_const(i2),
584
585 (Self::Named(n1), Self::Named(n2)) => n1 == n2,
586
587 (Self::Unknown, Self::Unknown) => true,
588
589 _ => false,
590 }
591 }
592
593 pub fn equals_ignoring_case(&self, other: &Self) -> bool {
595 match (self, other) {
596 (Self::Named(n1), Self::Named(n2)) => n1.eq_ignore_ascii_case(n2),
597
598 (
599 Self::Pointer { inner: i1, is_const: c1 },
600 Self::Pointer { inner: i2, is_const: c2 },
601 ) => c1 == c2 && i1.equals_ignoring_case(i2),
602
603 (
604 Self::Array { inner: i1, size: s1 },
605 Self::Array { inner: i2, size: s2 },
606 ) => s1 == s2 && i1.equals_ignoring_case(i2),
607
608 _ => self == other,
610 }
611 }
612
613 pub fn is_pointer(&self) -> bool {
615 matches!(self, Self::Pointer { .. })
616 }
617
618 pub fn is_void_pointer(&self) -> bool {
620 match self {
621 Self::Pointer { inner, .. } => {
622 matches!(**inner, Self::Void) || matches!(**inner, Self::Named(ref n) if n == "c_void")
623 }
624 _ => false,
625 }
626 }
627
628 pub fn is_concrete_pointer(&self) -> bool {
630 self.is_pointer() && !self.is_void_pointer()
631 }
632
633 pub fn is_const_pointer(&self) -> bool {
635 matches!(self, Self::Pointer { is_const: true, .. })
636 }
637
638 pub fn is_float(&self) -> bool {
640 matches!(self, Self::Float | Self::Double | Self::LongDouble)
641 || matches!(self, Self::Named(n) if n == "NV")
642 }
643
644 pub fn is_bool(&self) -> bool {
646 matches!(self, Self::Bool)
647 }
648
649 pub fn is_void(&self) -> bool {
651 matches!(self, Self::Void)
652 }
653
654 pub fn is_named(&self) -> bool {
656 matches!(self, Self::Named(_))
657 }
658
659 pub fn as_named(&self) -> Option<&str> {
661 match self {
662 Self::Named(name) => Some(name),
663 _ => None,
664 }
665 }
666
667 pub fn inner_type(&self) -> Option<&UnifiedType> {
669 match self {
670 Self::Pointer { inner, .. } => Some(inner),
671 Self::Array { inner, .. } => Some(inner),
672 _ => None,
673 }
674 }
675
676 pub fn is_fn_ptr(&self) -> bool {
678 matches!(self, Self::FnPtr { .. })
679 }
680
681 pub fn is_optional_fn_ptr(&self) -> bool {
683 matches!(self, Self::FnPtr { is_optional: true, .. })
684 }
685
686 pub fn is_verbatim(&self) -> bool {
688 matches!(self, Self::Verbatim(_))
689 }
690}
691
692fn extract_array_size(expr: &syn::Expr) -> Option<usize> {
698 if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(li), .. }) = expr {
699 li.base10_parse::<usize>().ok()
700 } else {
701 None
702 }
703}
704
705fn from_bare_fn(f: &syn::TypeBareFn, is_optional: bool) -> UnifiedType {
708 let abi = f.abi.as_ref().map(|abi| {
709 abi.name
710 .as_ref()
711 .map(|n| n.value())
712 .unwrap_or_else(|| "C".to_string())
713 });
714 let params: Vec<UnifiedType> = f
715 .inputs
716 .iter()
717 .map(|arg| UnifiedType::from_syn_type(&arg.ty))
718 .collect();
719 let ret = match &f.output {
720 syn::ReturnType::Default => UnifiedType::Void,
721 syn::ReturnType::Type(_, t) => UnifiedType::from_syn_type(t),
722 };
723 UnifiedType::FnPtr {
724 params,
725 ret: Box::new(ret),
726 abi,
727 is_unsafe: f.unsafety.is_some(),
728 is_optional,
729 }
730}
731
732fn from_type_path(tp: &syn::TypePath) -> UnifiedType {
734 if tp.qself.is_some() {
736 return verbatim_of(&syn::Type::Path(tp.clone()));
737 }
738 let path = &tp.path;
739
740 if let Some(last) = path.segments.last() {
742 if last.ident == "Option" {
743 if let syn::PathArguments::AngleBracketed(ab) = &last.arguments {
744 if let Some(syn::GenericArgument::Type(inner)) = ab.args.first() {
745 return from_optional_inner(inner);
746 }
747 }
748 }
749 }
750
751 if let Some(ident) = single_ident_of(path) {
753 if let Some(prim) = primitive_from_ident(&ident) {
754 return prim;
755 }
756 return UnifiedType::Named(ident);
757 }
758
759 verbatim_of(&syn::Type::Path(tp.clone()))
761}
762
763fn from_optional_inner(inner: &syn::Type) -> UnifiedType {
766 match inner {
767 syn::Type::BareFn(f) => from_bare_fn(f, true),
768 syn::Type::Group(g) => from_optional_inner(&g.elem),
769 syn::Type::Paren(p) => from_optional_inner(&p.elem),
770 _ => {
772 let wrapped: syn::TypePath = syn::parse_quote!(::std::option::Option<#inner>);
773 verbatim_of(&syn::Type::Path(wrapped))
774 }
775 }
776}
777
778fn single_ident_of(path: &syn::Path) -> Option<String> {
782 let last = path.segments.last()?;
783 if !matches!(last.arguments, syn::PathArguments::None) {
784 return None;
785 }
786 Some(last.ident.to_string())
787}
788
789fn primitive_from_ident(name: &str) -> Option<UnifiedType> {
792 Some(match name {
793 "c_void" => UnifiedType::Void,
795 "bool" => UnifiedType::Bool,
796
797 "c_char" => UnifiedType::Char { signed: None },
799 "c_schar" => UnifiedType::Char { signed: Some(true) },
800 "c_uchar" => UnifiedType::Char { signed: Some(false) },
801
802 "c_short" => UnifiedType::Int { signed: true, size: IntSize::Short },
804 "c_ushort" => UnifiedType::Int { signed: false, size: IntSize::Short },
805 "c_int" => UnifiedType::Int { signed: true, size: IntSize::Int },
806 "c_uint" => UnifiedType::Int { signed: false, size: IntSize::Int },
807 "c_long" => UnifiedType::Int { signed: true, size: IntSize::Long },
808 "c_ulong" => UnifiedType::Int { signed: false, size: IntSize::Long },
809 "c_longlong" => UnifiedType::Int { signed: true, size: IntSize::LongLong },
810 "c_ulonglong" => UnifiedType::Int { signed: false, size: IntSize::LongLong },
811
812 "i8" => UnifiedType::Char { signed: Some(true) },
815 "u8" => UnifiedType::Char { signed: Some(false) },
816 "i16" => UnifiedType::Int { signed: true, size: IntSize::Short },
817 "u16" => UnifiedType::Int { signed: false, size: IntSize::Short },
818 "i32" => UnifiedType::Int { signed: true, size: IntSize::Int },
819 "u32" => UnifiedType::Int { signed: false, size: IntSize::Int },
820 "i64" => UnifiedType::Int { signed: true, size: IntSize::LongLong },
821 "u64" => UnifiedType::Int { signed: false, size: IntSize::LongLong },
822 "i128" => UnifiedType::Int { signed: true, size: IntSize::Int128 },
823 "u128" => UnifiedType::Int { signed: false, size: IntSize::Int128 },
824
825 "isize" | "usize" => UnifiedType::Named(name.to_string()),
827
828 "c_float" | "f32" => UnifiedType::Float,
830 "c_double" | "f64" => UnifiedType::Double,
831
832 _ => return None,
833 })
834}
835
836fn verbatim_of(ty: &syn::Type) -> UnifiedType {
839 UnifiedType::Verbatim(ty.to_token_stream().to_string())
840}
841
842impl fmt::Display for UnifiedType {
843 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844 write!(f, "{}", self.to_rust_string())
845 }
846}
847
848impl SourcedType {
849 pub fn new(ty: UnifiedType, source: TypeSource) -> Self {
851 Self { ty, source }
852 }
853
854 pub fn from_apidoc(raw: &str, entry_name: &str) -> Self {
856 Self {
857 ty: UnifiedType::from_c_str(raw),
858 source: TypeSource::Apidoc {
859 raw: raw.to_string(),
860 entry_name: entry_name.to_string(),
861 },
862 }
863 }
864
865 pub fn from_bindings(raw: &str) -> Self {
867 Self {
868 ty: UnifiedType::from_rust_str(raw),
869 source: TypeSource::Bindings {
870 raw: raw.to_string(),
871 },
872 }
873 }
874
875 pub fn raw_string(&self) -> Option<&str> {
877 match &self.source {
878 TypeSource::Apidoc { raw, .. } => Some(raw),
879 TypeSource::Bindings { raw } => Some(raw),
880 _ => None,
881 }
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888
889 #[test]
890 fn test_from_c_str_basic_types() {
891 assert_eq!(UnifiedType::from_c_str("void"), UnifiedType::Void);
892 assert_eq!(UnifiedType::from_c_str("bool"), UnifiedType::Bool);
893 assert_eq!(
894 UnifiedType::from_c_str("int"),
895 UnifiedType::Int { signed: true, size: IntSize::Int }
896 );
897 assert_eq!(
898 UnifiedType::from_c_str("unsigned int"),
899 UnifiedType::Int { signed: false, size: IntSize::Int }
900 );
901 assert_eq!(
902 UnifiedType::from_c_str("long long"),
903 UnifiedType::Int { signed: true, size: IntSize::LongLong }
904 );
905 }
906
907 #[test]
908 fn test_from_c_str_pointer() {
909 assert_eq!(
910 UnifiedType::from_c_str("SV *"),
911 UnifiedType::Pointer {
912 inner: Box::new(UnifiedType::Named("SV".to_string())),
913 is_const: false,
914 }
915 );
916 assert_eq!(
917 UnifiedType::from_c_str("const char *"),
918 UnifiedType::Pointer {
919 inner: Box::new(UnifiedType::Char { signed: None }),
920 is_const: true,
921 }
922 );
923 }
924
925 #[test]
926 fn test_from_c_str_double_pointer() {
927 let ty = UnifiedType::from_c_str("SV **");
928 assert_eq!(
929 ty,
930 UnifiedType::Pointer {
931 inner: Box::new(UnifiedType::Pointer {
932 inner: Box::new(UnifiedType::Named("SV".to_string())),
933 is_const: false,
934 }),
935 is_const: false,
936 }
937 );
938 }
939
940 #[test]
941 fn test_from_rust_str_basic_types() {
942 assert_eq!(UnifiedType::from_rust_str("()"), UnifiedType::Void);
943 assert_eq!(UnifiedType::from_rust_str("bool"), UnifiedType::Bool);
944 assert_eq!(
945 UnifiedType::from_rust_str("c_int"),
946 UnifiedType::Int { signed: true, size: IntSize::Int }
947 );
948 assert_eq!(
949 UnifiedType::from_rust_str("c_uint"),
950 UnifiedType::Int { signed: false, size: IntSize::Int }
951 );
952 }
953
954 #[test]
955 fn test_from_rust_str_pointer() {
956 assert_eq!(
957 UnifiedType::from_rust_str("*mut SV"),
958 UnifiedType::Pointer {
959 inner: Box::new(UnifiedType::Named("SV".to_string())),
960 is_const: false,
961 }
962 );
963 assert_eq!(
964 UnifiedType::from_rust_str("*const c_char"),
965 UnifiedType::Pointer {
966 inner: Box::new(UnifiedType::Char { signed: None }),
967 is_const: true,
968 }
969 );
970 }
971
972 #[test]
973 fn test_from_rust_str_double_pointer() {
974 let ty = UnifiedType::from_rust_str("*mut *mut SV");
975 assert_eq!(
976 ty,
977 UnifiedType::Pointer {
978 inner: Box::new(UnifiedType::Pointer {
979 inner: Box::new(UnifiedType::Named("SV".to_string())),
980 is_const: false,
981 }),
982 is_const: false,
983 }
984 );
985 }
986
987 #[test]
988 fn test_from_rust_str_with_spaces() {
989 let ty = UnifiedType::from_rust_str("* mut * mut SV");
991 assert_eq!(
992 ty,
993 UnifiedType::Pointer {
994 inner: Box::new(UnifiedType::Pointer {
995 inner: Box::new(UnifiedType::Named("SV".to_string())),
996 is_const: false,
997 }),
998 is_const: false,
999 }
1000 );
1001 }
1002
1003 #[test]
1004 fn test_to_rust_string() {
1005 assert_eq!(UnifiedType::Void.to_rust_string(), "()");
1006 assert_eq!(
1007 UnifiedType::Int { signed: true, size: IntSize::Int }.to_rust_string(),
1008 "c_int"
1009 );
1010 assert_eq!(
1011 UnifiedType::Pointer {
1012 inner: Box::new(UnifiedType::Named("SV".to_string())),
1013 is_const: false,
1014 }.to_rust_string(),
1015 "*mut SV"
1016 );
1017 assert_eq!(
1018 UnifiedType::Pointer {
1019 inner: Box::new(UnifiedType::Char { signed: None }),
1020 is_const: true,
1021 }.to_rust_string(),
1022 "*const c_char"
1023 );
1024 }
1025
1026 #[test]
1027 fn test_roundtrip_c_to_rust() {
1028 let cases = [
1029 ("void", "()"),
1030 ("int", "c_int"),
1031 ("unsigned int", "c_uint"),
1032 ("SV *", "*mut SV"),
1033 ("const char *", "*const c_char"),
1034 ("SV **", "*mut *mut SV"),
1035 ];
1036
1037 for (c_type, expected_rust) in cases {
1038 let ty = UnifiedType::from_c_str(c_type);
1039 assert_eq!(ty.to_rust_string(), expected_rust, "C type: {}", c_type);
1040 }
1041 }
1042
1043 #[test]
1044 fn test_roundtrip_rust() {
1045 let cases = [
1046 "()",
1047 "bool",
1048 "c_int",
1049 "c_uint",
1050 "*mut SV",
1051 "*const c_char",
1052 "*mut *mut SV",
1053 ];
1054
1055 for rust_type in cases {
1056 let ty = UnifiedType::from_rust_str(rust_type);
1057 assert_eq!(ty.to_rust_string(), rust_type, "Rust type: {}", rust_type);
1058 }
1059 }
1060
1061 #[test]
1062 fn test_equals_ignoring_const() {
1063 let mut_sv = UnifiedType::Pointer {
1064 inner: Box::new(UnifiedType::Named("SV".to_string())),
1065 is_const: false,
1066 };
1067 let const_sv = UnifiedType::Pointer {
1068 inner: Box::new(UnifiedType::Named("SV".to_string())),
1069 is_const: true,
1070 };
1071
1072 assert!(mut_sv.equals_ignoring_const(&const_sv));
1073 assert!(!mut_sv.eq(&const_sv)); }
1075
1076 #[test]
1077 fn test_equals_ignoring_case() {
1078 let sv_upper = UnifiedType::Named("SV".to_string());
1079 let sv_lower = UnifiedType::Named("sv".to_string());
1080
1081 assert!(sv_upper.equals_ignoring_case(&sv_lower));
1082 assert!(!sv_upper.eq(&sv_lower)); let ptr_sv_upper = UnifiedType::Pointer {
1086 inner: Box::new(sv_upper),
1087 is_const: false,
1088 };
1089 let ptr_sv_lower = UnifiedType::Pointer {
1090 inner: Box::new(sv_lower),
1091 is_const: false,
1092 };
1093 assert!(ptr_sv_upper.equals_ignoring_case(&ptr_sv_lower));
1094 }
1095
1096 #[test]
1097 fn test_const_value_type() {
1098 assert_eq!(
1100 UnifiedType::from_c_str("const U32"),
1101 UnifiedType::Named("U32".to_string())
1102 );
1103 assert_eq!(
1104 UnifiedType::from_c_str("const STRLEN"),
1105 UnifiedType::Named("STRLEN".to_string())
1106 );
1107 assert_eq!(
1108 UnifiedType::from_c_str("const int"),
1109 UnifiedType::Int { signed: true, size: IntSize::Int }
1110 );
1111 assert_eq!(
1112 UnifiedType::from_c_str("const bool"),
1113 UnifiedType::Bool
1114 );
1115 }
1116
1117 #[test]
1118 fn test_struct_prefix() {
1119 assert_eq!(
1121 UnifiedType::from_c_str("struct refcounted_he *"),
1122 UnifiedType::Pointer {
1123 inner: Box::new(UnifiedType::Named("refcounted_he".to_string())),
1124 is_const: false,
1125 }
1126 );
1127 assert_eq!(
1128 UnifiedType::from_c_str("struct SV"),
1129 UnifiedType::Named("SV".to_string())
1130 );
1131 }
1132
1133 #[test]
1134 fn test_is_const_pointer() {
1135 let mut_ptr = UnifiedType::from_rust_str("*mut SV");
1136 let const_ptr = UnifiedType::from_rust_str("*const c_char");
1137 let non_ptr = UnifiedType::from_rust_str("c_int");
1138
1139 assert!(!mut_ptr.is_const_pointer());
1140 assert!(const_ptr.is_const_pointer());
1141 assert!(!non_ptr.is_const_pointer());
1142 }
1143
1144 #[test]
1145 fn test_is_float() {
1146 assert!(UnifiedType::Float.is_float());
1147 assert!(UnifiedType::Double.is_float());
1148 assert!(UnifiedType::LongDouble.is_float());
1149 assert!(UnifiedType::Named("NV".to_string()).is_float());
1150 assert!(!UnifiedType::Int { signed: true, size: IntSize::Int }.is_float());
1151 assert!(!UnifiedType::Named("SV".to_string()).is_float());
1152 }
1153
1154 #[test]
1155 fn test_is_bool() {
1156 assert!(UnifiedType::Bool.is_bool());
1157 assert!(!UnifiedType::Int { signed: true, size: IntSize::Int }.is_bool());
1158 assert!(!UnifiedType::Void.is_bool());
1159 }
1160
1161 #[test]
1162 fn test_is_void() {
1163 assert!(UnifiedType::Void.is_void());
1164 assert!(!UnifiedType::Bool.is_void());
1165 assert!(!UnifiedType::Int { signed: true, size: IntSize::Int }.is_void());
1166 }
1167
1168 #[test]
1171 fn test_fn_ptr_void_no_args() {
1172 let ty = UnifiedType::FnPtr {
1173 params: vec![],
1174 ret: Box::new(UnifiedType::Void),
1175 abi: None,
1176 is_unsafe: false,
1177 is_optional: false,
1178 };
1179 assert!(ty.is_fn_ptr());
1180 assert!(!ty.is_optional_fn_ptr());
1181 assert!(!ty.is_pointer());
1182 assert!(!ty.is_void());
1183 assert_eq!(ty.to_rust_string(), "fn()");
1184 }
1185
1186 #[test]
1187 fn test_fn_ptr_extern_c_with_args() {
1188 let ty = UnifiedType::FnPtr {
1189 params: vec![
1190 UnifiedType::Pointer {
1191 inner: Box::new(UnifiedType::Named("CV".to_string())),
1192 is_const: false,
1193 },
1194 ],
1195 ret: Box::new(UnifiedType::Void),
1196 abi: Some("C".to_string()),
1197 is_unsafe: true,
1198 is_optional: false,
1199 };
1200 assert_eq!(ty.to_rust_string(), "unsafe extern \"C\" fn(*mut CV)");
1201 }
1202
1203 #[test]
1204 fn test_fn_ptr_optional_with_return() {
1205 let ty = UnifiedType::FnPtr {
1207 params: vec![
1208 UnifiedType::Pointer {
1209 inner: Box::new(UnifiedType::Named("CV".to_string())),
1210 is_const: false,
1211 },
1212 ],
1213 ret: Box::new(UnifiedType::Pointer {
1214 inner: Box::new(UnifiedType::Named("SV".to_string())),
1215 is_const: false,
1216 }),
1217 abi: Some("C".to_string()),
1218 is_unsafe: true,
1219 is_optional: true,
1220 };
1221 assert!(ty.is_fn_ptr());
1222 assert!(ty.is_optional_fn_ptr());
1223 assert_eq!(
1224 ty.to_rust_string(),
1225 "Option<unsafe extern \"C\" fn(*mut CV) -> *mut SV>"
1226 );
1227 }
1228
1229 #[test]
1230 fn test_verbatim_emits_as_is() {
1231 let raw = ":: std :: option :: Option < unsafe extern \"C\" fn (arg1 : * mut CV) >";
1232 let ty = UnifiedType::Verbatim(raw.to_string());
1233 assert!(ty.is_verbatim());
1234 assert!(!ty.is_pointer());
1235 assert!(!ty.is_fn_ptr());
1236 assert_eq!(ty.to_rust_string(), raw);
1237 }
1238
1239 fn parse_ty(s: &str) -> syn::Type {
1242 syn::parse_str::<syn::Type>(s).expect("syn parse failed")
1243 }
1244
1245 #[test]
1246 fn test_syn_void_tuple() {
1247 assert_eq!(UnifiedType::from_syn_type(&parse_ty("()")), UnifiedType::Void);
1248 }
1249
1250 #[test]
1251 fn test_syn_pointer_mut() {
1252 let ty = parse_ty("*mut SV");
1253 assert_eq!(
1254 UnifiedType::from_syn_type(&ty),
1255 UnifiedType::Pointer {
1256 inner: Box::new(UnifiedType::Named("SV".to_string())),
1257 is_const: false,
1258 }
1259 );
1260 }
1261
1262 #[test]
1263 fn test_syn_pointer_const_char() {
1264 let ty = parse_ty("*const c_char");
1265 assert_eq!(
1266 UnifiedType::from_syn_type(&ty),
1267 UnifiedType::Pointer {
1268 inner: Box::new(UnifiedType::Char { signed: None }),
1269 is_const: true,
1270 }
1271 );
1272 }
1273
1274 #[test]
1275 fn test_syn_double_pointer() {
1276 let ty = parse_ty("*mut *mut OP");
1277 assert_eq!(
1278 UnifiedType::from_syn_type(&ty),
1279 UnifiedType::Pointer {
1280 inner: Box::new(UnifiedType::Pointer {
1281 inner: Box::new(UnifiedType::Named("OP".to_string())),
1282 is_const: false,
1283 }),
1284 is_const: false,
1285 }
1286 );
1287 }
1288
1289 #[test]
1290 fn test_syn_array() {
1291 let ty = parse_ty("[U32; 8]");
1292 match UnifiedType::from_syn_type(&ty) {
1293 UnifiedType::Array { inner, size } => {
1294 assert_eq!(*inner, UnifiedType::Named("U32".to_string()));
1295 assert_eq!(size, Some(8));
1296 }
1297 other => panic!("expected Array, got {:?}", other),
1298 }
1299 }
1300
1301 #[test]
1302 fn test_syn_primitive_full_path() {
1303 let ty = parse_ty(":: std :: os :: raw :: c_int");
1305 assert_eq!(
1306 UnifiedType::from_syn_type(&ty),
1307 UnifiedType::Int { signed: true, size: IntSize::Int }
1308 );
1309 }
1310
1311 #[test]
1312 fn test_syn_primitive_short_path() {
1313 let ty = parse_ty("c_uchar");
1314 assert_eq!(
1315 UnifiedType::from_syn_type(&ty),
1316 UnifiedType::Char { signed: Some(false) }
1317 );
1318 }
1319
1320 #[test]
1321 fn test_syn_named_type() {
1322 let ty = parse_ty("PerlInterpreter");
1323 assert_eq!(
1324 UnifiedType::from_syn_type(&ty),
1325 UnifiedType::Named("PerlInterpreter".to_string())
1326 );
1327 }
1328
1329 #[test]
1330 fn test_syn_option_extern_c_fn_ptr() {
1331 let ty = parse_ty(
1333 ":: std :: option :: Option < unsafe extern \"C\" fn (arg1 : * mut CV) >",
1334 );
1335 let ut = UnifiedType::from_syn_type(&ty);
1336 let expected = UnifiedType::FnPtr {
1337 params: vec![UnifiedType::Pointer {
1338 inner: Box::new(UnifiedType::Named("CV".to_string())),
1339 is_const: false,
1340 }],
1341 ret: Box::new(UnifiedType::Void),
1342 abi: Some("C".to_string()),
1343 is_unsafe: true,
1344 is_optional: true,
1345 };
1346 assert_eq!(ut, expected);
1347 assert_eq!(
1349 ut.to_rust_string(),
1350 "Option<unsafe extern \"C\" fn(*mut CV)>"
1351 );
1352 }
1353
1354 #[test]
1355 fn test_syn_option_fn_with_return() {
1356 let ty = parse_ty(
1357 "Option<unsafe extern \"C\" fn(my_perl: *mut PerlInterpreter, rx: *mut REGEXP) -> *mut SV>",
1358 );
1359 let ut = UnifiedType::from_syn_type(&ty);
1360 assert!(ut.is_optional_fn_ptr());
1361 assert_eq!(
1362 ut.to_rust_string(),
1363 "Option<unsafe extern \"C\" fn(*mut PerlInterpreter, *mut REGEXP) -> *mut SV>"
1364 );
1365 }
1366
1367 #[test]
1368 fn test_syn_unsupported_falls_to_verbatim() {
1369 let ty = parse_ty("Vec<u8>");
1371 let ut = UnifiedType::from_syn_type(&ty);
1372 assert!(ut.is_verbatim());
1373 assert_eq!(ut.to_rust_string(), "Vec < u8 >");
1375 }
1376
1377 #[test]
1378 fn test_syn_paren_and_group_unwrap() {
1379 let ty = parse_ty("(*mut SV)");
1381 assert_eq!(
1382 UnifiedType::from_syn_type(&ty),
1383 UnifiedType::Pointer {
1384 inner: Box::new(UnifiedType::Named("SV".to_string())),
1385 is_const: false,
1386 }
1387 );
1388 }
1389
1390 #[test]
1391 fn test_fn_ptr_eq_hash() {
1392 let a = UnifiedType::FnPtr {
1394 params: vec![UnifiedType::Void],
1395 ret: Box::new(UnifiedType::Void),
1396 abi: Some("C".to_string()),
1397 is_unsafe: true,
1398 is_optional: true,
1399 };
1400 let b = a.clone();
1401 assert_eq!(a, b);
1402 let mut set = std::collections::HashSet::new();
1403 set.insert(a.clone());
1404 assert!(set.contains(&b));
1405 }
1406}