Skip to main content

gobject_ast/model/types/
property.rs

1use std::ffi::{c_long, c_ulong};
2
3use serde::Serialize;
4
5use crate::model::{
6    SourceLocation,
7    doc::PropertyDoc,
8    expression::{Argument, CallExpression, Expression},
9    operators::UnaryOp,
10    types::GType,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub enum ParamFlag {
16    /// The parameter is readable (value: 1)
17    Readable,
18    /// The parameter is writable (value: 2)
19    Writable,
20    /// Alias for READABLE | WRITABLE (value: 3)
21    ReadWrite,
22    /// The parameter will be set upon object construction (value: 4)
23    Construct,
24    /// The parameter can only be set upon object construction (value: 8)
25    ConstructOnly,
26    /// Strict validation not required upon parameter conversion (value: 16)
27    LaxValidation,
28    /// String used as name is guaranteed to remain valid (value: 32)
29    StaticName,
30    /// Internal flag (value: 32)
31    Private,
32    /// String used as nick is guaranteed to remain valid (value: 64)
33    StaticNick,
34    /// String used as blurb is guaranteed to remain valid (value: 128)
35    StaticBlurb,
36    /// Alias for STATIC_NAME | STATIC_NICK | STATIC_BLURB
37    StaticStrings,
38    /// No automatic notify signal emission (value: 1073741824)
39    ExplicitNotify,
40    /// The parameter is deprecated (value: 2147483648)
41    Deprecated,
42    /// Custom or unrecognized flag
43    Unknown(String),
44}
45
46impl ParamFlag {
47    pub fn from_identifier(name: &str) -> Self {
48        match name {
49            "G_PARAM_READABLE" => Self::Readable,
50            "G_PARAM_WRITABLE" => Self::Writable,
51            "G_PARAM_READWRITE" => Self::ReadWrite,
52            "G_PARAM_CONSTRUCT" => Self::Construct,
53            "G_PARAM_CONSTRUCT_ONLY" => Self::ConstructOnly,
54            "G_PARAM_LAX_VALIDATION" => Self::LaxValidation,
55            "G_PARAM_STATIC_NAME" => Self::StaticName,
56            "G_PARAM_PRIVATE" => Self::Private,
57            "G_PARAM_STATIC_NICK" => Self::StaticNick,
58            "G_PARAM_STATIC_BLURB" => Self::StaticBlurb,
59            "G_PARAM_STATIC_STRINGS" => Self::StaticStrings,
60            "G_PARAM_EXPLICIT_NOTIFY" => Self::ExplicitNotify,
61            "G_PARAM_DEPRECATED" => Self::Deprecated,
62            _ => Self::Unknown(name.to_string()),
63        }
64    }
65
66    pub fn as_str(&self) -> &str {
67        match self {
68            Self::Readable => "G_PARAM_READABLE",
69            Self::Writable => "G_PARAM_WRITABLE",
70            Self::ReadWrite => "G_PARAM_READWRITE",
71            Self::Construct => "G_PARAM_CONSTRUCT",
72            Self::ConstructOnly => "G_PARAM_CONSTRUCT_ONLY",
73            Self::LaxValidation => "G_PARAM_LAX_VALIDATION",
74            Self::StaticName => "G_PARAM_STATIC_NAME",
75            Self::Private => "G_PARAM_PRIVATE",
76            Self::StaticNick => "G_PARAM_STATIC_NICK",
77            Self::StaticBlurb => "G_PARAM_STATIC_BLURB",
78            Self::StaticStrings => "G_PARAM_STATIC_STRINGS",
79            Self::ExplicitNotify => "G_PARAM_EXPLICIT_NOTIFY",
80            Self::Deprecated => "G_PARAM_DEPRECATED",
81            Self::Unknown(name) => name.as_str(),
82        }
83    }
84}
85
86#[derive(Debug, Clone, Serialize)]
87pub struct Property {
88    pub name: String,
89    pub property_type: PropertyType,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub nick: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub blurb: Option<String>,
94    #[serde(skip_serializing_if = "Vec::is_empty")]
95    pub flags: Vec<ParamFlag>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub doc: Option<PropertyDoc>,
98}
99
100#[derive(Debug, Clone, Serialize)]
101#[serde(rename_all = "snake_case")]
102pub enum PropertyType {
103    String,
104    Int {
105        min: i32,
106        max: i32,
107        default: i32,
108    },
109    UInt {
110        min: u32,
111        max: u32,
112        default: u32,
113    },
114    Int64 {
115        min: i64,
116        max: i64,
117        default: i64,
118    },
119    UInt64 {
120        min: u64,
121        max: u64,
122        default: u64,
123    },
124    Long {
125        min: c_long,
126        max: c_long,
127        default: c_long,
128    },
129    ULong {
130        min: c_ulong,
131        max: c_ulong,
132        default: c_ulong,
133    },
134    Char {
135        min: i8,
136        max: i8,
137        default: i8,
138    },
139    UChar {
140        min: u8,
141        max: u8,
142        default: u8,
143    },
144    Unichar {
145        default: u32,
146    },
147    Param {
148        param_type: GType,
149    },
150    Boolean {
151        default: bool,
152    },
153    Float {
154        min: f32,
155        max: f32,
156        default: f32,
157    },
158    Double {
159        min: f64,
160        max: f64,
161        default: f64,
162    },
163    Enum {
164        enum_type: GType,
165        default: i32,
166    },
167    Flags {
168        flags_type: GType,
169        default: u32,
170    },
171    Object {
172        object_type: GType,
173    },
174    Boxed {
175        boxed_type: GType,
176    },
177    Pointer,
178    GType {
179        is_a_type: GType,
180    },
181    Variant {
182        variant_type: Option<String>,
183        default_value: Option<Expression>,
184    },
185    Override,
186    Unknown {
187        spec_function: String,
188    },
189}
190
191impl Property {
192    /// Extract property from g_param_spec_* call
193    /// Call signature varies by type:
194    /// - g_param_spec_string(name, nick, blurb, default, flags)
195    /// - g_param_spec_int(name, nick, blurb, min, max, default, flags)
196    /// - g_param_spec_object(name, nick, blurb, object_type, flags)
197    pub(crate) fn from_param_spec_call(call: &CallExpression, source: &[u8]) -> Option<Self> {
198        let func_name = call.function_name_str()?;
199
200        // Extract common arguments (name, nick, blurb)
201        let args = &call.arguments;
202        if args.len() < 3 {
203            return None;
204        }
205
206        let name = extract_string_arg(&args[0])?;
207        let nick = extract_string_arg(&args[1]);
208        let blurb = extract_string_arg(&args[2]);
209
210        let property_type = match func_name {
211            "g_param_spec_string" => {
212                // (name, nick, blurb, default, flags)
213                PropertyType::String
214            }
215            "g_param_spec_boolean" => {
216                // (name, nick, blurb, default, flags)
217                let default = if args.len() > 3 {
218                    extract_boolean_arg(&args[3]).unwrap_or(false)
219                } else {
220                    false
221                };
222                PropertyType::Boolean { default }
223            }
224            "g_param_spec_int" => {
225                // (name, nick, blurb, min, max, default, flags)
226                let min = if args.len() > 3 {
227                    extract_int_arg::<i32>(&args[3]).unwrap_or(i32::MIN)
228                } else {
229                    i32::MIN
230                };
231                let max = if args.len() > 4 {
232                    extract_int_arg::<i32>(&args[4]).unwrap_or(i32::MAX)
233                } else {
234                    i32::MAX
235                };
236                let default = if args.len() > 5 {
237                    extract_int_arg::<i32>(&args[5]).unwrap_or(0)
238                } else {
239                    0
240                };
241                PropertyType::Int { min, max, default }
242            }
243            "g_param_spec_uint" => {
244                // (name, nick, blurb, min, max, default, flags)
245                let min = if args.len() > 3 {
246                    extract_uint_arg::<u32>(&args[3]).unwrap_or(0)
247                } else {
248                    0
249                };
250                let max = if args.len() > 4 {
251                    extract_uint_arg::<u32>(&args[4]).unwrap_or(u32::MAX)
252                } else {
253                    u32::MAX
254                };
255                let default = if args.len() > 5 {
256                    extract_uint_arg::<u32>(&args[5]).unwrap_or(0)
257                } else {
258                    0
259                };
260                PropertyType::UInt { min, max, default }
261            }
262            "g_param_spec_float" => {
263                // (name, nick, blurb, min, max, default, flags)
264                let min = if args.len() > 3 {
265                    extract_float_arg::<f32>(&args[3]).unwrap_or(f32::MIN)
266                } else {
267                    f32::MIN
268                };
269                let max = if args.len() > 4 {
270                    extract_float_arg::<f32>(&args[4]).unwrap_or(f32::MAX)
271                } else {
272                    f32::MAX
273                };
274                let default = if args.len() > 5 {
275                    extract_float_arg::<f32>(&args[5]).unwrap_or(0.0)
276                } else {
277                    0.0
278                };
279                PropertyType::Float { min, max, default }
280            }
281            "g_param_spec_double" => {
282                // (name, nick, blurb, min, max, default, flags)
283                let min = if args.len() > 3 {
284                    extract_float_arg::<f64>(&args[3]).unwrap_or(f64::MIN)
285                } else {
286                    f64::MIN
287                };
288                let max = if args.len() > 4 {
289                    extract_float_arg::<f64>(&args[4]).unwrap_or(f64::MAX)
290                } else {
291                    f64::MAX
292                };
293                let default = if args.len() > 5 {
294                    extract_float_arg::<f64>(&args[5]).unwrap_or(0.0)
295                } else {
296                    0.0
297                };
298                PropertyType::Double { min, max, default }
299            }
300            "g_param_spec_enum" => {
301                // (name, nick, blurb, enum_type, default, flags)
302                let enum_type = args.get(3).and_then(|a| extract_gtype_arg(a, source))?;
303                let default = if args.len() > 4 {
304                    extract_int_arg::<i32>(&args[4]).unwrap_or(0)
305                } else {
306                    0
307                };
308                PropertyType::Enum { enum_type, default }
309            }
310            "g_param_spec_flags" => {
311                // (name, nick, blurb, flags_type, default, flags)
312                let flags_type = args.get(3).and_then(|a| extract_gtype_arg(a, source))?;
313                let default = if args.len() > 4 {
314                    extract_uint_arg::<u32>(&args[4]).unwrap_or(0)
315                } else {
316                    0
317                };
318                PropertyType::Flags {
319                    flags_type,
320                    default,
321                }
322            }
323            "g_param_spec_object" => {
324                // (name, nick, blurb, object_type, flags)
325                let object_type = args.get(3).and_then(|a| extract_gtype_arg(a, source))?;
326                PropertyType::Object { object_type }
327            }
328            "g_param_spec_boxed" => {
329                // (name, nick, blurb, boxed_type, flags)
330                let boxed_type = args.get(3).and_then(|a| extract_gtype_arg(a, source))?;
331                PropertyType::Boxed { boxed_type }
332            }
333            "g_param_spec_pointer" => PropertyType::Pointer,
334            "g_param_spec_gtype" => {
335                // (name, nick, blurb, is_a_type, flags)
336                let is_a_type = args.get(3).and_then(|a| extract_gtype_arg(a, source))?;
337                PropertyType::GType { is_a_type }
338            }
339            "g_param_spec_int64" => {
340                // (name, nick, blurb, min, max, default, flags)
341                let min = if args.len() > 3 {
342                    extract_int_arg::<i64>(&args[3]).unwrap_or(i64::MIN)
343                } else {
344                    i64::MIN
345                };
346                let max = if args.len() > 4 {
347                    extract_int_arg::<i64>(&args[4]).unwrap_or(i64::MAX)
348                } else {
349                    i64::MAX
350                };
351                let default = if args.len() > 5 {
352                    extract_int_arg::<i64>(&args[5]).unwrap_or(0)
353                } else {
354                    0
355                };
356                PropertyType::Int64 { min, max, default }
357            }
358            "g_param_spec_uint64" => {
359                // (name, nick, blurb, min, max, default, flags)
360                let min = if args.len() > 3 {
361                    extract_uint_arg::<u64>(&args[3]).unwrap_or(0)
362                } else {
363                    0
364                };
365                let max = if args.len() > 4 {
366                    extract_uint_arg::<u64>(&args[4]).unwrap_or(u64::MAX)
367                } else {
368                    u64::MAX
369                };
370                let default = if args.len() > 5 {
371                    extract_uint_arg::<u64>(&args[5]).unwrap_or(0)
372                } else {
373                    0
374                };
375                PropertyType::UInt64 { min, max, default }
376            }
377            "g_param_spec_long" => {
378                // (name, nick, blurb, min, max, default, flags)
379                let min = if args.len() > 3 {
380                    extract_int_arg::<c_long>(&args[3]).unwrap_or(c_long::MIN)
381                } else {
382                    c_long::MIN
383                };
384                let max = if args.len() > 4 {
385                    extract_int_arg::<c_long>(&args[4]).unwrap_or(c_long::MAX)
386                } else {
387                    c_long::MAX
388                };
389                let default = if args.len() > 5 {
390                    extract_int_arg::<c_long>(&args[5]).unwrap_or(0)
391                } else {
392                    0
393                };
394                PropertyType::Long { min, max, default }
395            }
396            "g_param_spec_ulong" => {
397                // (name, nick, blurb, min, max, default, flags)
398                let min = if args.len() > 3 {
399                    extract_uint_arg::<c_ulong>(&args[3]).unwrap_or(0)
400                } else {
401                    0
402                };
403                let max = if args.len() > 4 {
404                    extract_uint_arg::<c_ulong>(&args[4]).unwrap_or(c_ulong::MAX)
405                } else {
406                    c_ulong::MAX
407                };
408                let default = if args.len() > 5 {
409                    extract_uint_arg::<c_ulong>(&args[5]).unwrap_or(0)
410                } else {
411                    0
412                };
413                PropertyType::ULong { min, max, default }
414            }
415            "g_param_spec_char" => {
416                // (name, nick, blurb, min, max, default, flags)
417                let min = if args.len() > 3 {
418                    extract_int_arg::<i8>(&args[3]).unwrap_or(i8::MIN)
419                } else {
420                    i8::MIN
421                };
422                let max = if args.len() > 4 {
423                    extract_int_arg::<i8>(&args[4]).unwrap_or(i8::MAX)
424                } else {
425                    i8::MAX
426                };
427                let default = if args.len() > 5 {
428                    extract_int_arg::<i8>(&args[5]).unwrap_or(0)
429                } else {
430                    0
431                };
432                PropertyType::Char { min, max, default }
433            }
434            "g_param_spec_uchar" => {
435                // (name, nick, blurb, min, max, default, flags)
436                let min = if args.len() > 3 {
437                    extract_uint_arg::<u8>(&args[3]).unwrap_or(0)
438                } else {
439                    0
440                };
441                let max = if args.len() > 4 {
442                    extract_uint_arg::<u8>(&args[4]).unwrap_or(u8::MAX)
443                } else {
444                    u8::MAX
445                };
446                let default = if args.len() > 5 {
447                    extract_uint_arg::<u8>(&args[5]).unwrap_or(0)
448                } else {
449                    0
450                };
451                PropertyType::UChar { min, max, default }
452            }
453            "g_param_spec_unichar" => {
454                // (name, nick, blurb, default, flags)
455                let default = if args.len() > 3 {
456                    extract_uint_arg::<u32>(&args[3]).unwrap_or(0)
457                } else {
458                    0
459                };
460                PropertyType::Unichar { default }
461            }
462            "g_param_spec_param" => {
463                // (name, nick, blurb, param_type, flags)
464                let param_type = args.get(3).and_then(|a| extract_gtype_arg(a, source))?;
465                PropertyType::Param { param_type }
466            }
467            "g_param_spec_variant" => {
468                // (name, nick, blurb, type, default_value, flags)
469                let variant_type = args.get(3).and_then(|arg| {
470                    let Argument::Expression(expr) = arg;
471                    match expr.as_ref() {
472                        Expression::Identifier(id) => Some(id.name.as_str()),
473                        Expression::Call(call) => Some(call.function_name(source)),
474                        _ => None,
475                    }
476                });
477                let default_value = args.get(4).and_then(|arg| {
478                    let Argument::Expression(e) = arg;
479                    if matches!(e.as_ref(), Expression::Null(_)) {
480                        None
481                    } else {
482                        Some(*e.clone())
483                    }
484                });
485                PropertyType::Variant {
486                    variant_type: variant_type.map(ToOwned::to_owned),
487                    default_value,
488                }
489            }
490            _ => PropertyType::Unknown {
491                spec_function: func_name.to_string(),
492            },
493        };
494
495        // Extract flags (usually last argument)
496        let flags = if let Some(last_arg) = args.last() {
497            extract_flags_arg(last_arg)
498        } else {
499            Vec::new()
500        };
501
502        Some(Self {
503            name,
504            property_type,
505            nick,
506            blurb,
507            flags,
508            doc: None,
509        })
510    }
511
512    /// Extract property from g_object_class_override_property call
513    /// Call signature: g_object_class_override_property(oclass, property_id,
514    /// name)
515    pub fn from_override_property_call(call: &CallExpression) -> Option<Self> {
516        let func_name = call.function_name_str()?;
517        if func_name != "g_object_class_override_property" {
518            return None;
519        }
520
521        let args = &call.arguments;
522        if args.len() < 3 {
523            return None;
524        }
525
526        // Third argument is the property name
527        let name = extract_string_arg(&args[2])?;
528
529        Some(Self {
530            name,
531            property_type: PropertyType::Override,
532            nick: None,
533            blurb: None,
534            flags: Vec::new(),
535            doc: None,
536        })
537    }
538}
539
540// Helper functions to extract values from expression arguments
541
542fn extract_string_arg(arg: &Argument) -> Option<String> {
543    match arg {
544        Argument::Expression(boxed_expr) => match &**boxed_expr {
545            Expression::StringLiteral(s) => {
546                // Remove quotes
547                let text = s.value.trim_matches('"');
548                Some(text.to_owned())
549            }
550            Expression::Null(_) => None,
551            _ => None,
552        },
553    }
554}
555
556fn extract_int_arg<T>(arg: &Argument) -> Option<T>
557where
558    T: std::str::FromStr + std::ops::Neg<Output = T>,
559{
560    match arg {
561        Argument::Expression(boxed_expr) => match &**boxed_expr {
562            Expression::NumberLiteral(num) => num.value.parse().ok(),
563            Expression::Unary(unary) => {
564                if matches!(unary.operator, UnaryOp::Negate)
565                    && let Expression::NumberLiteral(num) = &*unary.operand
566                {
567                    return num.value.parse::<T>().ok().map(|v| -v);
568                }
569                None
570            }
571            _ => None,
572        },
573    }
574}
575
576fn extract_uint_arg<T: std::str::FromStr>(arg: &Argument) -> Option<T> {
577    match arg {
578        Argument::Expression(boxed_expr) => match &**boxed_expr {
579            Expression::NumberLiteral(num) => num.value.parse().ok(),
580            _ => None,
581        },
582    }
583}
584
585fn extract_float_arg<T>(arg: &Argument) -> Option<T>
586where
587    T: std::str::FromStr + std::ops::Neg<Output = T>,
588{
589    match arg {
590        Argument::Expression(boxed_expr) => match &**boxed_expr {
591            Expression::NumberLiteral(num) => num.value.parse().ok(),
592            Expression::Unary(unary) => {
593                if matches!(unary.operator, UnaryOp::Negate)
594                    && let Expression::NumberLiteral(num) = &*unary.operand
595                {
596                    return num.value.parse::<T>().ok().map(|v| -v);
597                }
598                None
599            }
600            _ => None,
601        },
602    }
603}
604
605fn extract_boolean_arg(arg: &Argument) -> Option<bool> {
606    match arg {
607        Argument::Expression(boxed_expr) => match &**boxed_expr {
608            Expression::Boolean(b) => Some(b.value),
609            Expression::Identifier(id) => {
610                // TRUE/FALSE macros
611                match id.name.as_str() {
612                    "TRUE" | "true" => Some(true),
613                    "FALSE" | "false" => Some(false),
614                    _ => None,
615                }
616            }
617            _ => None,
618        },
619    }
620}
621
622fn extract_gtype_arg(arg: &Argument, source: &[u8]) -> Option<GType> {
623    let Argument::Expression(expr) = arg;
624    GType::from_expression(expr, source)
625}
626
627fn extract_flags_arg(arg: &Argument) -> Vec<ParamFlag> {
628    let Argument::Expression(expr) = arg;
629    let mut flags = Vec::new();
630    expr.walk(&mut |e| {
631        if let Expression::Identifier(id) = e {
632            flags.push(ParamFlag::from_identifier(&id.name));
633        }
634    });
635    flags
636}
637
638/// Information about a param_spec assignment found in a class_init function
639#[derive(Debug, Clone, Serialize)]
640#[serde(rename_all = "snake_case")]
641pub enum ParamSpecAssignment {
642    /// Array subscript pattern: props[PROP_X] = g_param_spec_*()
643    ArraySubscript {
644        array_name: String,
645        enum_value: String,
646        statement_location: SourceLocation,
647        call: CallExpression,
648        property: Property,
649        install_call: Option<CallExpression>,
650    },
651    /// Variable pattern: param_spec = g_param_spec_*()
652    Variable {
653        variable_name: String,
654        statement_location: SourceLocation,
655        call: CallExpression,
656        property: Property,
657        install_call: Option<CallExpression>,
658    },
659    /// Override property pattern: g_object_class_override_property(class,
660    /// PROP_X, "name")
661    OverrideProperty {
662        enum_value: String,
663        statement_location: SourceLocation,
664        call: CallExpression,
665        property: Property,
666    },
667    /// Direct install: g_object_class_install_property(class, PROP_X,
668    /// g_param_spec_xxx(...))
669    DirectInstall {
670        enum_value: String,
671        statement_location: SourceLocation,
672        /// The inline g_param_spec_* call
673        call: CallExpression,
674        property: Property,
675        /// The enclosing g_object_class_install_property call
676        install_call: CallExpression,
677    },
678}
679
680impl ParamSpecAssignment {
681    /// Check if this param_spec assignment is installed (has an install call or
682    /// is an override)
683    pub fn is_installed(&self) -> bool {
684        match self {
685            Self::ArraySubscript { install_call, .. } => install_call.is_some(),
686            Self::OverrideProperty { .. } => true,
687            Self::Variable { install_call, .. } => install_call.is_some(),
688            Self::DirectInstall { .. } => true,
689        }
690    }
691
692    /// Get the enum value if this assignment is installed
693    /// For Variable assignments, extracts enum value from install_property call
694    pub fn get_installed_enum_value<'a>(&'a self, source: &'a [u8]) -> Option<&'a str> {
695        match self {
696            Self::ArraySubscript {
697                enum_value,
698                install_call,
699                ..
700            } => install_call.as_ref().map(|_| enum_value.as_str()),
701            Self::OverrideProperty { enum_value, .. } => Some(enum_value),
702            Self::Variable { install_call, .. } => install_call
703                .as_ref()
704                .and_then(|call| call.get_arg(1).and_then(|arg| arg.to_source_string(source))),
705            Self::DirectInstall { enum_value, .. } => Some(enum_value),
706        }
707    }
708
709    /// Get the property information
710    pub fn property(&self) -> &Property {
711        match self {
712            Self::ArraySubscript { property, .. } => property,
713            Self::Variable { property, .. } => property,
714            Self::OverrideProperty { property, .. } => property,
715            Self::DirectInstall { property, .. } => property,
716        }
717    }
718
719    /// Get the g_param_spec_* call (None for OverrideProperty)
720    pub fn param_spec_call(&self) -> Option<&CallExpression> {
721        match self {
722            Self::ArraySubscript { call, .. }
723            | Self::Variable { call, .. }
724            | Self::DirectInstall { call, .. } => Some(call),
725            Self::OverrideProperty { .. } => None,
726        }
727    }
728
729    /// Get the enum value (for ArraySubscript, OverrideProperty, and
730    /// DirectInstall)
731    pub fn enum_value(&self) -> Option<&str> {
732        match self {
733            Self::ArraySubscript { enum_value, .. } => Some(enum_value.as_str()),
734            Self::OverrideProperty { enum_value, .. } => Some(enum_value.as_str()),
735            Self::DirectInstall { enum_value, .. } => Some(enum_value.as_str()),
736            Self::Variable { .. } => None,
737        }
738    }
739}