Skip to main content

mago_codex/metadata/
property.rs

1use mago_php_version::PHPVersion;
2use mago_php_version::PHPVersionRange;
3
4use mago_span::Span;
5use mago_word::WordMap;
6
7use crate::metadata::attribute::AttributeMetadata;
8use crate::metadata::flags::MetadataFlags;
9use crate::metadata::property_hook::PropertyHookMetadata;
10use crate::metadata::ttype::TypeMetadata;
11use crate::metadata::version_constraint::VersionConstraint;
12use crate::misc::VariableIdentifier;
13use crate::visibility::Visibility;
14
15/// Contains metadata associated with a declared class property in PHP.
16///
17/// This includes information about its name, location, visibility (potentially asymmetric),
18/// type hints, default values, and various modifiers (`static`, `readonly`, `abstract`, etc.).
19#[derive(Clone, Debug, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::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 accepted when writing to the property, when it differs from `type_metadata`.
64    ///
65    /// Only set for magic properties declaring split `@property-read` / `@property-write`
66    /// types; `None` means writes accept the same type that reads produce (`type_metadata`).
67    pub write_type_metadata: Option<TypeMetadata>,
68
69    /// The type inferred from the property's default value, if it has one.
70    ///
71    /// e.g., for `public $count = 0;`, this would contain the metadata for `int(0)`.
72    /// This can be used to compare against `type_signature` for consistency checks.
73    pub default_type_metadata: Option<TypeMetadata>,
74
75    /// Flags indicating various properties of the property.
76    pub flags: MetadataFlags,
77
78    /// Attributes attached to the property declaration.
79    pub attributes: Vec<AttributeMetadata>,
80
81    /// Metadata for property hooks (get/set).
82    ///
83    /// Key is the hook name atom ("get" or "set").
84    /// Only present for PHP 8.4+ hooked properties.
85    pub hooks: WordMap<PropertyHookMetadata>,
86
87    /// PHP version range in which this property is available, derived from
88    /// `Mago\AvailableSince` / `Mago\AvailableUntil` attributes during
89    /// scanning.
90    pub version_constraint: VersionConstraint,
91}
92
93impl PropertyMetadata {
94    /// Creates new `PropertyMetadata` with basic defaults (public, non-static, non-readonly, etc.).
95    /// Name is mandatory. Spans, types, and flags can be set using modifier methods.
96    #[inline]
97    #[must_use]
98    pub fn new(name: VariableIdentifier, flags: MetadataFlags) -> Self {
99        Self {
100            name,
101            name_span: None,
102            span: None,
103            read_visibility: Visibility::Public,
104            write_visibility: Visibility::Public,
105            type_declaration_metadata: None,
106            type_metadata: None,
107            write_type_metadata: None,
108            default_type_metadata: None,
109            flags,
110            attributes: Vec::new(),
111            hooks: WordMap::default(),
112            version_constraint: VersionConstraint::unconstrained(),
113        }
114    }
115
116    /// Returns `true` when this property is available in the given PHP
117    /// version.
118    #[inline]
119    #[must_use]
120    pub fn is_available_in_version(&self, version: PHPVersion) -> bool {
121        self.version_constraint.allows_version(version)
122    }
123
124    /// Returns `true` when this property is available across the entire
125    /// supplied [`PHPVersionRange`].
126    #[inline]
127    #[must_use]
128    pub fn is_available_in_version_range(&self, range: PHPVersionRange) -> bool {
129        self.version_constraint.allows_version_range(range)
130    }
131
132    #[inline]
133    pub fn set_default_type_metadata(&mut self, default_type_metadata: Option<TypeMetadata>) {
134        self.default_type_metadata = default_type_metadata;
135    }
136
137    #[inline]
138    pub fn set_type_declaration_metadata(&mut self, type_declaration_metadata: Option<TypeMetadata>) {
139        if self.type_metadata.is_none() {
140            self.type_metadata.clone_from(&type_declaration_metadata);
141        }
142
143        self.type_declaration_metadata = type_declaration_metadata;
144    }
145
146    #[inline]
147    pub fn set_type_metadata(&mut self, type_metadata: Option<TypeMetadata>) {
148        self.type_metadata = type_metadata;
149    }
150
151    /// Returns the type accepted when writing to the property: the distinct write type
152    /// when one is declared, the read type otherwise.
153    #[inline]
154    #[must_use]
155    pub fn get_write_type_metadata(&self) -> Option<&TypeMetadata> {
156        self.write_type_metadata.as_ref().or(self.type_metadata.as_ref())
157    }
158
159    /// Returns a reference to the property's name identifier.
160    #[inline]
161    #[must_use]
162    pub fn get_name(&self) -> &VariableIdentifier {
163        &self.name
164    }
165
166    /// Checks if the property is effectively final (private read access).
167    ///
168    /// A property with `private(set)` (private write but public read) is NOT final
169    /// because child classes can still read and override it.
170    #[inline]
171    #[must_use]
172    pub fn is_final(&self) -> bool {
173        self.read_visibility.is_private()
174    }
175
176    /// Sets the span for the property name identifier.
177    #[inline]
178    pub fn set_name_span(&mut self, name_span: Option<Span>) {
179        self.name_span = name_span;
180    }
181
182    /// Sets the overall span for the property declaration.
183    #[inline]
184    pub fn set_span(&mut self, span: Option<Span>) {
185        self.span = span;
186    }
187
188    /// Sets both read and write visibility levels. Updates `is_asymmetric`. Ensures virtual properties remain symmetric.
189    #[inline]
190    pub fn set_visibility(&mut self, read: Visibility, write: Visibility) {
191        self.read_visibility = read;
192        self.write_visibility = write;
193        self.update_asymmetric();
194    }
195
196    /// Sets whether the property uses property hooks. Updates `is_asymmetric`.
197    #[inline]
198    pub fn set_is_virtual(&mut self, is_virtual: bool) {
199        self.flags.set(MetadataFlags::VIRTUAL_PROPERTY, is_virtual);
200
201        self.update_asymmetric();
202    }
203
204    /// Also ensures virtual properties are not asymmetric.
205    #[inline]
206    fn update_asymmetric(&mut self) {
207        if self.flags.is_virtual_property() {
208            if self.read_visibility != self.write_visibility {
209                // If virtual and somehow asymmetric, force symmetry (prefer read)
210                self.write_visibility = self.read_visibility;
211            }
212
213            self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
214        } else if self.read_visibility == self.write_visibility {
215            // If both visibilities are the same, ensure no asymmetric flag is set
216            self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
217        } else {
218            // Otherwise, set the asymmetric flag
219            self.flags |= MetadataFlags::ASYMMETRIC_PROPERTY;
220        }
221    }
222}