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