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}