Skip to main content

gobject_ast/model/types/
signal.rs

1use serde::Serialize;
2
3use crate::model::{
4    doc::SignalDoc,
5    expression::{CallExpression, Expression, StructField},
6    types::GType,
7};
8
9/// Represents a GObject signal registration
10///
11/// Parsed from g_signal_new/g_signal_newv calls:
12/// ```c
13/// g_signal_new (const gchar *signal_name,
14///               GType itype,
15///               GSignalFlags signal_flags,
16///               guint class_offset,
17///               GSignalAccumulator accumulator,
18///               gpointer accu_data,
19///               GSignalCMarshaller c_marshaller,
20///               GType return_type,
21///               guint n_params,
22///               ...);
23/// ```
24#[derive(Debug, Clone, Serialize)]
25pub struct Signal {
26    pub name: String,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub enum_value: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub doc: Option<SignalDoc>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub itype: Option<String>, // G_TYPE_FROM_CLASS(klass), G_OBJECT_TYPE, etc.
33    #[serde(skip_serializing_if = "Vec::is_empty")]
34    pub flags: Vec<SignalFlag>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub class_offset: Option<StructField>, // None when 0 (no default handler)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub accumulator: Option<String>, // function name or NULL
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub accu_data: Option<String>, // data or NULL
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub c_marshaller: Option<String>, // marshaller or NULL
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub return_type: Option<GType>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub n_params: Option<i64>,
47    #[serde(skip_serializing_if = "Vec::is_empty")]
48    pub param_types: Vec<GType>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
52#[serde(rename_all = "snake_case")]
53pub enum SignalFlag {
54    RunFirst,
55    RunLast,
56    RunCleanup,
57    NoRecurse,
58    Detailed,
59    Action,
60    NoHooks,
61    MustCollect,
62    Deprecated,
63    AccumulatorFirstRun,
64    Unknown(String),
65}
66
67impl SignalFlag {
68    pub fn from_identifier(s: &str) -> Self {
69        match s {
70            "G_SIGNAL_RUN_FIRST" => Self::RunFirst,
71            "G_SIGNAL_RUN_LAST" => Self::RunLast,
72            "G_SIGNAL_RUN_CLEANUP" => Self::RunCleanup,
73            "G_SIGNAL_NO_RECURSE" => Self::NoRecurse,
74            "G_SIGNAL_DETAILED" => Self::Detailed,
75            "G_SIGNAL_ACTION" => Self::Action,
76            "G_SIGNAL_NO_HOOKS" => Self::NoHooks,
77            "G_SIGNAL_MUST_COLLECT" => Self::MustCollect,
78            "G_SIGNAL_DEPRECATED" => Self::Deprecated,
79            "G_SIGNAL_ACCUMULATOR_FIRST_RUN" => Self::AccumulatorFirstRun,
80            _ => Self::Unknown(s.to_owned()),
81        }
82    }
83
84    pub fn as_str(&self) -> &str {
85        match self {
86            Self::RunFirst => "G_SIGNAL_RUN_FIRST",
87            Self::RunLast => "G_SIGNAL_RUN_LAST",
88            Self::RunCleanup => "G_SIGNAL_RUN_CLEANUP",
89            Self::NoRecurse => "G_SIGNAL_NO_RECURSE",
90            Self::Detailed => "G_SIGNAL_DETAILED",
91            Self::Action => "G_SIGNAL_ACTION",
92            Self::NoHooks => "G_SIGNAL_NO_HOOKS",
93            Self::MustCollect => "G_SIGNAL_MUST_COLLECT",
94            Self::Deprecated => "G_SIGNAL_DEPRECATED",
95            Self::AccumulatorFirstRun => "G_SIGNAL_ACCUMULATOR_FIRST_RUN",
96            Self::Unknown(s) => s,
97        }
98    }
99}
100
101impl Signal {
102    /// Extract a signal from a g_signal_new* function call
103    ///
104    /// ```c
105    /// g_signal_new ("changed",
106    ///               G_TYPE_FROM_CLASS (klass),
107    ///               G_SIGNAL_RUN_LAST,
108    ///               G_STRUCT_OFFSET (MyObjectClass, changed),
109    ///               NULL, NULL, NULL,
110    ///               G_TYPE_NONE,
111    ///               0);
112    /// ```
113    pub fn from_g_signal_new_call(call: &CallExpression, source: &[u8]) -> Option<Self> {
114        if !call.function_name(source).starts_with("g_signal_new") {
115            return None;
116        }
117
118        // Argument 0: signal_name (string literal)
119        let name = call.extract_string_from_arg(0)?;
120
121        // Argument 1: itype (GType expression) - use source text
122        let itype = call.get_arg_text(1, source).map(ToOwned::to_owned);
123
124        // Argument 2: signal_flags (can be bitwise OR of multiple flags)
125        let flags = call
126            .get_arg(2)
127            .map(extract_signal_flags)
128            .unwrap_or_default();
129
130        // Argument 3: class_offset — G_STRUCT_OFFSET(StructType, field) or 0
131        let class_offset = call.get_arg(3).and_then(|expr| match expr {
132            Expression::Call(offset_call)
133                if offset_call.function_name(source) == "G_STRUCT_OFFSET" =>
134            {
135                let struct_type = offset_call.get_arg(0).and_then(|e| {
136                    if let Expression::Identifier(id) = e {
137                        Some(id.name.clone())
138                    } else {
139                        None
140                    }
141                })?;
142                let field = offset_call.get_arg(1).and_then(|e| {
143                    if let Expression::Identifier(id) = e {
144                        Some(id.name.clone())
145                    } else {
146                        None
147                    }
148                })?;
149                Some(StructField { struct_type, field })
150            }
151            _ => None, // 0, NULL, or any other form → no vtable slot
152        });
153
154        // Argument 4: accumulator (function pointer or NULL)
155        let accumulator = call.get_arg_text(4, source).map(ToOwned::to_owned);
156
157        // Argument 5: accu_data (gpointer or NULL)
158        let accu_data = call.get_arg_text(5, source).map(ToOwned::to_owned);
159
160        // Argument 6: c_marshaller (function pointer or NULL)
161        let c_marshaller = call.get_arg_text(6, source).map(ToOwned::to_owned);
162
163        // Argument 7: return_type (GType)
164        let return_type = call
165            .get_arg(7)
166            .and_then(|e| GType::from_expression(e, source));
167
168        // Argument 8: n_params (guint)
169        let n_params = call.get_arg(8).and_then(|expr| match expr {
170            Expression::NumberLiteral(n) => n.value.parse::<i64>().ok(),
171            _ => None,
172        });
173
174        // Arguments 9+: parameter types (variadic)
175        let param_types = (9..call.arguments.len())
176            .filter_map(|i| {
177                call.get_arg(i)
178                    .and_then(|e| GType::from_expression(e, source))
179            })
180            .collect();
181
182        Some(Self {
183            name,
184            enum_value: None,
185            doc: None,
186            itype,
187            flags,
188            class_offset,
189            accumulator,
190            accu_data,
191            c_marshaller,
192            return_type,
193            n_params,
194            param_types,
195        })
196    }
197}
198
199/// Extract signal flags from an expression (handles bitwise OR)
200fn extract_signal_flags(expr: &Expression) -> Vec<SignalFlag> {
201    let mut flags = Vec::new();
202
203    // Walk the expression tree to find all flag identifiers
204    // This handles simple cases like G_SIGNAL_RUN_LAST
205    // and complex cases like G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION
206    expr.walk(&mut |e| {
207        if let Expression::Identifier(id) = e
208            && id.name.starts_with("G_SIGNAL_")
209        {
210            flags.push(SignalFlag::from_identifier(&id.name));
211        }
212    });
213
214    flags
215}