1use serde::Serialize;
2
3fn is_zero(v: &usize) -> bool {
4 *v == 0
5}
6
7use crate::model::{SourceLocation, types::BasicType};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
10#[serde(rename_all = "snake_case")]
11pub enum AutoCleanupMacro {
12 Autoptr(String),
14 Auto(String),
16 Autofree,
18 Autolist(String),
20 Autoslist(String),
22 Autoqueue(String),
24}
25
26impl AutoCleanupMacro {
27 pub fn name(&self) -> &'static str {
29 match self {
30 Self::Autoptr(_) => "g_autoptr",
31 Self::Auto(_) => "g_auto",
32 Self::Autofree => "g_autofree",
33 Self::Autolist(_) => "g_autolist",
34 Self::Autoslist(_) => "g_autoslist",
35 Self::Autoqueue(_) => "g_autoqueue",
36 }
37 }
38
39 pub fn type_arg(&self) -> Option<&str> {
41 match self {
42 Self::Autoptr(t)
43 | Self::Auto(t)
44 | Self::Autolist(t)
45 | Self::Autoslist(t)
46 | Self::Autoqueue(t) => Some(t),
47 Self::Autofree => None,
48 }
49 }
50}
51
52impl std::fmt::Display for AutoCleanupMacro {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Self::Autoptr(t) => f.write_fmt(format_args!("g_autoptr({t})")),
56 Self::Auto(t) => f.write_fmt(format_args!("g_auto({t})")),
57 Self::Autofree => f.write_str("g_autofree"),
58 Self::Autolist(t) => f.write_fmt(format_args!("g_autolist({t})")),
59 Self::Autoslist(t) => f.write_fmt(format_args!("g_autoslist({t})")),
60 Self::Autoqueue(t) => f.write_fmt(format_args!("g_autoqueue({t})")),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize)]
66pub struct TypeInfo {
67 pub base_type: String,
69 #[serde(skip_serializing_if = "std::ops::Not::not")]
70 pub is_const: bool,
71 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
72 pub is_volatile: bool,
73 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
75 pub is_struct: bool,
76 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
78 pub is_union: bool,
79 #[serde(skip_serializing_if = "is_zero")]
81 pub pointer_depth: usize,
82 pub location: SourceLocation,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub auto_cleanup: Option<AutoCleanupMacro>,
85}
86
87impl TypeInfo {
88 pub fn new(type_string: &str, location: SourceLocation) -> Self {
89 let trimmed = type_string.trim();
90 let auto_cleanup = Self::parse_auto_cleanup(trimmed);
91
92 let parts: Vec<&str> = trimmed.split_whitespace().collect();
93 let mut filtered_parts: Vec<&str> = Vec::new();
94 let mut is_const = false;
95
96 let macro_name = auto_cleanup.as_ref().map(AutoCleanupMacro::name);
97 let mut is_volatile = false;
98
99 for part in &parts {
100 match *part {
101 "static" | "extern" | "inline" => {}
102 "const" => {
103 is_const = true;
104 filtered_parts.push(part);
105 }
106 "volatile" => {
107 is_volatile = true;
108 filtered_parts.push(part);
109 }
110 _ if macro_name
114 .is_some_and(|name| part.starts_with(name) || part.starts_with('(')) => {}
115 _ => {
116 filtered_parts.push(part);
117 }
118 }
119 }
120
121 let cleaned = filtered_parts.join(" ");
122
123 let without_qualifiers: String = cleaned
125 .split_whitespace()
126 .filter(|&w| w != "const" && w != "volatile")
127 .collect::<Vec<_>>()
128 .join(" ");
129
130 let pointer_depth = without_qualifiers.chars().filter(|&c| c == '*').count();
131
132 let raw_base = without_qualifiers.replace('*', "").trim().to_string();
133 let (base_type, is_struct, is_union) = if let Some(ref auto) = auto_cleanup {
134 if let Some(type_arg) = auto.type_arg() {
135 (type_arg.to_owned(), false, false)
136 } else {
137 Self::extract_base_type(&raw_base)
138 }
139 } else {
140 Self::extract_base_type(&raw_base)
141 };
142
143 Self {
144 base_type,
145 is_const,
146 is_volatile,
147 is_struct,
148 is_union,
149 pointer_depth,
150 location,
151 auto_cleanup,
152 }
153 }
154
155 fn extract_base_type(raw_base: &str) -> (String, bool, bool) {
156 if let Some(rest) = raw_base.strip_prefix("struct ") {
157 (rest.trim().to_string(), true, false)
158 } else if let Some(rest) = raw_base.strip_prefix("union ") {
159 (rest.trim().to_string(), false, true)
160 } else {
161 (raw_base.to_string(), false, false)
162 }
163 }
164
165 pub(crate) fn parse_auto_cleanup(type_str: &str) -> Option<AutoCleanupMacro> {
166 let try_with_arg = |macro_name: &str| -> Option<String> {
167 let pos = type_str.find(macro_name)?;
168 let after_name = type_str[pos + macro_name.len()..].trim_start();
169 if !after_name.starts_with('(') {
170 return None;
171 }
172 let inner = &after_name[1..];
173 let end = inner.find(')')?;
174 Some(inner[..end].trim().to_string())
175 };
176
177 if type_str.contains("g_autofree") {
178 Some(AutoCleanupMacro::Autofree)
179 } else if let Some(t) = try_with_arg("g_autoptr") {
180 Some(AutoCleanupMacro::Autoptr(t))
181 } else if let Some(t) = try_with_arg("g_autolist") {
182 Some(AutoCleanupMacro::Autolist(t))
183 } else if let Some(t) = try_with_arg("g_autoslist") {
184 Some(AutoCleanupMacro::Autoslist(t))
185 } else if let Some(t) = try_with_arg("g_autoqueue") {
186 Some(AutoCleanupMacro::Autoqueue(t))
187 } else {
188 try_with_arg("g_auto").map(AutoCleanupMacro::Auto)
189 }
190 }
191
192 pub fn is_pointer(&self) -> bool {
194 self.pointer_depth > 0
195 }
196
197 pub fn base_type_name(&self) -> &str {
199 &self.base_type
200 }
201
202 pub fn is_base_type(&self, name: &str) -> bool {
204 self.base_type == name
205 }
206
207 pub fn display_name(&self) -> String {
210 let mut s = String::new();
211 if self.is_const {
212 s.push_str("const ");
213 }
214 if self.is_struct {
215 s.push_str("struct ");
216 } else if self.is_union {
217 s.push_str("union ");
218 }
219 s.push_str(&self.base_type);
220 if self.pointer_depth > 0 {
221 s.push(' ');
222 for _ in 0..self.pointer_depth {
223 s.push('*');
224 }
225 }
226 s
227 }
228
229 pub fn uses_auto_cleanup(&self) -> bool {
232 self.auto_cleanup.is_some()
233 }
234
235 pub fn normalized_base_type(&self) -> &str {
237 match self.base_type.as_str() {
238 "gint" => "int",
239 "guint" => "unsigned int",
240 "glong" => "long",
241 "gulong" => "unsigned long",
242 "gshort" => "short",
243 "gushort" => "unsigned short",
244 "gchar" => "char",
245 "guchar" => "unsigned char",
246 "gfloat" => "float",
247 "gdouble" => "double",
248 other => other,
249 }
250 }
251
252 pub fn matches(&self, other: &Self) -> bool {
255 self.normalized_base_type() == other.normalized_base_type()
256 && self.pointer_depth == other.pointer_depth
257 && self.is_const == other.is_const
258 }
259
260 pub fn as_basic(&self) -> Option<BasicType> {
265 match (self.base_type.as_str(), self.pointer_depth) {
268 ("gchar" | "char", 1) => return Some(BasicType::String),
269 ("gpointer" | "void", 1) => return Some(BasicType::Pointer),
270 ("gconstpointer", 0) => return Some(BasicType::Pointer),
271 _ => {}
272 }
273
274 match self.base_type.as_str() {
276 "gboolean" => Some(BasicType::Boolean),
277 "gchar" | "char" => Some(BasicType::Char),
278 "guchar" | "unsigned char" => Some(BasicType::UChar),
279 "gint" | "int" | "signed" | "signed int" => Some(BasicType::Int),
280 "guint" | "unsigned int" | "unsigned" => Some(BasicType::UInt),
281 "glong" | "long" | "signed long" | "long int" | "signed long int" => {
282 Some(BasicType::Long)
283 }
284 "gulong" | "unsigned long" | "unsigned long int" => Some(BasicType::ULong),
285 "gint64" | "int64_t" => Some(BasicType::Int64),
286 "guint64" | "uint64_t" => Some(BasicType::UInt64),
287 "gfloat" | "float" => Some(BasicType::Float),
288 "gdouble" | "double" => Some(BasicType::Double),
289 "_Bool" | "bool" => Some(BasicType::Bool),
290 "gshort" | "short" | "signed short" | "short int" | "signed short int" => {
291 Some(BasicType::Short)
292 }
293 "gushort" | "unsigned short" | "unsigned short int" => Some(BasicType::UShort),
294 "long long" | "signed long long" | "long long int" | "signed long long int" => {
295 Some(BasicType::LongLong)
296 }
297 "unsigned long long" | "unsigned long long int" => Some(BasicType::ULongLong),
298 "long double" => Some(BasicType::LongDouble),
299 "gint8" | "int8_t" | "signed char" => Some(BasicType::Int8),
300 "guint8" | "uint8_t" => Some(BasicType::UInt8),
301 "gint16" | "int16_t" => Some(BasicType::Int16),
302 "guint16" | "uint16_t" => Some(BasicType::UInt16),
303 "gint32" | "int32_t" => Some(BasicType::Int32),
304 "guint32" | "uint32_t" => Some(BasicType::UInt32),
305 "gsize" | "size_t" => Some(BasicType::Size),
306 "gssize" | "ssize_t" => Some(BasicType::SSize),
307 "goffset" => Some(BasicType::Offset),
308 "gintptr" | "intptr_t" => Some(BasicType::IntPtr),
309 "guintptr" | "uintptr_t" => Some(BasicType::UIntPtr),
310 _ => None,
311 }
312 }
313
314 pub fn is_basic(&self) -> bool {
315 matches!(
316 self.base_type.as_str(),
317 "gboolean"
318 | "gchar"
319 | "char"
320 | "guchar"
321 | "unsigned char"
322 | "gint"
323 | "int"
324 | "signed"
325 | "signed int"
326 | "guint"
327 | "unsigned int"
328 | "unsigned"
329 | "glong"
330 | "long"
331 | "signed long"
332 | "long int"
333 | "signed long int"
334 | "gulong"
335 | "unsigned long"
336 | "unsigned long int"
337 | "gint64"
338 | "int64_t"
339 | "guint64"
340 | "uint64_t"
341 | "gfloat"
342 | "float"
343 | "gdouble"
344 | "double"
345 | "gpointer"
346 | "void"
347 | "gconstpointer"
348 | "_Bool"
349 | "bool"
350 | "gshort"
351 | "short"
352 | "signed short"
353 | "short int"
354 | "signed short int"
355 | "gushort"
356 | "unsigned short"
357 | "unsigned short int"
358 | "long long"
359 | "signed long long"
360 | "long long int"
361 | "signed long long int"
362 | "unsigned long long"
363 | "unsigned long long int"
364 | "long double"
365 | "gint8"
366 | "int8_t"
367 | "signed char"
368 | "guint8"
369 | "uint8_t"
370 | "gint16"
371 | "int16_t"
372 | "guint16"
373 | "uint16_t"
374 | "gint32"
375 | "int32_t"
376 | "guint32"
377 | "uint32_t"
378 | "gsize"
379 | "size_t"
380 | "gssize"
381 | "ssize_t"
382 | "goffset"
383 | "gintptr"
384 | "intptr_t"
385 | "guintptr"
386 | "uintptr_t"
387 )
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn test_parse_type_info() {
397 let t = TypeInfo::new("g_autofree char *", SourceLocation::default());
398 assert_eq!(t.auto_cleanup, Some(AutoCleanupMacro::Autofree));
399 assert_eq!(t.base_type, "char");
400 assert_eq!(t.pointer_depth, 1);
401
402 let t = TypeInfo::new(
403 "g_autofree FuZipFirmwareWriteItem *",
404 SourceLocation::default(),
405 );
406 assert_eq!(t.auto_cleanup, Some(AutoCleanupMacro::Autofree));
407 assert_eq!(t.base_type, "FuZipFirmwareWriteItem");
408
409 let t = TypeInfo::new("const g_autofree char *", SourceLocation::default());
410 assert_eq!(t.auto_cleanup, Some(AutoCleanupMacro::Autofree));
411 assert!(t.is_const);
412 assert_eq!(t.base_type, "char");
413
414 let t = TypeInfo::new("g_autoptr(GFile)", SourceLocation::default());
415 assert_eq!(
416 t.auto_cleanup,
417 Some(AutoCleanupMacro::Autoptr("GFile".into()))
418 );
419 assert_eq!(t.base_type, "GFile");
420
421 let t = TypeInfo::new("g_autoptr (GFile)", SourceLocation::default());
422 assert_eq!(
423 t.auto_cleanup,
424 Some(AutoCleanupMacro::Autoptr("GFile".into()))
425 );
426 assert_eq!(t.base_type, "GFile");
427
428 let t = TypeInfo::new(
429 "g_autoptr (GdmConfigCommandHandler)",
430 SourceLocation::default(),
431 );
432 assert_eq!(
433 t.auto_cleanup,
434 Some(AutoCleanupMacro::Autoptr("GdmConfigCommandHandler".into()))
435 );
436 assert_eq!(t.base_type, "GdmConfigCommandHandler");
437
438 let t = TypeInfo::new("g_auto(GString)", SourceLocation::default());
439 assert_eq!(
440 t.auto_cleanup,
441 Some(AutoCleanupMacro::Auto("GString".into()))
442 );
443 assert_eq!(t.base_type, "GString");
444
445 let t = TypeInfo::new("g_auto (GString)", SourceLocation::default());
446 assert_eq!(
447 t.auto_cleanup,
448 Some(AutoCleanupMacro::Auto("GString".into()))
449 );
450 assert_eq!(t.base_type, "GString");
451
452 let t = TypeInfo::new("g_autolist(GFile)", SourceLocation::default());
453 assert_eq!(
454 t.auto_cleanup,
455 Some(AutoCleanupMacro::Autolist("GFile".into()))
456 );
457 assert_eq!(t.base_type, "GFile");
458
459 let t = TypeInfo::new("g_autolist (GFile)", SourceLocation::default());
460 assert_eq!(
461 t.auto_cleanup,
462 Some(AutoCleanupMacro::Autolist("GFile".into()))
463 );
464 assert_eq!(t.base_type, "GFile");
465
466 let t = TypeInfo::new("g_autoslist(GFile)", SourceLocation::default());
467 assert_eq!(
468 t.auto_cleanup,
469 Some(AutoCleanupMacro::Autoslist("GFile".into()))
470 );
471 assert_eq!(t.base_type, "GFile");
472
473 let t = TypeInfo::new("g_autoslist (GFile)", SourceLocation::default());
474 assert_eq!(
475 t.auto_cleanup,
476 Some(AutoCleanupMacro::Autoslist("GFile".into()))
477 );
478 assert_eq!(t.base_type, "GFile");
479
480 let t = TypeInfo::new("g_autoqueue(GFile)", SourceLocation::default());
481 assert_eq!(
482 t.auto_cleanup,
483 Some(AutoCleanupMacro::Autoqueue("GFile".into()))
484 );
485 assert_eq!(t.base_type, "GFile");
486
487 let t = TypeInfo::new("g_autoqueue (GFile)", SourceLocation::default());
488 assert_eq!(
489 t.auto_cleanup,
490 Some(AutoCleanupMacro::Autoqueue("GFile".into()))
491 );
492 assert_eq!(t.base_type, "GFile");
493
494 let t = TypeInfo::new("char *", SourceLocation::default());
495 assert_eq!(t.auto_cleanup, None);
496 assert_eq!(t.base_type, "char");
497 assert_eq!(t.pointer_depth, 1);
498 }
499}