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            // Own methods (highest priority — first in, wins via `seen` dedup)
71            for (name, method) in &cls.own_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
87            // Collect chain before dropping the DashMap guard.
88            let own_traits = cls.traits.clone();
89            let all_parents = cls.all_parents.clone();
90            let cls_fqcn = cls.fqcn.clone();
91
92            // Own properties and constants
93            for (name, prop) in &cls.own_properties {
94                let key = (name.to_string(), MemberKind::Property);
95                if seen.insert(key) {
96                    out.push(MemberInfo {
97                        name: name.clone(),
98                        kind: MemberKind::Property,
99                        ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
100                        visibility: prop.visibility,
101                        is_static: prop.is_static,
102                        declaring_class: cls_fqcn.clone(),
103                        is_deprecated: false,
104                        params: vec![],
105                    });
106                }
107            }
108            for (name, con) in &cls.own_constants {
109                let key = (name.to_string(), MemberKind::Constant);
110                if seen.insert(key) {
111                    out.push(MemberInfo {
112                        name: name.clone(),
113                        kind: MemberKind::Constant,
114                        ty: Some(con.ty.clone()),
115                        visibility: con.visibility.unwrap_or(Visibility::Public),
116                        is_static: true,
117                        declaring_class: cls_fqcn.clone(),
118                        is_deprecated: false,
119                        params: vec![],
120                    });
121                }
122            }
123            drop(cls);
124
125            // Own trait methods and properties
126            for tr_fqcn in &own_traits {
127                if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
128                    for (name, method) in &tr.own_methods {
129                        let key = (name.to_string(), MemberKind::Method);
130                        if seen.insert(key) {
131                            out.push(MemberInfo {
132                                name: name.clone(),
133                                kind: MemberKind::Method,
134                                ty: method.effective_return_type().cloned(),
135                                visibility: method.visibility,
136                                is_static: method.is_static,
137                                declaring_class: method.fqcn.clone(),
138                                is_deprecated: method.is_deprecated,
139                                params: method.params.clone(),
140                            });
141                        }
142                    }
143                    for (name, prop) in &tr.own_properties {
144                        let key = (name.to_string(), MemberKind::Property);
145                        if seen.insert(key) {
146                            out.push(MemberInfo {
147                                name: name.clone(),
148                                kind: MemberKind::Property,
149                                ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
150                                visibility: prop.visibility,
151                                is_static: prop.is_static,
152                                declaring_class: tr.fqcn.clone(),
153                                is_deprecated: false,
154                                params: vec![],
155                            });
156                        }
157                    }
158                }
159            }
160
161            // Ancestor classes, their traits, and interfaces from all_parents
162            for ancestor_fqcn in &all_parents {
163                if let Some(ancestor) = self.classes.get(ancestor_fqcn.as_ref()) {
164                    for (name, method) in &ancestor.own_methods {
165                        let key = (name.to_string(), MemberKind::Method);
166                        if seen.insert(key) {
167                            out.push(MemberInfo {
168                                name: name.clone(),
169                                kind: MemberKind::Method,
170                                ty: method.effective_return_type().cloned(),
171                                visibility: method.visibility,
172                                is_static: method.is_static,
173                                declaring_class: method.fqcn.clone(),
174                                is_deprecated: method.is_deprecated,
175                                params: method.params.clone(),
176                            });
177                        }
178                    }
179                    for (name, prop) in &ancestor.own_properties {
180                        let key = (name.to_string(), MemberKind::Property);
181                        if seen.insert(key) {
182                            out.push(MemberInfo {
183                                name: name.clone(),
184                                kind: MemberKind::Property,
185                                ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
186                                visibility: prop.visibility,
187                                is_static: prop.is_static,
188                                declaring_class: ancestor.fqcn.clone(),
189                                is_deprecated: false,
190                                params: vec![],
191                            });
192                        }
193                    }
194                    for (name, con) in &ancestor.own_constants {
195                        let key = (name.to_string(), MemberKind::Constant);
196                        if seen.insert(key) {
197                            out.push(MemberInfo {
198                                name: name.clone(),
199                                kind: MemberKind::Constant,
200                                ty: Some(con.ty.clone()),
201                                visibility: con.visibility.unwrap_or(Visibility::Public),
202                                is_static: true,
203                                declaring_class: ancestor.fqcn.clone(),
204                                is_deprecated: false,
205                                params: vec![],
206                            });
207                        }
208                    }
209                    let anc_traits = ancestor.traits.clone();
210                    drop(ancestor);
211                    for tr_fqcn in &anc_traits {
212                        if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
213                            for (name, method) in &tr.own_methods {
214                                let key = (name.to_string(), MemberKind::Method);
215                                if seen.insert(key) {
216                                    out.push(MemberInfo {
217                                        name: name.clone(),
218                                        kind: MemberKind::Method,
219                                        ty: method.effective_return_type().cloned(),
220                                        visibility: method.visibility,
221                                        is_static: method.is_static,
222                                        declaring_class: method.fqcn.clone(),
223                                        is_deprecated: method.is_deprecated,
224                                        params: method.params.clone(),
225                                    });
226                                }
227                            }
228                            for (name, prop) in &tr.own_properties {
229                                let key = (name.to_string(), MemberKind::Property);
230                                if seen.insert(key) {
231                                    out.push(MemberInfo {
232                                        name: name.clone(),
233                                        kind: MemberKind::Property,
234                                        ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
235                                        visibility: prop.visibility,
236                                        is_static: prop.is_static,
237                                        declaring_class: tr.fqcn.clone(),
238                                        is_deprecated: false,
239                                        params: vec![],
240                                    });
241                                }
242                            }
243                        }
244                    }
245                } else if let Some(iface) = self.interfaces.get(ancestor_fqcn.as_ref()) {
246                    for (name, method) in &iface.own_methods {
247                        let key = (name.to_string(), MemberKind::Method);
248                        if seen.insert(key) {
249                            out.push(MemberInfo {
250                                name: name.clone(),
251                                kind: MemberKind::Method,
252                                ty: method.effective_return_type().cloned(),
253                                visibility: method.visibility,
254                                is_static: method.is_static,
255                                declaring_class: method.fqcn.clone(),
256                                is_deprecated: method.is_deprecated,
257                                params: method.params.clone(),
258                            });
259                        }
260                    }
261                    for (name, con) in &iface.own_constants {
262                        let key = (name.to_string(), MemberKind::Constant);
263                        if seen.insert(key) {
264                            out.push(MemberInfo {
265                                name: name.clone(),
266                                kind: MemberKind::Constant,
267                                ty: Some(con.ty.clone()),
268                                visibility: con.visibility.unwrap_or(Visibility::Public),
269                                is_static: true,
270                                declaring_class: iface.fqcn.clone(),
271                                is_deprecated: false,
272                                params: vec![],
273                            });
274                        }
275                    }
276                }
277                // Traits in all_parents are already covered via their owning class's .traits above.
278            }
279
280            return;
281        }
282
283        // --- Interface ---
284        if let Some(iface) = self.interfaces.get(fqcn) {
285            for (name, method) in &iface.own_methods {
286                let key = (name.to_string(), MemberKind::Method);
287                if seen.insert(key) {
288                    out.push(MemberInfo {
289                        name: name.clone(),
290                        kind: MemberKind::Method,
291                        ty: method.effective_return_type().cloned(),
292                        visibility: method.visibility,
293                        is_static: method.is_static,
294                        declaring_class: method.fqcn.clone(),
295                        is_deprecated: method.is_deprecated,
296                        params: method.params.clone(),
297                    });
298                }
299            }
300            for (name, con) in &iface.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: iface.fqcn.clone(),
310                        is_deprecated: false,
311                        params: vec![],
312                    });
313                }
314            }
315            let parents = iface.all_parents.clone();
316            drop(iface);
317            for parent_fqcn in &parents {
318                // Recurse into parent interfaces
319                self.collect_members_for_fqcn(parent_fqcn, out, seen);
320            }
321            return;
322        }
323
324        // --- Enum ---
325        if let Some(en) = self.enums.get(fqcn) {
326            // Enum cases
327            for (name, case) in &en.cases {
328                let key = (name.to_string(), MemberKind::EnumCase);
329                if seen.insert(key) {
330                    out.push(MemberInfo {
331                        name: name.clone(),
332                        kind: MemberKind::EnumCase,
333                        ty: case.value.clone(),
334                        visibility: Visibility::Public,
335                        is_static: true,
336                        declaring_class: en.fqcn.clone(),
337                        is_deprecated: false,
338                        params: vec![],
339                    });
340                }
341            }
342            // Enum methods
343            for (name, method) in &en.own_methods {
344                let key = (name.to_string(), MemberKind::Method);
345                if seen.insert(key) {
346                    out.push(MemberInfo {
347                        name: name.clone(),
348                        kind: MemberKind::Method,
349                        ty: method.effective_return_type().cloned(),
350                        visibility: method.visibility,
351                        is_static: method.is_static,
352                        declaring_class: method.fqcn.clone(),
353                        is_deprecated: method.is_deprecated,
354                        params: method.params.clone(),
355                    });
356                }
357            }
358            // Enum constants
359            for (name, con) in &en.own_constants {
360                let key = (name.to_string(), MemberKind::Constant);
361                if seen.insert(key) {
362                    out.push(MemberInfo {
363                        name: name.clone(),
364                        kind: MemberKind::Constant,
365                        ty: Some(con.ty.clone()),
366                        visibility: con.visibility.unwrap_or(Visibility::Public),
367                        is_static: true,
368                        declaring_class: en.fqcn.clone(),
369                        is_deprecated: false,
370                        params: vec![],
371                    });
372                }
373            }
374            return;
375        }
376
377        // --- Trait (rare: variable typed as a trait) ---
378        if let Some(tr) = self.traits.get(fqcn) {
379            for (name, method) in &tr.own_methods {
380                let key = (name.to_string(), MemberKind::Method);
381                if seen.insert(key) {
382                    out.push(MemberInfo {
383                        name: name.clone(),
384                        kind: MemberKind::Method,
385                        ty: method.effective_return_type().cloned(),
386                        visibility: method.visibility,
387                        is_static: method.is_static,
388                        declaring_class: method.fqcn.clone(),
389                        is_deprecated: method.is_deprecated,
390                        params: method.params.clone(),
391                    });
392                }
393            }
394            for (name, prop) in &tr.own_properties {
395                let key = (name.to_string(), MemberKind::Property);
396                if seen.insert(key) {
397                    out.push(MemberInfo {
398                        name: name.clone(),
399                        kind: MemberKind::Property,
400                        ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
401                        visibility: prop.visibility,
402                        is_static: prop.is_static,
403                        declaring_class: tr.fqcn.clone(),
404                        is_deprecated: false,
405                        params: vec![],
406                    });
407                }
408            }
409        }
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416    use crate::storage::*;
417    use indexmap::IndexMap;
418
419    fn make_method(name: &str, fqcn: &str) -> MethodStorage {
420        MethodStorage {
421            name: Arc::from(name),
422            fqcn: Arc::from(fqcn),
423            params: vec![],
424            return_type: Some(Union::single(Atomic::TString)),
425            inferred_return_type: None,
426            visibility: Visibility::Public,
427            is_static: false,
428            is_abstract: false,
429            is_final: false,
430            is_constructor: false,
431            template_params: vec![],
432            assertions: vec![],
433            throws: vec![],
434            is_deprecated: false,
435            is_internal: false,
436            is_pure: false,
437            location: None,
438        }
439    }
440
441    #[test]
442    fn visible_members_includes_inherited() {
443        let cb = Codebase::new();
444
445        // Parent class with a method
446        let mut parent_methods = IndexMap::new();
447        parent_methods.insert(
448            Arc::from("parentMethod"),
449            Arc::new(make_method("parentMethod", "Parent")),
450        );
451        cb.classes.insert(
452            Arc::from("Parent"),
453            ClassStorage {
454                fqcn: Arc::from("Parent"),
455                short_name: Arc::from("Parent"),
456                parent: None,
457                interfaces: vec![],
458                traits: vec![],
459                own_methods: parent_methods,
460                own_properties: IndexMap::new(),
461                own_constants: IndexMap::new(),
462                template_params: vec![],
463                extends_type_args: vec![],
464                is_abstract: false,
465                is_final: false,
466                is_readonly: false,
467                all_parents: vec![],
468                is_deprecated: false,
469                is_internal: false,
470                location: None,
471            },
472        );
473
474        // Child class with its own method
475        let mut child_methods = IndexMap::new();
476        child_methods.insert(
477            Arc::from("childMethod"),
478            Arc::new(make_method("childMethod", "Child")),
479        );
480        cb.classes.insert(
481            Arc::from("Child"),
482            ClassStorage {
483                fqcn: Arc::from("Child"),
484                short_name: Arc::from("Child"),
485                parent: Some(Arc::from("Parent")),
486                interfaces: vec![],
487                traits: vec![],
488                own_methods: child_methods,
489                own_properties: IndexMap::new(),
490                own_constants: IndexMap::new(),
491                template_params: vec![],
492                extends_type_args: vec![],
493                is_abstract: false,
494                is_final: false,
495                is_readonly: false,
496                all_parents: vec![],
497                is_deprecated: false,
498                is_internal: false,
499                location: None,
500            },
501        );
502
503        cb.finalize();
504
505        let ty = Union::single(Atomic::TNamedObject {
506            fqcn: Arc::from("Child"),
507            type_params: vec![],
508        });
509        let members = cb.visible_members(&ty);
510        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
511        assert!(names.contains(&"childMethod"), "should have own method");
512        assert!(
513            names.contains(&"parentMethod"),
514            "should have inherited method"
515        );
516    }
517
518    #[test]
519    fn visible_members_union_type() {
520        let cb = Codebase::new();
521
522        let mut a_methods = IndexMap::new();
523        a_methods.insert(Arc::from("aMethod"), Arc::new(make_method("aMethod", "A")));
524        cb.classes.insert(
525            Arc::from("A"),
526            ClassStorage {
527                fqcn: Arc::from("A"),
528                short_name: Arc::from("A"),
529                parent: None,
530                interfaces: vec![],
531                traits: vec![],
532                own_methods: a_methods,
533                own_properties: IndexMap::new(),
534                own_constants: IndexMap::new(),
535                template_params: vec![],
536                extends_type_args: vec![],
537                is_abstract: false,
538                is_final: false,
539                is_readonly: false,
540                all_parents: vec![],
541                is_deprecated: false,
542                is_internal: false,
543                location: None,
544            },
545        );
546
547        let mut b_methods = IndexMap::new();
548        b_methods.insert(Arc::from("bMethod"), Arc::new(make_method("bMethod", "B")));
549        cb.classes.insert(
550            Arc::from("B"),
551            ClassStorage {
552                fqcn: Arc::from("B"),
553                short_name: Arc::from("B"),
554                parent: None,
555                interfaces: vec![],
556                traits: vec![],
557                own_methods: b_methods,
558                own_properties: IndexMap::new(),
559                own_constants: IndexMap::new(),
560                template_params: vec![],
561                extends_type_args: vec![],
562                is_abstract: false,
563                is_final: false,
564                is_readonly: false,
565                all_parents: vec![],
566                is_deprecated: false,
567                is_internal: false,
568                location: None,
569            },
570        );
571
572        cb.finalize();
573
574        let ty = Union::merge(
575            &Union::single(Atomic::TNamedObject {
576                fqcn: Arc::from("A"),
577                type_params: vec![],
578            }),
579            &Union::single(Atomic::TNamedObject {
580                fqcn: Arc::from("B"),
581                type_params: vec![],
582            }),
583        );
584        let members = cb.visible_members(&ty);
585        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
586        assert!(names.contains(&"aMethod"));
587        assert!(names.contains(&"bMethod"));
588    }
589}