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        // --- Class ---
136        if let Some(cls) = self.classes.get(fqcn) {
137            let cls_fqcn = cls.fqcn.clone();
138
139            for (name, method) in &cls.own_methods {
140                push_method(name, method, out, seen);
141            }
142            for (name, prop) in &cls.own_properties {
143                push_property(name, prop, cls_fqcn.clone(), out, seen);
144            }
145            for (name, con) in &cls.own_constants {
146                push_constant(name, con, cls_fqcn.clone(), out, seen);
147            }
148
149            // Collect chain before dropping the DashMap guard to avoid deadlock.
150            let own_traits = cls.traits.clone();
151            let all_parents = cls.all_parents.clone();
152            drop(cls);
153
154            for tr_fqcn in &own_traits {
155                if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
156                    let tr_fqcn = tr.fqcn.clone();
157                    for (name, method) in &tr.own_methods {
158                        push_method(name, method, out, seen);
159                    }
160                    for (name, prop) in &tr.own_properties {
161                        push_property(name, prop, tr_fqcn.clone(), out, seen);
162                    }
163                }
164            }
165
166            for ancestor_fqcn in &all_parents {
167                if let Some(ancestor) = self.classes.get(ancestor_fqcn.as_ref()) {
168                    let anc_fqcn = ancestor.fqcn.clone();
169                    for (name, method) in &ancestor.own_methods {
170                        push_method(name, method, out, seen);
171                    }
172                    for (name, prop) in &ancestor.own_properties {
173                        push_property(name, prop, anc_fqcn.clone(), out, seen);
174                    }
175                    for (name, con) in &ancestor.own_constants {
176                        push_constant(name, con, anc_fqcn.clone(), out, seen);
177                    }
178                    let anc_traits = ancestor.traits.clone();
179                    drop(ancestor);
180                    for tr_fqcn in &anc_traits {
181                        if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
182                            let tr_fqcn = tr.fqcn.clone();
183                            for (name, method) in &tr.own_methods {
184                                push_method(name, method, out, seen);
185                            }
186                            for (name, prop) in &tr.own_properties {
187                                push_property(name, prop, tr_fqcn.clone(), out, seen);
188                            }
189                        }
190                    }
191                } else if let Some(iface) = self.interfaces.get(ancestor_fqcn.as_ref()) {
192                    let iface_fqcn = iface.fqcn.clone();
193                    for (name, method) in &iface.own_methods {
194                        push_method(name, method, out, seen);
195                    }
196                    for (name, con) in &iface.own_constants {
197                        push_constant(name, con, iface_fqcn.clone(), out, seen);
198                    }
199                }
200                // Traits in all_parents are covered via their owning class's .traits above.
201            }
202
203            return;
204        }
205
206        // --- Interface ---
207        if let Some(iface) = self.interfaces.get(fqcn) {
208            let iface_fqcn = iface.fqcn.clone();
209            for (name, method) in &iface.own_methods {
210                push_method(name, method, out, seen);
211            }
212            for (name, con) in &iface.own_constants {
213                push_constant(name, con, iface_fqcn.clone(), out, seen);
214            }
215            let parents = iface.all_parents.clone();
216            drop(iface);
217            for parent_fqcn in &parents {
218                self.collect_members_for_fqcn(parent_fqcn, out, seen);
219            }
220            return;
221        }
222
223        // --- Enum ---
224        if let Some(en) = self.enums.get(fqcn) {
225            let en_fqcn = en.fqcn.clone();
226            for (name, case) in &en.cases {
227                if seen.insert((name.to_string(), MemberKind::EnumCase)) {
228                    out.push(MemberInfo {
229                        name: name.clone(),
230                        kind: MemberKind::EnumCase,
231                        ty: case.value.clone(),
232                        visibility: Visibility::Public,
233                        is_static: true,
234                        declaring_class: en_fqcn.clone(),
235                        deprecated: None,
236                        params: vec![],
237                    });
238                }
239            }
240            for (name, method) in &en.own_methods {
241                push_method(name, method, out, seen);
242            }
243            for (name, con) in &en.own_constants {
244                push_constant(name, con, en_fqcn.clone(), out, seen);
245            }
246            return;
247        }
248
249        // --- Trait (rare: variable typed as a trait) ---
250        if let Some(tr) = self.traits.get(fqcn) {
251            let tr_fqcn = tr.fqcn.clone();
252            for (name, method) in &tr.own_methods {
253                push_method(name, method, out, seen);
254            }
255            for (name, prop) in &tr.own_properties {
256                push_property(name, prop, tr_fqcn.clone(), out, seen);
257            }
258        }
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use crate::storage::*;
266    use indexmap::IndexMap;
267
268    fn make_method(name: &str, fqcn: &str) -> MethodStorage {
269        MethodStorage {
270            name: Arc::from(name),
271            fqcn: Arc::from(fqcn),
272            params: vec![],
273            return_type: Some(Union::single(Atomic::TString)),
274            inferred_return_type: None,
275            visibility: Visibility::Public,
276            is_static: false,
277            is_abstract: false,
278            is_final: false,
279            is_constructor: false,
280            template_params: vec![],
281            assertions: vec![],
282            throws: vec![],
283            deprecated: None,
284            is_internal: false,
285            is_pure: false,
286            location: None,
287        }
288    }
289
290    #[test]
291    fn visible_members_includes_inherited() {
292        let cb = Codebase::new();
293
294        // Parent class with a method
295        let mut parent_methods = IndexMap::new();
296        parent_methods.insert(
297            Arc::from("parentMethod"),
298            Arc::new(make_method("parentMethod", "Parent")),
299        );
300        cb.classes.insert(
301            Arc::from("Parent"),
302            ClassStorage {
303                fqcn: Arc::from("Parent"),
304                short_name: Arc::from("Parent"),
305                parent: None,
306                interfaces: vec![],
307                traits: vec![],
308                own_methods: parent_methods,
309                own_properties: IndexMap::new(),
310                own_constants: IndexMap::new(),
311                template_params: vec![],
312                extends_type_args: vec![],
313                is_abstract: false,
314                is_final: false,
315                is_readonly: false,
316                all_parents: vec![],
317                deprecated: None,
318                is_internal: false,
319                location: None,
320            },
321        );
322
323        // Child class with its own method
324        let mut child_methods = IndexMap::new();
325        child_methods.insert(
326            Arc::from("childMethod"),
327            Arc::new(make_method("childMethod", "Child")),
328        );
329        cb.classes.insert(
330            Arc::from("Child"),
331            ClassStorage {
332                fqcn: Arc::from("Child"),
333                short_name: Arc::from("Child"),
334                parent: Some(Arc::from("Parent")),
335                interfaces: vec![],
336                traits: vec![],
337                own_methods: child_methods,
338                own_properties: IndexMap::new(),
339                own_constants: IndexMap::new(),
340                template_params: vec![],
341                extends_type_args: vec![],
342                is_abstract: false,
343                is_final: false,
344                is_readonly: false,
345                all_parents: vec![],
346                deprecated: None,
347                is_internal: false,
348                location: None,
349            },
350        );
351
352        cb.finalize();
353
354        let ty = Union::single(Atomic::TNamedObject {
355            fqcn: Arc::from("Child"),
356            type_params: vec![],
357        });
358        let members = cb.visible_members(&ty);
359        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
360        assert!(names.contains(&"childMethod"), "should have own method");
361        assert!(
362            names.contains(&"parentMethod"),
363            "should have inherited method"
364        );
365    }
366
367    #[test]
368    fn visible_members_union_type() {
369        let cb = Codebase::new();
370
371        let mut a_methods = IndexMap::new();
372        a_methods.insert(Arc::from("aMethod"), Arc::new(make_method("aMethod", "A")));
373        cb.classes.insert(
374            Arc::from("A"),
375            ClassStorage {
376                fqcn: Arc::from("A"),
377                short_name: Arc::from("A"),
378                parent: None,
379                interfaces: vec![],
380                traits: vec![],
381                own_methods: a_methods,
382                own_properties: IndexMap::new(),
383                own_constants: IndexMap::new(),
384                template_params: vec![],
385                extends_type_args: vec![],
386                is_abstract: false,
387                is_final: false,
388                is_readonly: false,
389                all_parents: vec![],
390                deprecated: None,
391                is_internal: false,
392                location: None,
393            },
394        );
395
396        let mut b_methods = IndexMap::new();
397        b_methods.insert(Arc::from("bMethod"), Arc::new(make_method("bMethod", "B")));
398        cb.classes.insert(
399            Arc::from("B"),
400            ClassStorage {
401                fqcn: Arc::from("B"),
402                short_name: Arc::from("B"),
403                parent: None,
404                interfaces: vec![],
405                traits: vec![],
406                own_methods: b_methods,
407                own_properties: IndexMap::new(),
408                own_constants: IndexMap::new(),
409                template_params: vec![],
410                extends_type_args: vec![],
411                is_abstract: false,
412                is_final: false,
413                is_readonly: false,
414                all_parents: vec![],
415                deprecated: None,
416                is_internal: false,
417                location: None,
418            },
419        );
420
421        cb.finalize();
422
423        let ty = Union::merge(
424            &Union::single(Atomic::TNamedObject {
425                fqcn: Arc::from("A"),
426                type_params: vec![],
427            }),
428            &Union::single(Atomic::TNamedObject {
429                fqcn: Arc::from("B"),
430                type_params: vec![],
431            }),
432        );
433        let members = cb.visible_members(&ty);
434        let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
435        assert!(names.contains(&"aMethod"));
436        assert!(names.contains(&"bMethod"));
437    }
438}