Parse `@method`, `@property`, and `@mixin` tags from the class. Uses the existing [`docblock::extract_method_tags`] and [`docblock::extract_property_tags`] functions for tag parsing. Then collects public members from `@mixin` classes. Within the provider, `@method` / `@property` tags take precedence over `@mixin` members.
(
&self,
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
cache: Option<&super::ResolvedClassCache>,
)
| 204 | /// provider, `@method` / `@property` tags take precedence over |
| 205 | /// `@mixin` members. |
| 206 | fn provide( |
| 207 | &self, |
| 208 | class: &ClassInfo, |
| 209 | class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>, |
| 210 | cache: Option<&super::ResolvedClassCache>, |
| 211 | ) -> VirtualMembers { |
| 212 | let mut methods = Vec::new(); |
| 213 | let mut properties = Vec::new(); |
| 214 | let constants = Vec::new(); |
| 215 | |
| 216 | // Dedup sets for O(1) membership checks. Seeded from the |
| 217 | // base-resolved class members (real + inherited) and updated |
| 218 | // as virtual members are collected. |
| 219 | // |
| 220 | // `seen_props` is NOT seeded from existing class properties. |
| 221 | // Phase 1 (`@property` tags) always emits its properties so |
| 222 | // that `merge_virtual_members` can compare type specificity |
| 223 | // and keep the most specific type (e.g. `array<string>` from |
| 224 | // `@property` beats bare `array` from `$casts`). After |
| 225 | // phase 1 emits, names are added to `seen_props` to prevent |
| 226 | // lower-priority sources (trait tags, parent tags, `@mixin` |
| 227 | // members) from overriding them. |
| 228 | let mut seen_methods: HashSet<String> = |
| 229 | class.methods.iter().map(|m| m.name.to_string()).collect(); |
| 230 | let mut seen_props: HashSet<String> = HashSet::new(); |
| 231 | let seen_consts: HashSet<String> = |
| 232 | class.constants.iter().map(|c| c.name.to_string()).collect(); |
| 233 | |
| 234 | // ── Phase 1: @method and @property tags (higher precedence) ───── |
| 235 | |
| 236 | if let Some(doc_text) = class.class_docblock.as_deref() |
| 237 | && !doc_text.is_empty() |
| 238 | { |
| 239 | for m in docblock::extract_method_tags(doc_text) { |
| 240 | seen_methods.insert(m.name.to_string()); |
| 241 | methods.push(m); |
| 242 | } |
| 243 | |
| 244 | for (name, type_hint) in docblock::extract_property_tags(doc_text) { |
| 245 | seen_props.insert(name.clone()); |
| 246 | properties.push(PropertyInfo { |
| 247 | name: atom(&name), |
| 248 | name_offset: 0, |
| 249 | type_hint, |
| 250 | native_type_hint: None, |
| 251 | description: None, |
| 252 | is_static: false, |
| 253 | visibility: Visibility::Public, |
| 254 | deprecation_message: None, |
| 255 | deprecated_replacement: None, |
| 256 | see_refs: Vec::new(), |
| 257 | is_virtual: true, |
| 258 | }); |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | // ── Phase 1b: @method and @property tags from used traits ─────── |
| 263 | // |
nothing calls this directly
no test coverage detected