Skip to main content

gobject_ast/model/types/
signal.rs

1use serde::Serialize;
2
3use crate::model::{
4    doc::SignalDoc,
5    expression::{CallExpression, Expression, OffsetField},
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<OffsetField>, // 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) -> Option<Self> {
114        if !call.function_name().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).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) if offset_call.function_name() == "G_STRUCT_OFFSET" => {
133                let struct_type = offset_call.get_arg(0).and_then(|e| {
134                    if let Expression::Identifier(id) = e {
135                        Some(id.name.clone())
136                    } else {
137                        None
138                    }
139                })?;
140                let field = offset_call.get_arg(1).and_then(|e| {
141                    if let Expression::Identifier(id) = e {
142                        Some(id.name.clone())
143                    } else {
144                        None
145                    }
146                })?;
147                Some(OffsetField { struct_type, field })
148            }
149            _ => None, // 0, NULL, or any other form → no vtable slot
150        });
151
152        // Argument 4: accumulator (function pointer or NULL)
153        let accumulator = call.get_arg_text(4).map(ToOwned::to_owned);
154
155        // Argument 5: accu_data (gpointer or NULL)
156        let accu_data = call.get_arg_text(5).map(ToOwned::to_owned);
157
158        // Argument 6: c_marshaller (function pointer or NULL)
159        let c_marshaller = call.get_arg_text(6).map(ToOwned::to_owned);
160
161        // Argument 7: return_type (GType)
162        let return_type = call.get_arg(7).and_then(GType::from_expression);
163
164        // Argument 8: n_params (guint)
165        let n_params = call.get_arg(8).and_then(|expr| match expr {
166            Expression::NumberLiteral(n) => n.value.parse::<i64>().ok(),
167            _ => None,
168        });
169
170        // Arguments 9+: parameter types (variadic)
171        let param_types = (9..call.arguments.len())
172            .filter_map(|i| call.get_arg(i).and_then(GType::from_expression))
173            .collect();
174
175        Some(Self {
176            name,
177            enum_value: None,
178            doc: None,
179            itype,
180            flags,
181            class_offset,
182            accumulator,
183            accu_data,
184            c_marshaller,
185            return_type,
186            n_params,
187            param_types,
188        })
189    }
190}
191
192/// Extract signal flags from an expression (handles bitwise OR)
193fn extract_signal_flags(expr: &Expression) -> Vec<SignalFlag> {
194    let mut flags = Vec::new();
195
196    // Walk the expression tree to find all flag identifiers
197    // This handles simple cases like G_SIGNAL_RUN_LAST
198    // and complex cases like G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION
199    expr.walk(&mut |e| {
200        if let Expression::Identifier(id) = e
201            && id.name.starts_with("G_SIGNAL_")
202        {
203            flags.push(SignalFlag::from_identifier(&id.name));
204        }
205    });
206
207    flags
208}