mago_codex/metadata/property.rs
1use serde::Deserialize;
2use serde::Serialize;
3
4use mago_atom::AtomMap;
5use mago_span::Span;
6
7use crate::metadata::flags::MetadataFlags;
8use crate::metadata::property_hook::PropertyHookMetadata;
9use crate::metadata::ttype::TypeMetadata;
10use crate::misc::VariableIdentifier;
11use crate::visibility::Visibility;
12
13/// Contains metadata associated with a declared class property in PHP.
14///
15/// This includes information about its name, location, visibility (potentially asymmetric),
16/// type hints, default values, and various modifiers (`static`, `readonly`, `abstract`, etc.).
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18#[non_exhaustive]
19pub struct PropertyMetadata {
20 /// The identifier (name) of the property, including the leading '$'.
21 pub name: VariableIdentifier,
22
23 /// The specific source code location (span) of the property's name identifier itself.
24 /// `None` if the location is unknown or not relevant (e.g., for synthetic properties).
25 pub name_span: Option<Span>,
26
27 /// The source code location (span) covering the entire property declaration statement.
28 /// `None` if the location is unknown or not relevant.
29 pub span: Option<Span>,
30
31 /// The visibility level required for reading the property's value.
32 ///
33 /// In PHP, this corresponds to the primary visibility keyword specified
34 /// (e.g., the `public` in `public private(set) string $prop;`).
35 ///
36 /// If no asymmetric visibility is specified (e.g., `public string $prop`),
37 /// this level applies to both reading and writing. Defaults to `Public`.
38 pub read_visibility: Visibility,
39
40 /// The visibility level required for writing/modifying the property's value.
41 ///
42 /// In PHP, this can differ from `read_visibility` using asymmetric visibility syntax
43 /// like `private(set)` (e.g., `public private(set) string $prop;`).
44 ///
45 /// If asymmetric visibility is not used, this implicitly matches `read_visibility`.
46 /// Defaults to `Public`.
47 pub write_visibility: Visibility,
48
49 /// The explicit type declaration (type hint) associated with the property, if any.
50 ///
51 /// e.g., for `public string $name;`, this would contain the metadata for `string`.
52 pub type_declaration_metadata: Option<TypeMetadata>,
53
54 /// The type metadata for the property's type, if any.
55 ///
56 /// This is either the same as `type_declaration_metadata` or the type provided
57 /// in a docblock comment (e.g., `@var string`).
58 pub type_metadata: Option<TypeMetadata>,
59
60 /// The type inferred from the property's default value, if it has one.
61 ///
62 /// e.g., for `public $count = 0;`, this would contain the metadata for `int(0)`.
63 /// This can be used to compare against `type_signature` for consistency checks.
64 pub default_type_metadata: Option<TypeMetadata>,
65
66 /// Flags indicating various properties of the property.
67 pub flags: MetadataFlags,
68
69 /// Metadata for property hooks (get/set).
70 ///
71 /// Key is the hook name atom ("get" or "set").
72 /// Only present for PHP 8.4+ hooked properties.
73 pub hooks: AtomMap<PropertyHookMetadata>,
74}
75
76impl PropertyMetadata {
77 /// Creates new `PropertyMetadata` with basic defaults (public, non-static, non-readonly, etc.).
78 /// Name is mandatory. Spans, types, and flags can be set using modifier methods.
79 #[inline]
80 #[must_use]
81 pub fn new(name: VariableIdentifier, flags: MetadataFlags) -> Self {
82 Self {
83 name,
84 name_span: None,
85 span: None,
86 read_visibility: Visibility::Public,
87 write_visibility: Visibility::Public,
88 type_declaration_metadata: None,
89 type_metadata: None,
90 default_type_metadata: None,
91 flags,
92 hooks: AtomMap::default(),
93 }
94 }
95
96 #[inline]
97 pub fn set_default_type_metadata(&mut self, default_type_metadata: Option<TypeMetadata>) {
98 self.default_type_metadata = default_type_metadata;
99 }
100
101 #[inline]
102 pub fn set_type_declaration_metadata(&mut self, type_declaration_metadata: Option<TypeMetadata>) {
103 if self.type_metadata.is_none() {
104 self.type_metadata.clone_from(&type_declaration_metadata);
105 }
106
107 self.type_declaration_metadata = type_declaration_metadata;
108 }
109
110 #[inline]
111 pub fn set_type_metadata(&mut self, type_metadata: Option<TypeMetadata>) {
112 self.type_metadata = type_metadata;
113 }
114
115 /// Returns a reference to the property's name identifier.
116 #[inline]
117 #[must_use]
118 pub fn get_name(&self) -> &VariableIdentifier {
119 &self.name
120 }
121
122 /// Checks if the property is effectively final (private read or write access).
123 #[inline]
124 #[must_use]
125 pub fn is_final(&self) -> bool {
126 self.read_visibility.is_private() || self.write_visibility.is_private()
127 }
128
129 /// Sets the span for the property name identifier.
130 #[inline]
131 pub fn set_name_span(&mut self, name_span: Option<Span>) {
132 self.name_span = name_span;
133 }
134
135 /// Sets the overall span for the property declaration.
136 #[inline]
137 pub fn set_span(&mut self, span: Option<Span>) {
138 self.span = span;
139 }
140
141 /// Sets both read and write visibility levels. Updates `is_asymmetric`. Ensures virtual properties remain symmetric.
142 #[inline]
143 pub fn set_visibility(&mut self, read: Visibility, write: Visibility) {
144 self.read_visibility = read;
145 self.write_visibility = write;
146 self.update_asymmetric();
147 }
148
149 /// Sets whether the property uses property hooks. Updates `is_asymmetric`.
150 #[inline]
151 pub fn set_is_virtual(&mut self, is_virtual: bool) {
152 self.flags.set(MetadataFlags::VIRTUAL_PROPERTY, is_virtual);
153
154 self.update_asymmetric();
155 }
156
157 /// Also ensures virtual properties are not asymmetric.
158 #[inline]
159 fn update_asymmetric(&mut self) {
160 if self.flags.is_virtual_property() {
161 if self.read_visibility != self.write_visibility {
162 // If virtual and somehow asymmetric, force symmetry (prefer read)
163 self.write_visibility = self.read_visibility;
164 }
165
166 self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
167 } else if self.read_visibility == self.write_visibility {
168 // If both visibilities are the same, ensure no asymmetric flag is set
169 self.flags &= !MetadataFlags::ASYMMETRIC_PROPERTY;
170 } else {
171 // Otherwise, set the asymmetric flag
172 self.flags |= MetadataFlags::ASYMMETRIC_PROPERTY;
173 }
174 }
175}