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