1use std::collections::HashMap;
2
3use serde::Serialize;
4
5use crate::model::{
6 CallExpression, DefineValue, ExportMacro, Expression, FunctionDoc, ParamSpecAssignment,
7 Property, PropertyDoc, Signal, SignalDoc, SourceLocation, Statement, TypeInfo, VariableDecl,
8};
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(untagged)]
12pub enum Parameter {
13 Regular {
14 #[serde(skip_serializing_if = "Option::is_none")]
15 name: Option<String>,
16 type_info: TypeInfo,
17 location: SourceLocation,
18 },
19 Variadic,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct FunctionDeclItem {
24 pub name: String,
25 pub return_type: TypeInfo,
26 #[serde(skip_serializing_if = "std::ops::Not::not")]
27 pub is_static: bool,
28 #[serde(skip_serializing_if = "std::ops::Not::not")]
29 pub is_inline: bool,
30 #[serde(skip_serializing_if = "Vec::is_empty")]
31 pub parameters: Vec<Parameter>,
32 #[serde(skip_serializing_if = "Vec::is_empty")]
33 pub export_macros: Vec<ExportMacro>,
34 pub location: SourceLocation,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub doc: Option<FunctionDoc>,
37}
38
39#[derive(Debug, Clone, Serialize)]
40pub struct FunctionDefItem {
41 pub name: String,
42 pub return_type: TypeInfo,
43 #[serde(skip_serializing_if = "std::ops::Not::not")]
44 pub is_static: bool,
45 #[serde(skip_serializing_if = "std::ops::Not::not")]
46 pub is_inline: bool,
47 #[serde(skip_serializing_if = "Vec::is_empty")]
48 pub parameters: Vec<Parameter>,
49 #[serde(skip_serializing_if = "Vec::is_empty")]
50 pub body_statements: Vec<Statement>,
51 pub location: SourceLocation,
52 #[serde(skip)]
53 pub body_location: Option<SourceLocation>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub doc: Option<FunctionDoc>,
56}
57
58impl FunctionDefItem {
59 pub fn find_calls<'a>(&'a self, function_names: &[&str]) -> Vec<&'a CallExpression> {
63 self.find_calls_matching(|name| function_names.contains(&name))
64 }
65
66 pub fn find_calls_matching<F>(&self, predicate: F) -> Vec<&CallExpression>
68 where
69 F: Fn(&str) -> bool,
70 {
71 let mut exprs: Vec<&Expression> = Vec::new();
72 for stmt in &self.body_statements {
73 stmt.walk_expressions(&mut |expr| exprs.push(expr));
74 }
75
76 let mut results = Vec::new();
77 for expr in exprs {
78 expr.walk(&mut |e| {
79 if let Expression::Call(call) = e
80 && call.function_name_str().is_some_and(&predicate)
81 {
82 results.push(call);
83 }
84 });
85 }
86 results
87 }
88
89 pub fn find_signal_registrations(&self, type_name: &str) -> Vec<Signal> {
93 let mut signals = Vec::new();
94 let mut seen_names = std::collections::HashSet::new();
95
96 for (i, stmt) in self.body_statements.iter().enumerate() {
98 for assignment in stmt.iter_assignments() {
99 let Expression::Call(call) = &*assignment.rhs else {
100 continue;
101 };
102 if !call.function_contains("g_signal_new") {
103 continue;
104 }
105 let Some(mut signal) = Signal::from_g_signal_new_call(call) else {
106 continue;
107 };
108 if let Expression::Subscript(sub) = &*assignment.lhs
109 && let Expression::Identifier(id) = &*sub.index
110 {
111 signal.enum_value = Some(id.name.clone());
112 }
113 if i > 0
114 && let Statement::Comment(c) = &self.body_statements[i - 1]
115 {
116 signal.doc = SignalDoc::from_comment_for(c, type_name, &signal.name);
117 }
118 seen_names.insert(signal.name.clone());
119 signals.push(signal);
120 }
121 }
122
123 for (i, stmt) in self.body_statements.iter().enumerate() {
125 for call in stmt.iter_calls() {
126 if !call.function_name().starts_with("g_signal_new") {
127 continue;
128 }
129 let Some(name) = call.extract_string_from_arg(0) else {
130 continue;
131 };
132 if seen_names.contains(&name) {
133 continue;
134 }
135 if let Some(mut signal) = Signal::from_g_signal_new_call(call) {
136 if i > 0
137 && let Statement::Comment(c) = &self.body_statements[i - 1]
138 {
139 signal.doc = SignalDoc::from_comment_for(c, type_name, &signal.name);
140 }
141 signals.push(signal);
142 }
143 }
144 }
145
146 signals
147 }
148
149 pub fn iter_local_declarations(&self) -> impl Iterator<Item = &VariableDecl> {
151 self.body_statements
152 .iter()
153 .flat_map(Statement::iter_declarations)
154 }
155
156 pub fn collect_return_values(&self) -> Vec<&Expression> {
158 self.body_statements
159 .iter()
160 .flat_map(Statement::iter_returns)
161 .filter_map(|r| r.value.as_ref())
162 .collect()
163 }
164
165 pub fn is_var_returned(&self, type_info: &TypeInfo) -> bool {
168 for stmt in &self.body_statements {
169 for ret in stmt.iter_returns() {
170 if let Some(Expression::Identifier(id)) = &ret.value {
171 for body_stmt in &self.body_statements {
173 for decl in body_stmt.iter_declarations() {
174 if decl.name == id.name
175 && decl.type_info.base_type == type_info.base_type
176 && decl.type_info.is_pointer() == type_info.is_pointer()
177 {
178 return true;
179 }
180 }
181 }
182 }
183 }
184 }
185 false
186 }
187
188 pub fn is_var_passed_to_cleanup(&self, type_info: &TypeInfo) -> bool {
191 for stmt in &self.body_statements {
192 for call in stmt.iter_calls() {
193 if call.is_cleanup_call()
194 && let Some(arg) = call.get_arg(0)
195 && let Expression::Identifier(id) = arg
196 {
197 for body_stmt in &self.body_statements {
199 for decl in body_stmt.iter_declarations() {
200 if decl.name == id.name
201 && decl.type_info.base_type == type_info.base_type
202 && decl.type_info.is_pointer() == type_info.is_pointer()
203 {
204 return true;
205 }
206 }
207 }
208 }
209 }
210 }
211 false
212 }
213
214 pub fn is_var_passed_to_function(
217 &self,
218 var_name: &str,
219 func_name: &str,
220 arg_index: usize,
221 ) -> bool {
222 self.body_statements.iter().any(|stmt| {
223 stmt.iter_calls().any(|call| {
224 call.is_function(func_name)
225 && call.get_arg(arg_index).is_some_and(
226 |arg| matches!(arg, Expression::Identifier(id) if id.name == var_name),
227 )
228 })
229 })
230 }
231
232 pub fn is_var_allocated(&self, type_info: &TypeInfo) -> bool {
236 self.is_var_allocated_with(type_info, CallExpression::is_allocation_call)
237 }
238
239 pub fn is_var_allocated_with(
242 &self,
243 type_info: &TypeInfo,
244 is_allocation: impl Fn(&CallExpression) -> bool,
245 ) -> bool {
246 for stmt in &self.body_statements {
247 let mut found = false;
248 stmt.walk(&mut |s| {
249 match s {
250 Statement::Declaration(decl)
252 if decl.type_info.base_type == type_info.base_type
253 && decl.type_info.is_pointer() == type_info.is_pointer() =>
254 {
255 let is_alloc_init = match &decl.initializer {
256 Some(Expression::Call(call)) => is_allocation(call),
257 Some(Expression::AllocCall(_)) => true,
258 _ => false,
259 };
260 if is_alloc_init {
261 found = true;
262 }
263 }
264 Statement::Expression(expr_stmt) => {
266 if let Expression::Assignment(assign) = expr_stmt.as_ref()
267 && let Expression::Identifier(id) = &*assign.lhs
268 {
269 let is_alloc_rhs = match &*assign.rhs {
270 Expression::Call(call) => is_allocation(call),
271 Expression::AllocCall(_) => true,
272 _ => false,
273 };
274 if is_alloc_rhs {
275 for body_stmt in &self.body_statements {
277 for decl in body_stmt.iter_declarations() {
278 if decl.name == id.name
279 && decl.type_info.base_type == type_info.base_type
280 && decl.type_info.is_pointer() == type_info.is_pointer()
281 {
282 found = true;
283 }
284 }
285 }
286 }
287 }
288 }
289 _ => {}
290 }
291 });
292 if found {
293 return true;
294 }
295 }
296 false
297 }
298
299 pub fn find_install_properties_calls(&self) -> Vec<&CallExpression> {
301 self.find_calls(&["g_object_class_install_properties"])
302 }
303
304 pub fn local_var_types(&self) -> std::collections::HashMap<&str, &TypeInfo> {
308 let mut map = std::collections::HashMap::new();
309 for param in &self.parameters {
310 if let Parameter::Regular {
311 name: Some(name),
312 type_info,
313 ..
314 } = param
315 {
316 map.insert(name.as_str(), type_info);
317 }
318 }
319 for stmt in &self.body_statements {
320 stmt.walk(&mut |s| {
321 if let Statement::Declaration(decl) = s {
322 map.insert(decl.name.as_str(), &decl.type_info);
323 }
324 });
325 }
326 map
327 }
328
329 pub fn get_param_by_name(&self, name: &str) -> Option<&Parameter> {
331 self.parameters
332 .iter()
333 .find(|p| matches!(p, Parameter::Regular { name: Some(n), .. } if n == name))
334 }
335
336 pub(crate) fn find_param_spec_assignments(
341 &self,
342 type_name: &str,
343 defines: &HashMap<String, DefineValue>,
344 ) -> Vec<ParamSpecAssignment> {
345 let mut assignments = Vec::new();
346 let mut array_assignments: HashMap<&str, Vec<usize>> = HashMap::new();
347 let mut variable_assignments: HashMap<&str, Vec<usize>> = HashMap::new();
348
349 for (i, stmt) in self.body_statements.iter().enumerate() {
351 stmt.walk(&mut |s| {
352 match s {
353 Statement::Declaration(decl)
355 if matches!(
356 &decl.initializer,
357 Some(Expression::Call(c)) if c.function_contains("_param_spec_")
358 ) =>
359 {
360 let Some(Expression::Call(param_call)) = &decl.initializer else {
361 unreachable!()
362 };
363 let Some(mut property) =
364 Property::from_param_spec_call(param_call, defines)
365 else {
366 return;
367 };
368 if i > 0
369 && let Statement::Comment(c) = &self.body_statements[i - 1]
370 {
371 property.doc =
372 PropertyDoc::from_comment_for(c, type_name, &property.name);
373 }
374 let idx = assignments.len();
375 variable_assignments
376 .entry(&*decl.name)
377 .or_default()
378 .push(idx);
379 assignments.push(ParamSpecAssignment::Variable {
380 variable_name: decl.name.clone(),
381 statement_location: s.location().clone(),
382 call: param_call.clone(),
383 property,
384 install_call: None,
385 });
386 }
387 Statement::Expression(expr_stmt) => {
388 match expr_stmt.as_ref() {
389 Expression::Assignment(assignment) => {
392 if let Expression::Call(param_call) = &*assignment.rhs {
393 let func_name = param_call.function_name();
394 if !func_name.contains("_param_spec_") {
395 return;
396 }
397
398 let Some(mut property) =
399 Property::from_param_spec_call(param_call, defines)
400 else {
401 return;
402 };
403 if i > 0
404 && let Statement::Comment(c) = &self.body_statements[i - 1]
405 {
406 property.doc = PropertyDoc::from_comment_for(
407 c,
408 type_name,
409 &property.name,
410 );
411 }
412
413 if let Expression::Subscript(subscript) = &*assignment.lhs {
415 if let Some(array_name) =
416 subscript.array.location().as_str()
417 && let Some(enum_value) =
418 subscript.index.location().as_str()
419 {
420 let idx = assignments.len();
421 array_assignments
422 .entry(array_name)
423 .or_default()
424 .push(idx);
425 assignments.push(ParamSpecAssignment::ArraySubscript {
426 array_name: array_name.to_owned(),
427 enum_value: enum_value.to_owned(),
428 statement_location: s.location().clone(),
429 call: param_call.clone(),
430 property,
431 install_call: None,
432 });
433 }
434 } else if let Some(var_name) =
435 assignment.lhs.location().as_str()
436 {
437 let idx = assignments.len();
438 variable_assignments.entry(var_name).or_default().push(idx);
439 assignments.push(ParamSpecAssignment::Variable {
440 variable_name: var_name.to_owned(),
441 statement_location: s.location().clone(),
442 call: param_call.clone(),
443 property,
444 install_call: None,
445 });
446 }
447 }
448 }
449 Expression::Call(call) => {
451 if call.function_contains("override_property")
452 && let Some(mut property) =
453 Property::from_override_property_call(call, defines)
454 && let Some(enum_arg) = call.get_arg(1)
455 && let Some(enum_value) = enum_arg.location().as_str()
456 {
457 if i > 0
458 && let Statement::Comment(c) = &self.body_statements[i - 1]
459 {
460 property.doc = PropertyDoc::from_comment_for(
461 c,
462 type_name,
463 &property.name,
464 );
465 }
466 assignments.push(ParamSpecAssignment::OverrideProperty {
467 enum_value: enum_value.to_owned(),
468 statement_location: s.location().clone(),
469 call: call.clone(),
470 property,
471 });
472 }
473 }
474 _ => {}
475 }
476 }
477 _ => {}
478 }
479 });
480 }
481
482 for (i, stmt) in self.body_statements.iter().enumerate() {
484 stmt.walk(&mut |s| {
485 if let Statement::Expression(expr_stmt) = s
486 && let Expression::Call(call) = expr_stmt.as_ref()
487 {
488 if call.function_contains("install_properties") {
490 if let Some(array_arg) = call.get_arg(2)
491 && let Some(array_name) = array_arg.location().as_str()
492 && let Some(indices) = array_assignments.get(&array_name)
493 {
494 for &idx in indices {
495 if let ParamSpecAssignment::ArraySubscript {
496 install_call, ..
497 } = &mut assignments[idx]
498 {
499 *install_call = Some(call.clone());
500 }
501 }
502 }
503 }
504 else if call.function_contains("install_property") {
507 let is_interface = call.function_contains("interface_install_property");
508 let spec_arg_idx = if is_interface { 1 } else { 2 };
509
510 if let Some(spec_expr) = call.get_arg(spec_arg_idx) {
511 if let Expression::Call(spec_call) = spec_expr
512 && spec_call.function_contains("_param_spec_")
513 && let Some(mut property) =
514 Property::from_param_spec_call(spec_call, defines)
515 {
516 let enum_value = if is_interface {
517 String::new()
518 } else if let Some(enum_arg) = call.get_arg(1)
519 && let Some(ev) = enum_arg.location().as_str()
520 {
521 ev.to_owned()
522 } else {
523 return;
524 };
525
526 if i > 0
527 && let Statement::Comment(c) = &self.body_statements[i - 1]
528 {
529 property.doc = PropertyDoc::from_comment(c);
530 }
531 assignments.push(ParamSpecAssignment::DirectInstall {
532 enum_value,
533 statement_location: s.location().clone(),
534 call: spec_call.clone(),
535 property,
536 install_call: call.clone(),
537 });
538 } else if let Some(var_name) = spec_expr.location().as_str()
539 && let Some(indices) = variable_assignments.get(var_name)
540 {
541 let indices = indices.clone();
542 for idx in indices {
543 if let ParamSpecAssignment::Variable { install_call, .. } =
544 &mut assignments[idx]
545 && install_call.is_none()
546 {
547 *install_call = Some(call.clone());
548 break;
549 }
550 }
551 }
552 }
553 }
554 }
555 });
556 }
557
558 assignments.sort_by_key(|a| a.statement_location().start_byte);
559 assignments
560 }
561}