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