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 inferred from the property's default value, if it has one.
63 ///
64 /// e.g., for `public $count = 0;`, this would contain the metadata for `int(0)`.
65 /// This can be used to compare against `type_signature` for consistency checks.
66 pub default_type_metadata: Option<TypeMetadata>,
67
68 /// Flags indicating various properties of the property.
69 pub flags: MetadataFlags,
70
71 /// Metadata for property hooks (get/set).
72 ///
73 /// Key is the hook name atom ("get" or "set").
74 /// Only present for PHP 8.4+ hooked properties.
75 pub hooks: WordMap<PropertyHookMetadata>,
76
77 /// PHP version range in which this property is available, derived from
78 /// `Mago\AvailableSince` / `Mago\AvailableUntil` attributes during
79 /// scanning.
80 pub version_constraint: VersionConstraint,
81}
82
83impl PropertyMetadata {
84 /// Creates new `PropertyMetadata` with basic defaults (public, non-static, non-readonly, etc.).
85 /// Name is mandatory. Spans, types, and flags can be set using modifier methods.
86 #[inline]
87 #[must_use]
88 pub fn new(name: VariableIdentifier, flags: MetadataFlags) -> Self {
89 Self {
90 name,
91 name_span: None,
92 span: None,
93 read_visibility: Visibility::Public,
94 write_visibility: Visibility::Public,
95 type_declaration_metadata: None,
96 type_metadata: None,
97 default_type_metadata: None,
98 flags,
99 hooks: WordMap::default(),
100 version_constraint: VersionConstraint::unconstrained(),
101 }
102 }
103
104 /// Returns `true` when this property is available in the given PHP
105 /// version.
106 #[inline]
107 #[must_use]
108 pub fn is_available_in_version(&self, version: PHPVersion) -> bool {
109 self.version_constraint.allows_version(version)
110 }
111
112 /// Returns `true` when this property is available across the entire
113 /// supplied [`PHPVersionRange`].
114 #[inline]
115 #[must_use]
116 pub fn is_available_in_version_range(&self, range: PHPVersionRange) -> bool {
117 self.version_constraint.allows_version_range(range)
118 }
119
120 #[inline]
121 pub fn set_default_type_metadata(&mut self, default_type_metadata: Option<TypeMetadata>) {
122 self.default_type_metadata = default_type_metadata;
123 }
124
125 #[inline]
126 pub fn set_type_declaration_metadata(&mut self, type_declaration_metadata: Option<TypeMetadata>) {
127 if self.type_metadata.is_none() {
128 self.type_metadata.clone_from(&type_declaration_metadata);
129 }
130
131 self.type_declaration_metadata = type_declaration_metadata;
132 }
133
134 #[inline]
135 pub fn set_type_metadata(&mut self, type_metadata: Option<TypeMetadata>) {
136 self.type_metadata = type_metadata;
137 }
138
139 /// Returns a reference to the property's name identifier.
140 #[inline]
141 #[must_use]
142 pub fn get_name(&self) -> &VariableIdentifier {
143 &self.name
144 }
145
146 /// Checks if the property is effectively final (private read access).
147 ///
148 /// A property with `private(set)` (private write but public read) is NOT final
149 /// because child classes can still read and override it.
150 #[inline]
151 #[must_use]
152 pub fn is_final(&self) -> bool {
153 self.read_visibility.is_private()
154 }
155
156 /// Sets the span for the property name identifier.
157 #[inline]
158 pub fn set_name_span(&mut self, name_span: Option<Span>) {
159 self.name_span = name_span;
160 }
161
162 /// Sets the overall span for the property declaration.
163 #[inline]
164 pub fn set_span(&mut self, span: Option<Span>) {
165 self.span = span;
166 }
167
168 /// Sets both read and write visibility levels. Updates `is_asymmetric`. Ensures virtual properties remain symmetric.
169 #[inline]
170 pub fn set_visibility(&mut self, read: Visibility, write: Visibility) {
171 self.read_visibility = read;
172 self.write_visibility = write;
173 self.update_asymmetric();
174 }
175
176 /// Sets whether the property uses property hooks. Updates `is_asymmetric`.
177 #[inline]
178 pub fn set_is_virtual(&mut self, is_virtual: bool) {
179 self.flags.set(MetadataFlags::VIRTUAL_PROPERTY, is_virtual);
180
181 self.update_asymmetric();
182 }
183
184 /// Also ensures virtual properties are not asymmetric.
185 #[inline]
186 fn update_asymmetric(&mut self) {
187 if self.flags.is_virtual_property() {
188 if self.read_visibility != self.write_visibility {
189 // If virtual and somehow asymmetric, force symmetry (prefer read)
190 self.write_visibility = self.read_visibility;
191 }
192
193 self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
194 } else if self.read_visibility == self.write_visibility {
195 // If both visibilities are the same, ensure no asymmetric flag is set
196 self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
197 } else {
198 // Otherwise, set the asymmetric flag
199 self.flags |= MetadataFlags::ASYMMETRIC_PROPERTY;
200 }
201 }
202}