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::{ConstantStorage, MethodStorage, PropertyStorage, 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    /// Deprecation message if this member is deprecated, or None if not.
29    pub deprecated: Option<Arc<str>>,
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
43// ---------------------------------------------------------------------------
44// Push helpers — each deduplicates via `seen` before inserting
45// ---------------------------------------------------------------------------
46
47type Seen = std::collections::HashSet<(String, MemberKind)>;
48
49fn push_method(
50    name: &Arc<str>,
51    method: &MethodStorage,
52    out: &mut Vec<MemberInfo>,
53    seen: &mut Seen,
54) {
55    if seen.insert((name.to_string(), MemberKind::Method)) {
56        out.push(MemberInfo {
57            name: name.clone(),
58            kind: MemberKind::Method,
59            ty: method.effective_return_type().cloned(),
60            visibility: method.visibility,
61            is_static: method.is_static,
62            declaring_class: method.fqcn.clone(),
63            deprecated: method.deprecated.clone(),
64            params: method.params.clone(),
65        });
66    }
67}
68
69fn push_property(
70    name: &Arc<str>,
71    prop: &PropertyStorage,
72    declaring_class: Arc<str>,
73    out: &mut Vec<MemberInfo>,
74    seen: &mut Seen,
75) {
76    if seen.insert((name.to_string(), MemberKind::Property)) {
77        out.push(MemberInfo {
78            name: name.clone(),
79            kind: MemberKind::Property,
80            ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
81            visibility: prop.visibility,
82            is_static: prop.is_static,
83            declaring_class,
84            deprecated: None,
85            params: vec![],
86        });
87    }
88}
89
90fn push_constant(
91    name: &Arc<str>,
92    con: &ConstantStorage,
93    declaring_class: Arc<str>,
94    out: &mut Vec<MemberInfo>,
95    seen: &mut Seen,
96) {
97    if seen.insert((name.to_string(), MemberKind::Constant)) {
98        out.push(MemberInfo {
99            name: name.clone(),
100            kind: MemberKind::Constant,
101            ty: Some(con.ty.clone()),
102            visibility: con.visibility.unwrap_or(Visibility::Public),
103            is_static: true,
104            declaring_class,
105            deprecated: None,
106            params: vec![],
107        });
108    }
109}
110
111// ---------------------------------------------------------------------------
112// Codebase query
113// ---------------------------------------------------------------------------
114
115impl Codebase {
116    /// Return all members (methods, properties, constants) visible on the given type.
117    ///
118    /// Walks the full class hierarchy including parents, interfaces, traits, and enums.
119    /// For union types, returns the union of members from all constituent types.
120    pub fn visible_members(&self, ty: &Union) -> Vec<MemberInfo> {
121        let mut result = Vec::new();
122        let mut seen = std::collections::HashSet::new();
123
124        for atomic in &ty.types {
125            if let Atomic::TNamedObject { fqcn, .. } = atomic {
126                self.collect_members_for_fqcn(fqcn, &mut result, &mut seen);
127            }
128        }
129
130        result
131    }
132
133    /// Collect all visible members for a single FQCN.
134    fn collect_members_for_fqcn(&self, fqcn: &str, out: &mut Vec<MemberInfo>, seen: &mut Seen) {
135        self.ensure_finalized(fqcn);
136        // --- Class ---
137        if let Some(cls) = self.classes.get(fqcn) {
138            let cls_fqcn = cls.fqcn.clone();
139
140            for (name, method) in &cls.own_methods {
141                push_method(name, method, out, seen);
142            }
143            for (name, prop) in &cls.own_properties {
144                push_property(name, prop, cls_fqcn.clone(), out, seen);
145            }
146            for (name, con) in &cls.own_constants {
147                push_constant(name, con, cls_fqcn.clone(), out, seen);
148            }
149
150            // Collect chain before dropping the DashMap guard to avoid deadlock.
151            let own_traits = cls.traits.clone();
152            let all_parents = cls.all_parents.clone();
153            drop(cls);
154
155            for tr_fqcn in &own_traits {
156                if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
157                    let tr_fqcn = tr.fqcn.clone();
158                    for (name, method) in &tr.own_methods {
159                        push_method(name, method, out, seen);
160                    }
161                    for (name, prop) in &tr.own_properties {
162                        push_property(name, prop, tr_fqcn.clone(), out, seen);
163                    }
164                }
165            }
166
167            for ancestor_fqcn in &all_parents {
168                if let Some(ancestor) = self.classes.get(ancestor_fqcn.as_ref()) {
169                    let anc_fqcn = ancestor.fqcn.clone();
170                    for (name, method) in &ancestor.own_methods {
171                        push_method(name, method, out, seen);
172                    }
173                    for (name, prop) in &ancestor.own_properties {
174                        push_property(name, prop, anc_fqcn.clone(), out, seen);
175                    }
176                    for (name, con) in &ancestor.own_constants {
177                        push_constant(name, con, anc_fqcn.clone(), out, seen);
178                    }
179                    let anc_traits = ancestor.traits.clone();
180                    drop(ancestor);
181                    for tr_fqcn in &anc_traits {
182                        if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
183                            let tr_fqcn = tr.fqcn.clone();
184                            for (name, method) in &tr.own_methods {
185                                push_method(name, method, out, seen);
186                            }
187                            for (name, prop) in &tr.own_properties {
188                                push_property(name, prop, tr_fqcn.clone(), out, seen);
189                            }
190                        }
191                    }
192                } else if let Some(iface) = self.interfaces.get(ancestor_fqcn.as_ref()) {
193                    let iface_fqcn = iface.fqcn.clone();
194                    for (name, method) in &iface.own_methods {
195                        push_method(name, method, out, seen);
196                    }
197                    for (name, con) in &iface.own_constants {
198                        push_constant(name, con, iface_fqcn.clone(), out, seen);
199                    }
200                }
201                // Traits in all_parents are covered via their owning class's .traits above.
202            }
203
204            return;
205        }
206
207        // --- Interface ---
208        if let Some(iface) = self.interfaces.get(fqcn) {
209            let iface_fqcn = iface.fqcn.clone();
210            for (name, method) in &iface.own_methods {
211                push_method(name, method, out, seen);
212            }
213            for (name, con) in &iface.own_constants {
214                push_constant(name, con, iface_fqcn.clone(), out, seen);
215            }
216            let parents = iface.all_parents.clone();
217            drop(iface);
218            for parent_fqcn in &parents {
219                self.collect_members_for_fqcn(parent_fqcn, out, seen);
220            }
221            return;
222        }
223
224        // --- Enum ---
225        if let Some(en) = self.enums.get(fqcn) {
226            let en_fqcn = en.fqcn.clone();
227            for (name, case) in &en.cases {
228                if seen.insert((name.to_string(), MemberKind::EnumCase)) {
229                    out.push(MemberInfo {
230                        name: name.clone(),
231                        kind: MemberKind::EnumCase,
232                        ty: case.value.clone(),
233                        visibility: Visibility::Public,
234                        is_static: true,
235                        declaring_class: en_fqcn.clone(),
236                        deprecated: None,
237                        params: vec![],
238                    });
239                }
240            }
241            for (name, method) in &en.own_methods {
242                push_method(name, method, out, seen);
243            }
244            for (name, con) in &en.own_constants {
245                push_constant(name, con, en_fqcn.clone(), out, seen);
246            }
247            return;
248        }
249
250        // --- Trait (rare: variable typed as a trait) ---
251        if let Some(tr) = self.traits.get(fqcn) {
252            let tr_fqcn = tr.fqcn.clone();
253            for (name, method) in &tr.own_methods {
254                push_method(name, method, out, seen);
255            }
256            for (name, prop) in &tr.own_properties {
257                push_property(name, prop, tr_fqcn.clone(), out, seen);
258            }
259        }
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use crate::storage::*;
267    use indexmap::IndexMap;
268
269    fn make_method(name: &str, fqcn: &str) -> MethodStorage {
270        MethodStorage {
271            name: Arc::from(name),
272            fqcn: Arc::from(fqcn),
273            params: vec![],
274            return_type: Some(Union::single(Atomic::TString)),
275            inferred_return_type: None,
276            visibility: Visibility::Public,
277            is_static: false,
278            is_abstract: false,
279            is_final: false,
280            is_constructor: false,
281            template_params: vec![],
282            assertions: vec![],
283            throws: vec![],
284            deprecated: None,
285            is_internal: false,
286            is_pure: false,
287            location: None,
288        }
289    }
290
291    #[test]
292    fn visible_members_includes_inherited() {
293        let cb = Codebase::new();
294
295        // Parent class with a method
296        let mut parent_methods = IndexMap::new();
297        parent_methods.insert(
298            Arc::from("parentMethod"),
299            Arc::new(make_method("parentMethod", "Parent")),
300        );
301        cb.classes.insert(
302            Arc::from("Parent"),
303            ClassStorage {
304                fqcn: Arc::from("Parent"),
305                short_name: Arc::from("Parent"),
306                parent: None,
307                interfaces: vec![],
308                traits: vec![],
309                own_methods: parent_methods,
310                own_properties: IndexMap::new(),
311                own_constants: IndexMap::new(),
312                mixins: vec![],
313                template_params: vec![],
314                extends_type_args: vec![],
315                implements_type_args: vec![],
316                is_abstract: false,
317                is_final: false,
318                is_readonly: false,
319                all_parents: vec![],
320                deprecated: None,
321                is_internal: false,
322                location: None,
323                type_aliases: std::collections::HashMap::new(),
324                pending_import_types: vec![],
325            },
326        );
327
328        // Child class with its own method
329        let mut child_methods = IndexMap::new();
330        child_methods.insert(
331            Arc::from("childMethod"),
332            Arc::new(make_method("childMethod", "Child")),
333        );
334        cb.classes.insert(
335            Arc::from("Child"),
336            ClassStorage {
337                fqcn: Arc::from("Child"),
338                short_name: Arc::from("Child"),
339                parent: Some(Arc::from("Parent")),
340                interfaces: vec![],
341                traits: vec![],
342                own_methods: child_methods,
343                own_properties: IndexMap::new(),
344                own_constants: IndexMap::new(),
345                mixins: vec![],
346                template_params: vec![],
347                extends_type_args: vec![],
348                implements_type_args: vec![],
349                is_abstract: false,
350                is_final: false,
351                is_readonly: false,
352                all_parents: vec![],
353                deprecated: None,
354                is_internal: false,
355                location: None,
356                type_aliases: std::collections::HashMap::new(),
357                pending_import_types: vec![],
358            },
359        );
360
361        cb.finalize();
362
363        let ty = Union::single(Atomic::TNamedObject {
364            fqcn: Arc::from("Child"),
365            type_params: vec![],
366        });
367        let members = cb.visible_members(&ty);
368        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
369        assert!(names.contains(&"childMethod"), "should have own method");
370        assert!(
371            names.contains(&"parentMethod"),
372            "should have inherited method"
373        );
374    }
375
376    #[test]
377    fn visible_members_union_type() {
378        let cb = Codebase::new();
379
380        let mut a_methods = IndexMap::new();
381        a_methods.insert(Arc::from("aMethod"), Arc::new(make_method("aMethod", "A")));
382        cb.classes.insert(
383            Arc::from("A"),
384            ClassStorage {
385                fqcn: Arc::from("A"),
386                short_name: Arc::from("A"),
387                parent: None,
388                interfaces: vec![],
389                traits: vec![],
390                own_methods: a_methods,
391                own_properties: IndexMap::new(),
392                own_constants: IndexMap::new(),
393                mixins: vec![],
394                template_params: vec![],
395                extends_type_args: vec![],
396                implements_type_args: vec![],
397                is_abstract: false,
398                is_final: false,
399                is_readonly: false,
400                all_parents: vec![],
401                deprecated: None,
402                is_internal: false,
403                location: None,
404                type_aliases: std::collections::HashMap::new(),
405                pending_import_types: vec![],
406            },
407        );
408
409        let mut b_methods = IndexMap::new();
410        b_methods.insert(Arc::from("bMethod"), Arc::new(make_method("bMethod", "B")));
411        cb.classes.insert(
412            Arc::from("B"),
413            ClassStorage {
414                fqcn: Arc::from("B"),
415                short_name: Arc::from("B"),
416                parent: None,
417                interfaces: vec![],
418                traits: vec![],
419                own_methods: b_methods,
420                own_properties: IndexMap::new(),
421                own_constants: IndexMap::new(),
422                mixins: vec![],
423                template_params: vec![],
424                extends_type_args: vec![],
425                implements_type_args: vec![],
426                is_abstract: false,
427                is_final: false,
428                is_readonly: false,
429                all_parents: vec![],
430                deprecated: None,
431                is_internal: false,
432                location: None,
433                type_aliases: std::collections::HashMap::new(),
434                pending_import_types: vec![],
435            },
436        );
437
438        cb.finalize();
439
440        let ty = Union::merge(
441            &Union::single(Atomic::TNamedObject {
442                fqcn: Arc::from("A"),
443                type_params: vec![],
444            }),
445            &Union::single(Atomic::TNamedObject {
446                fqcn: Arc::from("B"),
447                type_params: vec![],
448            }),
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(&"aMethod"));
453        assert!(names.contains(&"bMethod"));
454    }
455}