mago_codex/metadata/
property.rs

1use serde::Deserialize;
2use serde::Serialize;
3
4use mago_atom::AtomMap;
5use mago_span::Span;
6
7use crate::metadata::flags::MetadataFlags;
8use crate::metadata::property_hook::PropertyHookMetadata;
9use crate::metadata::ttype::TypeMetadata;
10use crate::misc::VariableIdentifier;
11use crate::visibility::Visibility;
12
13/// Contains metadata associated with a declared class property in PHP.
14///
15/// This includes information about its name, location, visibility (potentially asymmetric),
16/// type hints, default values, and various modifiers (`static`, `readonly`, `abstract`, etc.).
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18pub struct PropertyMetadata {
19    /// The identifier (name) of the property, including the leading '$'.
20    pub name: VariableIdentifier,
21
22    /// The specific source code location (span) of the property's name identifier itself.
23    /// `None` if the location is unknown or not relevant (e.g., for synthetic properties).
24    pub name_span: Option<Span>,
25
26    /// The source code location (span) covering the entire property declaration statement.
27    /// `None` if the location is unknown or not relevant.
28    pub span: Option<Span>,
29
30    /// The visibility level required for reading the property's value.
31    ///
32    /// In PHP, this corresponds to the primary visibility keyword specified
33    /// (e.g., the `public` in `public private(set) string $prop;`).
34    ///
35    /// If no asymmetric visibility is specified (e.g., `public string $prop`),
36    /// this level applies to both reading and writing. Defaults to `Public`.
37    pub read_visibility: Visibility,
38
39    /// The visibility level required for writing/modifying the property's value.
40    ///
41    /// In PHP, this can differ from `read_visibility` using asymmetric visibility syntax
42    /// like `private(set)` (e.g., `public private(set) string $prop;`).
43    ///
44    /// If asymmetric visibility is not used, this implicitly matches `read_visibility`.
45    /// Defaults to `Public`.
46    pub write_visibility: Visibility,
47
48    /// The explicit type declaration (type hint) associated with the property, if any.
49    ///
50    /// e.g., for `public string $name;`, this would contain the metadata for `string`.
51    pub type_declaration_metadata: Option<TypeMetadata>,
52
53    /// The type metadata for the property's type, if any.
54    ///
55    /// This is either the same as `type_declaration_metadata` or the type provided
56    /// in a docblock comment (e.g., `@var string`).
57    pub type_metadata: Option<TypeMetadata>,
58
59    /// The type inferred from the property's default value, if it has one.
60    ///
61    /// e.g., for `public $count = 0;`, this would contain the metadata for `int(0)`.
62    /// This can be used to compare against `type_signature` for consistency checks.
63    pub default_type_metadata: Option<TypeMetadata>,
64
65    /// Flags indicating various properties of the property.
66    pub flags: MetadataFlags,
67
68    /// Metadata for property hooks (get/set).
69    ///
70    /// Key is the hook name atom ("get" or "set").
71    /// Only present for PHP 8.4+ hooked properties.
72    pub hooks: AtomMap<PropertyHookMetadata>,
73}
74
75impl PropertyMetadata {
76    /// Creates new `PropertyMetadata` with basic defaults (public, non-static, non-readonly, etc.).
77    /// Name is mandatory. Spans, types, and flags can be set using modifier methods.
78    #[inline]
79    #[must_use]
80    pub fn new(name: VariableIdentifier, flags: MetadataFlags) -> Self {
81        Self {
82            name,
83            name_span: None,
84            span: None,
85            read_visibility: Visibility::Public,
86            write_visibility: Visibility::Public,
87            type_declaration_metadata: None,
88            type_metadata: None,
89            default_type_metadata: None,
90            flags,
91            hooks: AtomMap::default(),
92        }
93    }
94
95    #[inline]
96    pub fn set_default_type_metadata(&mut self, default_type_metadata: Option<TypeMetadata>) {
97        self.default_type_metadata = default_type_metadata;
98    }
99
100    #[inline]
101    pub fn set_type_declaration_metadata(&mut self, type_declaration_metadata: Option<TypeMetadata>) {
102        if self.type_metadata.is_none() {
103            self.type_metadata.clone_from(&type_declaration_metadata);
104        }
105
106        self.type_declaration_metadata = type_declaration_metadata;
107    }
108
109    #[inline]
110    pub fn set_type_metadata(&mut self, type_metadata: Option<TypeMetadata>) {
111        self.type_metadata = type_metadata;
112    }
113
114    /// Returns a reference to the property's name identifier.
115    #[inline]
116    #[must_use]
117    pub fn get_name(&self) -> &VariableIdentifier {
118        &self.name
119    }
120
121    /// Checks if the property is effectively final (private read or write access).
122    #[inline]
123    #[must_use]
124    pub fn is_final(&self) -> bool {
125        self.read_visibility.is_private() || self.write_visibility.is_private()
126    }
127
128    /// Sets the span for the property name identifier.
129    #[inline]
130    pub fn set_name_span(&mut self, name_span: Option<Span>) {
131        self.name_span = name_span;
132    }
133
134    /// Sets the overall span for the property declaration.
135    #[inline]
136    pub fn set_span(&mut self, span: Option<Span>) {
137        self.span = span;
138    }
139
140    /// Sets both read and write visibility levels. Updates `is_asymmetric`. Ensures virtual properties remain symmetric.
141    #[inline]
142    pub fn set_visibility(&mut self, read: Visibility, write: Visibility) {
143        self.read_visibility = read;
144        self.write_visibility = write;
145        self.update_asymmetric();
146    }
147
148    /// Sets whether the property uses property hooks. Updates `is_asymmetric`.
149    #[inline]
150    pub fn set_is_virtual(&mut self, is_virtual: bool) {
151        self.flags.set(MetadataFlags::VIRTUAL_PROPERTY, is_virtual);
152
153        self.update_asymmetric();
154    }
155
156    /// Also ensures virtual properties are not asymmetric.
157    #[inline]
158    fn update_asymmetric(&mut self) {
159        if self.flags.is_virtual_property() {
160            if self.read_visibility != self.write_visibility {
161                // If virtual and somehow asymmetric, force symmetry (prefer read)
162                self.write_visibility = self.read_visibility;
163            }
164
165            self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
166        } else if self.read_visibility == self.write_visibility {
167            // If both visibilities are the same, ensure no asymmetric flag is set
168            self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
169        } else {
170            // Otherwise, set the asymmetric flag
171            self.flags |= MetadataFlags::ASYMMETRIC_PROPERTY;
172        }
173    }
174}