Skip to main content

mir_codebase/
members.rs

1//! Visible-members query for autocomplete / LSP completions.
2//!
3//! Given a `Union` type, returns all methods, properties, and constants that
4//! are visible on that type, walking the full inheritance hierarchy.
5
6use std::sync::Arc;
7
8use mir_types::{Atomic, Union};
9
10use crate::codebase::Codebase;
11use crate::storage::Visibility;
12
13/// A single member visible on a type.
14#[derive(Debug, Clone)]
15pub struct MemberInfo {
16    /// Member name (without `$` prefix for properties).
17    pub name: Arc<str>,
18    /// What kind of member this is.
19    pub kind: MemberKind,
20    /// The resolved type of this member (return type for methods, property type, constant type).
21    pub ty: Option<Union>,
22    /// Visibility (public/protected/private).
23    pub visibility: Visibility,
24    /// Whether this is a static member.
25    pub is_static: bool,
26    /// The FQCN of the class that declares this member.
27    pub declaring_class: Arc<str>,
28    /// Whether this member is deprecated.
29    pub is_deprecated: bool,
30    /// Method parameters (empty for properties/constants).
31    pub params: Vec<crate::storage::FnParam>,
32}
33
34/// The kind of class member.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum MemberKind {
37    Method,
38    Property,
39    Constant,
40    EnumCase,
41}
42
43impl Codebase {
44    /// Return all members (methods, properties, constants) visible on the given type.
45    ///
46    /// Walks the full class hierarchy including parents, interfaces, traits, and enums.
47    /// For union types, returns the union of members from all constituent types.
48    pub fn visible_members(&self, ty: &Union) -> Vec<MemberInfo> {
49        let mut result = Vec::new();
50        let mut seen = std::collections::HashSet::new();
51
52        for atomic in &ty.types {
53            if let Atomic::TNamedObject { fqcn, .. } = atomic {
54                self.collect_members_for_fqcn(fqcn, &mut result, &mut seen);
55            }
56        }
57
58        result
59    }
60
61    /// Collect all visible members for a single FQCN.
62    fn collect_members_for_fqcn(
63        &self,
64        fqcn: &str,
65        out: &mut Vec<MemberInfo>,
66        seen: &mut std::collections::HashSet<(String, MemberKind)>,
67    ) {
68        // --- Class ---
69        if let Some(cls) = self.classes.get(fqcn) {
70            // Methods: all_methods already includes inherited + trait methods.
71            for (name, method) in &cls.all_methods {
72                let key = (name.to_string(), MemberKind::Method);
73                if seen.insert(key) {
74                    out.push(MemberInfo {
75                        name: name.clone(),
76                        kind: MemberKind::Method,
77                        ty: method.effective_return_type().cloned(),
78                        visibility: method.visibility,
79                        is_static: method.is_static,
80                        declaring_class: method.fqcn.clone(),
81                        is_deprecated: method.is_deprecated,
82                        params: method.params.clone(),
83                    });
84                }
85            }
86            // Also check own_methods for anything not in all_methods
87            for (name, method) in &cls.own_methods {
88                let key = (name.to_string(), MemberKind::Method);
89                if seen.insert(key) {
90                    out.push(MemberInfo {
91                        name: name.clone(),
92                        kind: MemberKind::Method,
93                        ty: method.effective_return_type().cloned(),
94                        visibility: method.visibility,
95                        is_static: method.is_static,
96                        declaring_class: method.fqcn.clone(),
97                        is_deprecated: method.is_deprecated,
98                        params: method.params.clone(),
99                    });
100                }
101            }
102
103            // Properties: walk own + ancestors (all_parents includes parent chain + traits)
104            let all_parents = cls.all_parents.clone();
105            // Own properties first
106            for (name, prop) in &cls.own_properties {
107                let key = (name.to_string(), MemberKind::Property);
108                if seen.insert(key) {
109                    out.push(MemberInfo {
110                        name: name.clone(),
111                        kind: MemberKind::Property,
112                        ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
113                        visibility: prop.visibility,
114                        is_static: prop.is_static,
115                        declaring_class: cls.fqcn.clone(),
116                        is_deprecated: false,
117                        params: vec![],
118                    });
119                }
120            }
121            // Own constants
122            for (name, con) in &cls.own_constants {
123                let key = (name.to_string(), MemberKind::Constant);
124                if seen.insert(key) {
125                    out.push(MemberInfo {
126                        name: name.clone(),
127                        kind: MemberKind::Constant,
128                        ty: Some(con.ty.clone()),
129                        visibility: con.visibility.unwrap_or(Visibility::Public),
130                        is_static: true,
131                        declaring_class: cls.fqcn.clone(),
132                        is_deprecated: false,
133                        params: vec![],
134                    });
135                }
136            }
137            drop(cls);
138
139            // Ancestor properties and constants
140            for ancestor_fqcn in &all_parents {
141                if let Some(ancestor) = self.classes.get(ancestor_fqcn.as_ref()) {
142                    for (name, prop) in &ancestor.own_properties {
143                        let key = (name.to_string(), MemberKind::Property);
144                        if seen.insert(key) {
145                            out.push(MemberInfo {
146                                name: name.clone(),
147                                kind: MemberKind::Property,
148                                ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
149                                visibility: prop.visibility,
150                                is_static: prop.is_static,
151                                declaring_class: ancestor.fqcn.clone(),
152                                is_deprecated: false,
153                                params: vec![],
154                            });
155                        }
156                    }
157                    for (name, con) in &ancestor.own_constants {
158                        let key = (name.to_string(), MemberKind::Constant);
159                        if seen.insert(key) {
160                            out.push(MemberInfo {
161                                name: name.clone(),
162                                kind: MemberKind::Constant,
163                                ty: Some(con.ty.clone()),
164                                visibility: con.visibility.unwrap_or(Visibility::Public),
165                                is_static: true,
166                                declaring_class: ancestor.fqcn.clone(),
167                                is_deprecated: false,
168                                params: vec![],
169                            });
170                        }
171                    }
172                }
173                // Trait properties
174                if let Some(tr) = self.traits.get(ancestor_fqcn.as_ref()) {
175                    for (name, prop) in &tr.own_properties {
176                        let key = (name.to_string(), MemberKind::Property);
177                        if seen.insert(key) {
178                            out.push(MemberInfo {
179                                name: name.clone(),
180                                kind: MemberKind::Property,
181                                ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
182                                visibility: prop.visibility,
183                                is_static: prop.is_static,
184                                declaring_class: tr.fqcn.clone(),
185                                is_deprecated: false,
186                                params: vec![],
187                            });
188                        }
189                    }
190                }
191            }
192
193            // Trait properties for directly-used traits (not in all_parents)
194            let traits = {
195                if let Some(cls) = self.classes.get(fqcn) {
196                    cls.traits.clone()
197                } else {
198                    vec![]
199                }
200            };
201            for trait_fqcn in &traits {
202                if let Some(tr) = self.traits.get(trait_fqcn.as_ref()) {
203                    for (name, prop) in &tr.own_properties {
204                        let key = (name.to_string(), MemberKind::Property);
205                        if seen.insert(key) {
206                            out.push(MemberInfo {
207                                name: name.clone(),
208                                kind: MemberKind::Property,
209                                ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
210                                visibility: prop.visibility,
211                                is_static: prop.is_static,
212                                declaring_class: tr.fqcn.clone(),
213                                is_deprecated: false,
214                                params: vec![],
215                            });
216                        }
217                    }
218                }
219            }
220
221            return;
222        }
223
224        // --- Interface ---
225        if let Some(iface) = self.interfaces.get(fqcn) {
226            for (name, method) in &iface.own_methods {
227                let key = (name.to_string(), MemberKind::Method);
228                if seen.insert(key) {
229                    out.push(MemberInfo {
230                        name: name.clone(),
231                        kind: MemberKind::Method,
232                        ty: method.effective_return_type().cloned(),
233                        visibility: method.visibility,
234                        is_static: method.is_static,
235                        declaring_class: method.fqcn.clone(),
236                        is_deprecated: method.is_deprecated,
237                        params: method.params.clone(),
238                    });
239                }
240            }
241            for (name, con) in &iface.own_constants {
242                let key = (name.to_string(), MemberKind::Constant);
243                if seen.insert(key) {
244                    out.push(MemberInfo {
245                        name: name.clone(),
246                        kind: MemberKind::Constant,
247                        ty: Some(con.ty.clone()),
248                        visibility: con.visibility.unwrap_or(Visibility::Public),
249                        is_static: true,
250                        declaring_class: iface.fqcn.clone(),
251                        is_deprecated: false,
252                        params: vec![],
253                    });
254                }
255            }
256            let parents = iface.all_parents.clone();
257            drop(iface);
258            for parent_fqcn in &parents {
259                // Recurse into parent interfaces
260                self.collect_members_for_fqcn(parent_fqcn, out, seen);
261            }
262            return;
263        }
264
265        // --- Enum ---
266        if let Some(en) = self.enums.get(fqcn) {
267            // Enum cases
268            for (name, case) in &en.cases {
269                let key = (name.to_string(), MemberKind::EnumCase);
270                if seen.insert(key) {
271                    out.push(MemberInfo {
272                        name: name.clone(),
273                        kind: MemberKind::EnumCase,
274                        ty: case.value.clone(),
275                        visibility: Visibility::Public,
276                        is_static: true,
277                        declaring_class: en.fqcn.clone(),
278                        is_deprecated: false,
279                        params: vec![],
280                    });
281                }
282            }
283            // Enum methods
284            for (name, method) in &en.own_methods {
285                let key = (name.to_string(), MemberKind::Method);
286                if seen.insert(key) {
287                    out.push(MemberInfo {
288                        name: name.clone(),
289                        kind: MemberKind::Method,
290                        ty: method.effective_return_type().cloned(),
291                        visibility: method.visibility,
292                        is_static: method.is_static,
293                        declaring_class: method.fqcn.clone(),
294                        is_deprecated: method.is_deprecated,
295                        params: method.params.clone(),
296                    });
297                }
298            }
299            // Enum constants
300            for (name, con) in &en.own_constants {
301                let key = (name.to_string(), MemberKind::Constant);
302                if seen.insert(key) {
303                    out.push(MemberInfo {
304                        name: name.clone(),
305                        kind: MemberKind::Constant,
306                        ty: Some(con.ty.clone()),
307                        visibility: con.visibility.unwrap_or(Visibility::Public),
308                        is_static: true,
309                        declaring_class: en.fqcn.clone(),
310                        is_deprecated: false,
311                        params: vec![],
312                    });
313                }
314            }
315            return;
316        }
317
318        // --- Trait (rare: variable typed as a trait) ---
319        if let Some(tr) = self.traits.get(fqcn) {
320            for (name, method) in &tr.own_methods {
321                let key = (name.to_string(), MemberKind::Method);
322                if seen.insert(key) {
323                    out.push(MemberInfo {
324                        name: name.clone(),
325                        kind: MemberKind::Method,
326                        ty: method.effective_return_type().cloned(),
327                        visibility: method.visibility,
328                        is_static: method.is_static,
329                        declaring_class: method.fqcn.clone(),
330                        is_deprecated: method.is_deprecated,
331                        params: method.params.clone(),
332                    });
333                }
334            }
335            for (name, prop) in &tr.own_properties {
336                let key = (name.to_string(), MemberKind::Property);
337                if seen.insert(key) {
338                    out.push(MemberInfo {
339                        name: name.clone(),
340                        kind: MemberKind::Property,
341                        ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
342                        visibility: prop.visibility,
343                        is_static: prop.is_static,
344                        declaring_class: tr.fqcn.clone(),
345                        is_deprecated: false,
346                        params: vec![],
347                    });
348                }
349            }
350        }
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use crate::storage::*;
358    use indexmap::IndexMap;
359
360    fn make_method(name: &str, fqcn: &str) -> MethodStorage {
361        MethodStorage {
362            name: Arc::from(name),
363            fqcn: Arc::from(fqcn),
364            params: vec![],
365            return_type: Some(Union::single(Atomic::TString)),
366            inferred_return_type: None,
367            visibility: Visibility::Public,
368            is_static: false,
369            is_abstract: false,
370            is_final: false,
371            is_constructor: false,
372            template_params: vec![],
373            assertions: vec![],
374            throws: vec![],
375            is_deprecated: false,
376            is_internal: false,
377            is_pure: false,
378            location: None,
379        }
380    }
381
382    #[test]
383    fn visible_members_includes_inherited() {
384        let cb = Codebase::new();
385
386        // Parent class with a method
387        let mut parent_methods = IndexMap::new();
388        parent_methods.insert(
389            Arc::from("parentMethod"),
390            make_method("parentMethod", "Parent"),
391        );
392        cb.classes.insert(
393            Arc::from("Parent"),
394            ClassStorage {
395                fqcn: Arc::from("Parent"),
396                short_name: Arc::from("Parent"),
397                parent: None,
398                interfaces: vec![],
399                traits: vec![],
400                own_methods: parent_methods,
401                own_properties: IndexMap::new(),
402                own_constants: IndexMap::new(),
403                template_params: vec![],
404                is_abstract: false,
405                is_final: false,
406                is_readonly: false,
407                all_methods: IndexMap::new(),
408                all_parents: vec![],
409                is_deprecated: false,
410                is_internal: false,
411                location: None,
412            },
413        );
414
415        // Child class with its own method
416        let mut child_methods = IndexMap::new();
417        child_methods.insert(
418            Arc::from("childMethod"),
419            make_method("childMethod", "Child"),
420        );
421        cb.classes.insert(
422            Arc::from("Child"),
423            ClassStorage {
424                fqcn: Arc::from("Child"),
425                short_name: Arc::from("Child"),
426                parent: Some(Arc::from("Parent")),
427                interfaces: vec![],
428                traits: vec![],
429                own_methods: child_methods,
430                own_properties: IndexMap::new(),
431                own_constants: IndexMap::new(),
432                template_params: vec![],
433                is_abstract: false,
434                is_final: false,
435                is_readonly: false,
436                all_methods: IndexMap::new(),
437                all_parents: vec![],
438                is_deprecated: false,
439                is_internal: false,
440                location: None,
441            },
442        );
443
444        cb.finalize();
445
446        let ty = Union::single(Atomic::TNamedObject {
447            fqcn: Arc::from("Child"),
448            type_params: vec![],
449        });
450        let members = cb.visible_members(&ty);
451        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
452        assert!(names.contains(&"childMethod"), "should have own method");
453        assert!(
454            names.contains(&"parentMethod"),
455            "should have inherited method"
456        );
457    }
458
459    #[test]
460    fn visible_members_union_type() {
461        let cb = Codebase::new();
462
463        let mut a_methods = IndexMap::new();
464        a_methods.insert(Arc::from("aMethod"), make_method("aMethod", "A"));
465        cb.classes.insert(
466            Arc::from("A"),
467            ClassStorage {
468                fqcn: Arc::from("A"),
469                short_name: Arc::from("A"),
470                parent: None,
471                interfaces: vec![],
472                traits: vec![],
473                own_methods: a_methods,
474                own_properties: IndexMap::new(),
475                own_constants: IndexMap::new(),
476                template_params: vec![],
477                is_abstract: false,
478                is_final: false,
479                is_readonly: false,
480                all_methods: IndexMap::new(),
481                all_parents: vec![],
482                is_deprecated: false,
483                is_internal: false,
484                location: None,
485            },
486        );
487
488        let mut b_methods = IndexMap::new();
489        b_methods.insert(Arc::from("bMethod"), make_method("bMethod", "B"));
490        cb.classes.insert(
491            Arc::from("B"),
492            ClassStorage {
493                fqcn: Arc::from("B"),
494                short_name: Arc::from("B"),
495                parent: None,
496                interfaces: vec![],
497                traits: vec![],
498                own_methods: b_methods,
499                own_properties: IndexMap::new(),
500                own_constants: IndexMap::new(),
501                template_params: vec![],
502                is_abstract: false,
503                is_final: false,
504                is_readonly: false,
505                all_methods: IndexMap::new(),
506                all_parents: vec![],
507                is_deprecated: false,
508                is_internal: false,
509                location: None,
510            },
511        );
512
513        cb.finalize();
514
515        let ty = Union::merge(
516            &Union::single(Atomic::TNamedObject {
517                fqcn: Arc::from("A"),
518                type_params: vec![],
519            }),
520            &Union::single(Atomic::TNamedObject {
521                fqcn: Arc::from("B"),
522                type_params: vec![],
523            }),
524        );
525        let members = cb.visible_members(&ty);
526        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
527        assert!(names.contains(&"aMethod"));
528        assert!(names.contains(&"bMethod"));
529    }
530}