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}