1use std::sync::Arc;
7
8use mir_types::{Atomic, Union};
9
10use crate::codebase::Codebase;
11use crate::storage::Visibility;
12
13#[derive(Debug, Clone)]
15pub struct MemberInfo {
16 pub name: Arc<str>,
18 pub kind: MemberKind,
20 pub ty: Option<Union>,
22 pub visibility: Visibility,
24 pub is_static: bool,
26 pub declaring_class: Arc<str>,
28 pub is_deprecated: bool,
30 pub params: Vec<crate::storage::FnParam>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum MemberKind {
37 Method,
38 Property,
39 Constant,
40 EnumCase,
41}
42
43impl Codebase {
44 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 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 if let Some(cls) = self.classes.get(fqcn) {
70 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 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 let all_parents = cls.all_parents.clone();
105 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 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 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 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 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 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 self.collect_members_for_fqcn(parent_fqcn, out, seen);
261 }
262 return;
263 }
264
265 if let Some(en) = self.enums.get(fqcn) {
267 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 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 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 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 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 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}