use std::sync::Arc;
use mir_types::{Atomic, Union};
use crate::codebase::Codebase;
use crate::storage::{ConstantStorage, MethodStorage, PropertyStorage, Visibility};
#[derive(Debug, Clone)]
pub struct MemberInfo {
pub name: Arc<str>,
pub kind: MemberKind,
pub ty: Option<Union>,
pub visibility: Visibility,
pub is_static: bool,
pub declaring_class: Arc<str>,
pub deprecated: Option<Arc<str>>,
pub params: Vec<crate::storage::FnParam>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MemberKind {
Method,
Property,
Constant,
EnumCase,
}
type Seen = std::collections::HashSet<(String, MemberKind)>;
fn push_method(
name: &Arc<str>,
method: &MethodStorage,
out: &mut Vec<MemberInfo>,
seen: &mut Seen,
) {
if seen.insert((name.to_string(), MemberKind::Method)) {
out.push(MemberInfo {
name: name.clone(),
kind: MemberKind::Method,
ty: method.effective_return_type().cloned(),
visibility: method.visibility,
is_static: method.is_static,
declaring_class: method.fqcn.clone(),
deprecated: method.deprecated.clone(),
params: method.params.clone(),
});
}
}
fn push_property(
name: &Arc<str>,
prop: &PropertyStorage,
declaring_class: Arc<str>,
out: &mut Vec<MemberInfo>,
seen: &mut Seen,
) {
if seen.insert((name.to_string(), MemberKind::Property)) {
out.push(MemberInfo {
name: name.clone(),
kind: MemberKind::Property,
ty: prop.ty.clone().or_else(|| prop.inferred_ty.clone()),
visibility: prop.visibility,
is_static: prop.is_static,
declaring_class,
deprecated: None,
params: vec![],
});
}
}
fn push_constant(
name: &Arc<str>,
con: &ConstantStorage,
declaring_class: Arc<str>,
out: &mut Vec<MemberInfo>,
seen: &mut Seen,
) {
if seen.insert((name.to_string(), MemberKind::Constant)) {
out.push(MemberInfo {
name: name.clone(),
kind: MemberKind::Constant,
ty: Some(con.ty.clone()),
visibility: con.visibility.unwrap_or(Visibility::Public),
is_static: true,
declaring_class,
deprecated: None,
params: vec![],
});
}
}
impl Codebase {
pub fn visible_members(&self, ty: &Union) -> Vec<MemberInfo> {
let mut result = Vec::new();
let mut seen = std::collections::HashSet::new();
for atomic in &ty.types {
if let Atomic::TNamedObject { fqcn, .. } = atomic {
self.collect_members_for_fqcn(fqcn, &mut result, &mut seen);
}
}
result
}
fn collect_members_for_fqcn(&self, fqcn: &str, out: &mut Vec<MemberInfo>, seen: &mut Seen) {
self.ensure_finalized(fqcn);
if let Some(cls) = self.classes.get(fqcn) {
let cls_fqcn = cls.fqcn.clone();
for (name, method) in &cls.own_methods {
push_method(name, method, out, seen);
}
for (name, prop) in &cls.own_properties {
push_property(name, prop, cls_fqcn.clone(), out, seen);
}
for (name, con) in &cls.own_constants {
push_constant(name, con, cls_fqcn.clone(), out, seen);
}
let own_traits = cls.traits.clone();
let all_parents = cls.all_parents.clone();
drop(cls);
for tr_fqcn in &own_traits {
if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
let tr_fqcn = tr.fqcn.clone();
for (name, method) in &tr.own_methods {
push_method(name, method, out, seen);
}
for (name, prop) in &tr.own_properties {
push_property(name, prop, tr_fqcn.clone(), out, seen);
}
}
}
for ancestor_fqcn in &all_parents {
if let Some(ancestor) = self.classes.get(ancestor_fqcn.as_ref()) {
let anc_fqcn = ancestor.fqcn.clone();
for (name, method) in &ancestor.own_methods {
push_method(name, method, out, seen);
}
for (name, prop) in &ancestor.own_properties {
push_property(name, prop, anc_fqcn.clone(), out, seen);
}
for (name, con) in &ancestor.own_constants {
push_constant(name, con, anc_fqcn.clone(), out, seen);
}
let anc_traits = ancestor.traits.clone();
drop(ancestor);
for tr_fqcn in &anc_traits {
if let Some(tr) = self.traits.get(tr_fqcn.as_ref()) {
let tr_fqcn = tr.fqcn.clone();
for (name, method) in &tr.own_methods {
push_method(name, method, out, seen);
}
for (name, prop) in &tr.own_properties {
push_property(name, prop, tr_fqcn.clone(), out, seen);
}
}
}
} else if let Some(iface) = self.interfaces.get(ancestor_fqcn.as_ref()) {
let iface_fqcn = iface.fqcn.clone();
for (name, method) in &iface.own_methods {
push_method(name, method, out, seen);
}
for (name, con) in &iface.own_constants {
push_constant(name, con, iface_fqcn.clone(), out, seen);
}
}
}
return;
}
if let Some(iface) = self.interfaces.get(fqcn) {
let iface_fqcn = iface.fqcn.clone();
for (name, method) in &iface.own_methods {
push_method(name, method, out, seen);
}
for (name, con) in &iface.own_constants {
push_constant(name, con, iface_fqcn.clone(), out, seen);
}
let parents = iface.all_parents.clone();
drop(iface);
for parent_fqcn in &parents {
self.collect_members_for_fqcn(parent_fqcn, out, seen);
}
return;
}
if let Some(en) = self.enums.get(fqcn) {
let en_fqcn = en.fqcn.clone();
for (name, case) in &en.cases {
if seen.insert((name.to_string(), MemberKind::EnumCase)) {
out.push(MemberInfo {
name: name.clone(),
kind: MemberKind::EnumCase,
ty: case.value.clone(),
visibility: Visibility::Public,
is_static: true,
declaring_class: en_fqcn.clone(),
deprecated: None,
params: vec![],
});
}
}
for (name, method) in &en.own_methods {
push_method(name, method, out, seen);
}
for (name, con) in &en.own_constants {
push_constant(name, con, en_fqcn.clone(), out, seen);
}
return;
}
if let Some(tr) = self.traits.get(fqcn) {
let tr_fqcn = tr.fqcn.clone();
for (name, method) in &tr.own_methods {
push_method(name, method, out, seen);
}
for (name, prop) in &tr.own_properties {
push_property(name, prop, tr_fqcn.clone(), out, seen);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::*;
use indexmap::IndexMap;
fn make_method(name: &str, fqcn: &str) -> MethodStorage {
MethodStorage {
name: Arc::from(name),
fqcn: Arc::from(fqcn),
params: vec![],
return_type: Some(Union::single(Atomic::TString)),
inferred_return_type: None,
visibility: Visibility::Public,
is_static: false,
is_abstract: false,
is_final: false,
is_constructor: false,
template_params: vec![],
assertions: vec![],
throws: vec![],
deprecated: None,
is_internal: false,
is_pure: false,
location: None,
}
}
#[test]
fn visible_members_includes_inherited() {
let cb = Codebase::new();
let mut parent_methods = IndexMap::new();
parent_methods.insert(
Arc::from("parentMethod"),
Arc::new(make_method("parentMethod", "Parent")),
);
cb.classes.insert(
Arc::from("Parent"),
ClassStorage {
fqcn: Arc::from("Parent"),
short_name: Arc::from("Parent"),
parent: None,
interfaces: vec![],
traits: vec![],
own_methods: parent_methods,
own_properties: IndexMap::new(),
own_constants: IndexMap::new(),
mixins: vec![],
template_params: vec![],
extends_type_args: vec![],
implements_type_args: vec![],
is_abstract: false,
is_final: false,
is_readonly: false,
all_parents: vec![],
deprecated: None,
is_internal: false,
location: None,
type_aliases: std::collections::HashMap::new(),
pending_import_types: vec![],
},
);
let mut child_methods = IndexMap::new();
child_methods.insert(
Arc::from("childMethod"),
Arc::new(make_method("childMethod", "Child")),
);
cb.classes.insert(
Arc::from("Child"),
ClassStorage {
fqcn: Arc::from("Child"),
short_name: Arc::from("Child"),
parent: Some(Arc::from("Parent")),
interfaces: vec![],
traits: vec![],
own_methods: child_methods,
own_properties: IndexMap::new(),
own_constants: IndexMap::new(),
mixins: vec![],
template_params: vec![],
extends_type_args: vec![],
implements_type_args: vec![],
is_abstract: false,
is_final: false,
is_readonly: false,
all_parents: vec![],
deprecated: None,
is_internal: false,
location: None,
type_aliases: std::collections::HashMap::new(),
pending_import_types: vec![],
},
);
cb.finalize();
let ty = Union::single(Atomic::TNamedObject {
fqcn: Arc::from("Child"),
type_params: vec![],
});
let members = cb.visible_members(&ty);
let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
assert!(names.contains(&"childMethod"), "should have own method");
assert!(
names.contains(&"parentMethod"),
"should have inherited method"
);
}
#[test]
fn visible_members_union_type() {
let cb = Codebase::new();
let mut a_methods = IndexMap::new();
a_methods.insert(Arc::from("aMethod"), Arc::new(make_method("aMethod", "A")));
cb.classes.insert(
Arc::from("A"),
ClassStorage {
fqcn: Arc::from("A"),
short_name: Arc::from("A"),
parent: None,
interfaces: vec![],
traits: vec![],
own_methods: a_methods,
own_properties: IndexMap::new(),
own_constants: IndexMap::new(),
mixins: vec![],
template_params: vec![],
extends_type_args: vec![],
implements_type_args: vec![],
is_abstract: false,
is_final: false,
is_readonly: false,
all_parents: vec![],
deprecated: None,
is_internal: false,
location: None,
type_aliases: std::collections::HashMap::new(),
pending_import_types: vec![],
},
);
let mut b_methods = IndexMap::new();
b_methods.insert(Arc::from("bMethod"), Arc::new(make_method("bMethod", "B")));
cb.classes.insert(
Arc::from("B"),
ClassStorage {
fqcn: Arc::from("B"),
short_name: Arc::from("B"),
parent: None,
interfaces: vec![],
traits: vec![],
own_methods: b_methods,
own_properties: IndexMap::new(),
own_constants: IndexMap::new(),
mixins: vec![],
template_params: vec![],
extends_type_args: vec![],
implements_type_args: vec![],
is_abstract: false,
is_final: false,
is_readonly: false,
all_parents: vec![],
deprecated: None,
is_internal: false,
location: None,
type_aliases: std::collections::HashMap::new(),
pending_import_types: vec![],
},
);
cb.finalize();
let ty = Union::merge(
&Union::single(Atomic::TNamedObject {
fqcn: Arc::from("A"),
type_params: vec![],
}),
&Union::single(Atomic::TNamedObject {
fqcn: Arc::from("B"),
type_params: vec![],
}),
);
let members = cb.visible_members(&ty);
let names: Vec<&str> = members.iter().map(|m| m.name.as_ref()).collect();
assert!(names.contains(&"aMethod"));
assert!(names.contains(&"bMethod"));
}
}