Skip to main content

mago_codex/metadata/
property.rs

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