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 #[serde(skip)]
84 pub full_text: String,
85 pub location: SourceLocation,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub auto_cleanup: Option<AutoCleanupMacro>,
88}
89
90impl TypeInfo {
91 pub fn new(type_string: &str, location: SourceLocation) -> Self {
92 let trimmed = type_string.trim();
93 let auto_cleanup = Self::parse_auto_cleanup(trimmed);
94
95 let parts: Vec<&str> = trimmed.split_whitespace().collect();
96 let mut filtered_parts: Vec<&str> = Vec::new();
97 let mut is_const = false;
98
99 let macro_name = auto_cleanup.as_ref().map(AutoCleanupMacro::name);
100 let mut is_volatile = false;
101
102 for part in &parts {
103 match *part {
104 "static" | "extern" | "inline" => {}
105 "const" => {
106 is_const = true;
107 filtered_parts.push(part);
108 }
109 "volatile" => {
110 is_volatile = true;
111 filtered_parts.push(part);
112 }
113 _ if macro_name
117 .is_some_and(|name| part.starts_with(name) || part.starts_with('(')) => {}
118 _ => {
119 filtered_parts.push(part);
120 }
121 }
122 }
123
124 let cleaned = filtered_parts.join(" ");
125
126 let without_qualifiers: String = cleaned
128 .split_whitespace()
129 .filter(|&w| w != "const" && w != "volatile")
130 .collect::<Vec<_>>()
131 .join(" ");
132
133 let pointer_depth = without_qualifiers.chars().filter(|&c| c == '*').count();
134
135 let raw_base = without_qualifiers.replace('*', "").trim().to_string();
136 let (base_type, is_struct, is_union) = if let Some(ref auto) = auto_cleanup {
137 if let Some(type_arg) = auto.type_arg() {
138 (type_arg.to_owned(), false, false)
139 } else {
140 Self::extract_base_type(&raw_base)
141 }
142 } else {
143 Self::extract_base_type(&raw_base)
144 };
145
146 Self {
147 base_type,
148 is_const,
149 is_volatile,
150 is_struct,
151 is_union,
152 pointer_depth,
153 full_text: cleaned,
154 location,
155 auto_cleanup,
156 }
157 }
158
159 fn extract_base_type(raw_base: &str) -> (String, bool, bool) {
160 if let Some(rest) = raw_base.strip_prefix("struct ") {
161 (rest.trim().to_string(), true, false)
162 } else if let Some(rest) = raw_base.strip_prefix("union ") {
163 (rest.trim().to_string(), false, true)
164 } else {
165 (raw_base.to_string(), false, false)
166 }
167 }
168
169 fn parse_auto_cleanup(type_str: &str) -> Option<AutoCleanupMacro> {
170 let try_with_arg = |macro_name: &str| -> Option<String> {
171 let pos = type_str.find(macro_name)?;
172 let after_name = type_str[pos + macro_name.len()..].trim_start();
173 if !after_name.starts_with('(') {
174 return None;
175 }
176 let inner = &after_name[1..];
177 let end = inner.find(')')?;
178 Some(inner[..end].trim().to_string())
179 };
180
181 if type_str.contains("g_autofree") {
182 Some(AutoCleanupMacro::Autofree)
183 } else if let Some(t) = try_with_arg("g_autoptr") {
184 Some(AutoCleanupMacro::Autoptr(t))
185 } else if let Some(t) = try_with_arg("g_autolist") {
186 Some(AutoCleanupMacro::Autolist(t))
187 } else if let Some(t) = try_with_arg("g_autoslist") {
188 Some(AutoCleanupMacro::Autoslist(t))
189 } else if let Some(t) = try_with_arg("g_autoqueue") {
190 Some(AutoCleanupMacro::Autoqueue(t))
191 } else {
192 try_with_arg("g_auto").map(AutoCleanupMacro::Auto)
193 }
194 }
195
196 pub fn is_pointer(&self) -> bool {
198 self.pointer_depth > 0
199 }
200
201 pub fn base_type_name(&self) -> &str {
203 &self.base_type
204 }
205
206 pub fn is_base_type(&self, name: &str) -> bool {
208 self.base_type == name
209 }
210
211 pub fn contains(&self, pattern: &str) -> bool {
213 self.full_text.contains(pattern)
214 }
215
216 pub fn uses_auto_cleanup(&self) -> bool {
219 self.auto_cleanup.is_some()
220 }
221
222 pub fn normalized_base_type(&self) -> &str {
224 match self.base_type.as_str() {
225 "gint" => "int",
226 "guint" => "unsigned int",
227 "glong" => "long",
228 "gulong" => "unsigned long",
229 "gshort" => "short",
230 "gushort" => "unsigned short",
231 "gchar" => "char",
232 "guchar" => "unsigned char",
233 "gfloat" => "float",
234 "gdouble" => "double",
235 other => other,
236 }
237 }
238
239 pub fn matches(&self, other: &Self) -> bool {
242 self.normalized_base_type() == other.normalized_base_type()
243 && self.pointer_depth == other.pointer_depth
244 && self.is_const == other.is_const
245 }
246
247 pub fn as_basic(&self) -> Option<BasicType> {
252 match (self.base_type.as_str(), self.pointer_depth) {
253 ("gboolean", 0) => Some(BasicType::Boolean),
254 ("gchar" | "char", 0) => Some(BasicType::Char),
255 ("guchar" | "unsigned char", 0) => Some(BasicType::UChar),
256 ("gint" | "int" | "signed" | "signed int", 0) => Some(BasicType::Int),
257 ("guint" | "unsigned int" | "unsigned", 0) => Some(BasicType::UInt),
258 ("glong" | "long" | "signed long" | "long int" | "signed long int", 0) => {
259 Some(BasicType::Long)
260 }
261 ("gulong" | "unsigned long" | "unsigned long int", 0) => Some(BasicType::ULong),
262 ("gint64" | "int64_t", 0) => Some(BasicType::Int64),
263 ("guint64" | "uint64_t", 0) => Some(BasicType::UInt64),
264 ("gfloat" | "float", 0) => Some(BasicType::Float),
265 ("gdouble" | "double", 0) => Some(BasicType::Double),
266 ("gchar" | "char", 1) => Some(BasicType::String),
267 ("gpointer" | "void", 1) => Some(BasicType::Pointer),
268 ("_Bool" | "bool", 0) => Some(BasicType::Bool),
270 ("gshort" | "short" | "signed short" | "short int" | "signed short int", 0) => {
271 Some(BasicType::Short)
272 }
273 ("gushort" | "unsigned short" | "unsigned short int", 0) => Some(BasicType::UShort),
274 ("long long" | "signed long long" | "long long int" | "signed long long int", 0) => {
275 Some(BasicType::LongLong)
276 }
277 ("unsigned long long" | "unsigned long long int", 0) => Some(BasicType::ULongLong),
278 ("long double", 0) => Some(BasicType::LongDouble),
279 ("gint8" | "int8_t" | "signed char", 0) => Some(BasicType::Int8),
280 ("guint8" | "uint8_t", 0) => Some(BasicType::UInt8),
281 ("gint16" | "int16_t", 0) => Some(BasicType::Int16),
282 ("guint16" | "uint16_t", 0) => Some(BasicType::UInt16),
283 ("gint32" | "int32_t", 0) => Some(BasicType::Int32),
284 ("guint32" | "uint32_t", 0) => Some(BasicType::UInt32),
285 ("gsize" | "size_t", 0) => Some(BasicType::Size),
286 ("gssize" | "ssize_t", 0) => Some(BasicType::SSize),
287 ("goffset", 0) => Some(BasicType::Offset),
288 ("gintptr" | "intptr_t", 0) => Some(BasicType::IntPtr),
289 ("guintptr" | "uintptr_t", 0) => Some(BasicType::UIntPtr),
290 ("gconstpointer", 0) => Some(BasicType::Pointer),
292 _ => None,
293 }
294 }
295
296 pub fn is_basic(&self) -> bool {
297 matches!(
298 self.base_type.as_str(),
299 "gboolean"
300 | "gchar"
301 | "char"
302 | "guchar"
303 | "unsigned char"
304 | "gint"
305 | "int"
306 | "signed"
307 | "signed int"
308 | "guint"
309 | "unsigned int"
310 | "unsigned"
311 | "glong"
312 | "long"
313 | "signed long"
314 | "long int"
315 | "signed long int"
316 | "gulong"
317 | "unsigned long"
318 | "unsigned long int"
319 | "gint64"
320 | "int64_t"
321 | "guint64"
322 | "uint64_t"
323 | "gfloat"
324 | "float"
325 | "gdouble"
326 | "double"
327 | "gpointer"
328 | "void"
329 | "gconstpointer"
330 | "_Bool"
331 | "bool"
332 | "gshort"
333 | "short"
334 | "signed short"
335 | "short int"
336 | "signed short int"
337 | "gushort"
338 | "unsigned short"
339 | "unsigned short int"
340 | "long long"
341 | "signed long long"
342 | "long long int"
343 | "signed long long int"
344 | "unsigned long long"
345 | "unsigned long long int"
346 | "long double"
347 | "gint8"
348 | "int8_t"
349 | "signed char"
350 | "guint8"
351 | "uint8_t"
352 | "gint16"
353 | "int16_t"
354 | "guint16"
355 | "uint16_t"
356 | "gint32"
357 | "int32_t"
358 | "guint32"
359 | "uint32_t"
360 | "gsize"
361 | "size_t"
362 | "gssize"
363 | "ssize_t"
364 | "goffset"
365 | "gintptr"
366 | "intptr_t"
367 | "guintptr"
368 | "uintptr_t"
369 )
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_parse_type_info() {
379 let t = TypeInfo::new("g_autofree char *", SourceLocation::default());
380 assert_eq!(t.auto_cleanup, Some(AutoCleanupMacro::Autofree));
381 assert_eq!(t.base_type, "char");
382 assert_eq!(t.pointer_depth, 1);
383
384 let t = TypeInfo::new(
385 "g_autofree FuZipFirmwareWriteItem *",
386 SourceLocation::default(),
387 );
388 assert_eq!(t.auto_cleanup, Some(AutoCleanupMacro::Autofree));
389 assert_eq!(t.base_type, "FuZipFirmwareWriteItem");
390
391 let t = TypeInfo::new("const g_autofree char *", SourceLocation::default());
392 assert_eq!(t.auto_cleanup, Some(AutoCleanupMacro::Autofree));
393 assert!(t.is_const);
394 assert_eq!(t.base_type, "char");
395
396 let t = TypeInfo::new("g_autoptr(GFile)", SourceLocation::default());
397 assert_eq!(
398 t.auto_cleanup,
399 Some(AutoCleanupMacro::Autoptr("GFile".into()))
400 );
401 assert_eq!(t.base_type, "GFile");
402
403 let t = TypeInfo::new("g_autoptr (GFile)", SourceLocation::default());
404 assert_eq!(
405 t.auto_cleanup,
406 Some(AutoCleanupMacro::Autoptr("GFile".into()))
407 );
408 assert_eq!(t.base_type, "GFile");
409
410 let t = TypeInfo::new(
411 "g_autoptr (GdmConfigCommandHandler)",
412 SourceLocation::default(),
413 );
414 assert_eq!(
415 t.auto_cleanup,
416 Some(AutoCleanupMacro::Autoptr("GdmConfigCommandHandler".into()))
417 );
418 assert_eq!(t.base_type, "GdmConfigCommandHandler");
419
420 let t = TypeInfo::new("g_auto(GString)", SourceLocation::default());
421 assert_eq!(
422 t.auto_cleanup,
423 Some(AutoCleanupMacro::Auto("GString".into()))
424 );
425 assert_eq!(t.base_type, "GString");
426
427 let t = TypeInfo::new("g_auto (GString)", SourceLocation::default());
428 assert_eq!(
429 t.auto_cleanup,
430 Some(AutoCleanupMacro::Auto("GString".into()))
431 );
432 assert_eq!(t.base_type, "GString");
433
434 let t = TypeInfo::new("g_autolist(GFile)", SourceLocation::default());
435 assert_eq!(
436 t.auto_cleanup,
437 Some(AutoCleanupMacro::Autolist("GFile".into()))
438 );
439 assert_eq!(t.base_type, "GFile");
440
441 let t = TypeInfo::new("g_autolist (GFile)", SourceLocation::default());
442 assert_eq!(
443 t.auto_cleanup,
444 Some(AutoCleanupMacro::Autolist("GFile".into()))
445 );
446 assert_eq!(t.base_type, "GFile");
447
448 let t = TypeInfo::new("g_autoslist(GFile)", SourceLocation::default());
449 assert_eq!(
450 t.auto_cleanup,
451 Some(AutoCleanupMacro::Autoslist("GFile".into()))
452 );
453 assert_eq!(t.base_type, "GFile");
454
455 let t = TypeInfo::new("g_autoslist (GFile)", SourceLocation::default());
456 assert_eq!(
457 t.auto_cleanup,
458 Some(AutoCleanupMacro::Autoslist("GFile".into()))
459 );
460 assert_eq!(t.base_type, "GFile");
461
462 let t = TypeInfo::new("g_autoqueue(GFile)", SourceLocation::default());
463 assert_eq!(
464 t.auto_cleanup,
465 Some(AutoCleanupMacro::Autoqueue("GFile".into()))
466 );
467 assert_eq!(t.base_type, "GFile");
468
469 let t = TypeInfo::new("g_autoqueue (GFile)", SourceLocation::default());
470 assert_eq!(
471 t.auto_cleanup,
472 Some(AutoCleanupMacro::Autoqueue("GFile".into()))
473 );
474 assert_eq!(t.base_type, "GFile");
475
476 let t = TypeInfo::new("char *", SourceLocation::default());
477 assert_eq!(t.auto_cleanup, None);
478 assert_eq!(t.base_type, "char");
479 assert_eq!(t.pointer_depth, 1);
480 }
481}