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::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}