Skip to main content

gobject_ast/model/types/
property.rs

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