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.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 let own_traits = cls.traits.clone();
89 let all_parents = cls.all_parents.clone();
90 let cls_fqcn = cls.fqcn.clone();
91
92 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 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 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 }
279
280 return;
281 }
282
283 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 self.collect_members_for_fqcn(parent_fqcn, out, seen);
320 }
321 return;
322 }
323
324 if let Some(en) = self.enums.get(fqcn) {
326 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 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 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 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 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 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}