use std::sync::Arc;
use mir_codebase::storage::{
ClassDef, ConstantDef, EnumDef, FnParam, FunctionDef, InterfaceDef, MethodDef, PropertyDef,
TraitDef,
};
use mir_types::Name;
use crate::db::{collect_file_definitions, source_file_for_fqcn, Fqcn, MirDatabase, SourceFile};
#[derive(Debug, Clone, PartialEq)]
pub enum ClassLike {
Class(Arc<ClassDef>),
Interface(Arc<InterfaceDef>),
Trait(Arc<TraitDef>),
Enum(Arc<EnumDef>),
}
impl ClassLike {
pub fn fqcn(&self) -> &Arc<str> {
match self {
ClassLike::Class(c) => &c.fqcn,
ClassLike::Interface(i) => &i.fqcn,
ClassLike::Trait(t) => &t.fqcn,
ClassLike::Enum(e) => &e.fqcn,
}
}
pub fn short_name(&self) -> &Arc<str> {
match self {
ClassLike::Class(c) => &c.short_name,
ClassLike::Interface(i) => &i.short_name,
ClassLike::Trait(t) => &t.short_name,
ClassLike::Enum(e) => &e.short_name,
}
}
pub fn ancestor_fqcns(&self) -> Vec<Arc<str>> {
match self {
ClassLike::Class(c) => {
let mut out = Vec::new();
if let Some(p) = &c.parent {
out.push(p.clone());
}
out.extend(c.interfaces.iter().cloned());
out.extend(c.traits.iter().cloned());
out
}
ClassLike::Interface(i) => i.extends.clone(),
ClassLike::Trait(t) => t.traits.clone(),
ClassLike::Enum(e) => e.interfaces.clone(),
}
}
pub fn own_methods(&self) -> &indexmap::IndexMap<Arc<str>, Arc<MethodDef>> {
match self {
ClassLike::Class(c) => &c.own_methods,
ClassLike::Interface(i) => &i.own_methods,
ClassLike::Trait(t) => &t.own_methods,
ClassLike::Enum(e) => &e.own_methods,
}
}
pub fn own_properties(&self) -> Option<&indexmap::IndexMap<Arc<str>, PropertyDef>> {
match self {
ClassLike::Class(c) => Some(&c.own_properties),
ClassLike::Trait(t) => Some(&t.own_properties),
ClassLike::Interface(_) | ClassLike::Enum(_) => None,
}
}
pub fn own_constants(&self) -> &indexmap::IndexMap<Arc<str>, ConstantDef> {
match self {
ClassLike::Class(c) => &c.own_constants,
ClassLike::Interface(i) => &i.own_constants,
ClassLike::Trait(t) => &t.own_constants,
ClassLike::Enum(e) => &e.own_constants,
}
}
pub fn is_abstract(&self) -> bool {
match self {
ClassLike::Class(c) => c.is_abstract,
ClassLike::Interface(_) => true, ClassLike::Trait(_) | ClassLike::Enum(_) => false,
}
}
pub fn is_final(&self) -> bool {
match self {
ClassLike::Class(c) => c.is_final,
ClassLike::Enum(_) => true, ClassLike::Interface(_) | ClassLike::Trait(_) => false,
}
}
pub fn is_interface(&self) -> bool {
matches!(self, ClassLike::Interface(_))
}
pub fn is_trait(&self) -> bool {
matches!(self, ClassLike::Trait(_))
}
pub fn is_enum(&self) -> bool {
matches!(self, ClassLike::Enum(_))
}
pub fn is_class(&self) -> bool {
matches!(self, ClassLike::Class(_))
}
pub fn class_traits(&self) -> &[Arc<str>] {
match self {
ClassLike::Class(c) => &c.traits,
ClassLike::Trait(t) => &t.traits,
_ => &[],
}
}
pub fn mixins(&self) -> &[Arc<str>] {
match self {
ClassLike::Class(c) => &c.mixins,
_ => &[],
}
}
pub fn deprecated(&self) -> Option<&Arc<str>> {
match self {
ClassLike::Class(c) => c.deprecated.as_ref(),
_ => None,
}
}
pub fn template_params(&self) -> &[mir_codebase::storage::TemplateParam] {
match self {
ClassLike::Class(c) => &c.template_params,
ClassLike::Interface(i) => &i.template_params,
ClassLike::Trait(t) => &t.template_params,
ClassLike::Enum(_) => &[],
}
}
pub fn location(&self) -> Option<&mir_types::Location> {
match self {
ClassLike::Class(c) => c.location.as_ref(),
ClassLike::Interface(i) => i.location.as_ref(),
ClassLike::Trait(t) => t.location.as_ref(),
ClassLike::Enum(e) => e.location.as_ref(),
}
}
pub fn interfaces(&self) -> &[Arc<str>] {
match self {
ClassLike::Class(c) => &c.interfaces,
ClassLike::Enum(e) => &e.interfaces,
_ => &[],
}
}
pub fn parent(&self) -> Option<&Arc<str>> {
match self {
ClassLike::Class(c) => c.parent.as_ref(),
_ => None,
}
}
pub fn enum_scalar_type(&self) -> Option<&mir_types::Type> {
match self {
ClassLike::Enum(e) => e.scalar_type.as_ref(),
_ => None,
}
}
pub fn extends(&self) -> &[Arc<str>] {
match self {
ClassLike::Interface(i) => &i.extends,
_ => &[],
}
}
pub fn extends_type_args(&self) -> &[mir_types::Type] {
match self {
ClassLike::Class(c) => &c.extends_type_args,
_ => &[],
}
}
pub fn implements_type_args(&self) -> &[(Arc<str>, Vec<mir_types::Type>)] {
match self {
ClassLike::Class(c) => &c.implements_type_args,
_ => &[],
}
}
pub fn trait_use_locations(&self) -> &[(Arc<str>, mir_types::Location)] {
match self {
ClassLike::Class(c) => &c.trait_use_locations,
_ => &[],
}
}
pub fn is_readonly(&self) -> bool {
match self {
ClassLike::Class(c) => c.is_readonly,
_ => false,
}
}
pub fn is_internal(&self) -> bool {
match self {
ClassLike::Class(c) => c.is_internal,
_ => false,
}
}
pub fn is_backed_enum(&self) -> bool {
match self {
ClassLike::Enum(e) => e.scalar_type.is_some(),
_ => false,
}
}
}
#[salsa::tracked]
pub fn class_in_file<'db>(
db: &'db dyn MirDatabase,
file: SourceFile,
fqcn: Fqcn<'db>,
) -> Option<Arc<ClassDef>> {
let defs = collect_file_definitions(db, file);
let target = fqcn.name(db);
defs.slice
.classes
.iter()
.find(|c| c.fqcn.eq_ignore_ascii_case(target.as_ref()))
.cloned()
}
#[salsa::tracked]
pub fn interface_in_file<'db>(
db: &'db dyn MirDatabase,
file: SourceFile,
fqcn: Fqcn<'db>,
) -> Option<Arc<InterfaceDef>> {
let defs = collect_file_definitions(db, file);
let target = fqcn.name(db);
defs.slice
.interfaces
.iter()
.find(|i| i.fqcn.eq_ignore_ascii_case(target.as_ref()))
.cloned()
}
#[salsa::tracked]
pub fn trait_in_file<'db>(
db: &'db dyn MirDatabase,
file: SourceFile,
fqcn: Fqcn<'db>,
) -> Option<Arc<TraitDef>> {
let defs = collect_file_definitions(db, file);
let target = fqcn.name(db);
defs.slice
.traits
.iter()
.find(|t| t.fqcn.eq_ignore_ascii_case(target.as_ref()))
.cloned()
}
#[salsa::tracked]
pub fn enum_in_file<'db>(
db: &'db dyn MirDatabase,
file: SourceFile,
fqcn: Fqcn<'db>,
) -> Option<Arc<EnumDef>> {
let defs = collect_file_definitions(db, file);
let target = fqcn.name(db);
defs.slice
.enums
.iter()
.find(|e| e.fqcn.eq_ignore_ascii_case(target.as_ref()))
.cloned()
}
#[salsa::tracked]
pub fn function_in_file<'db>(
db: &'db dyn MirDatabase,
file: SourceFile,
fqn: Fqcn<'db>,
) -> Option<Arc<FunctionDef>> {
let defs = collect_file_definitions(db, file);
let target = fqn.name(db);
defs.slice
.functions
.iter()
.find(|f| f.fqn.eq_ignore_ascii_case(target.as_ref()))
.cloned()
}
#[salsa::tracked]
pub fn global_constant_in_file<'db>(
db: &'db dyn MirDatabase,
file: SourceFile,
fqn: Fqcn<'db>,
) -> Option<Arc<mir_types::Type>> {
let defs = collect_file_definitions(db, file);
let target = fqn.name(db);
defs.slice
.constants
.iter()
.find(|(name, _)| name.as_ref() == target.as_ref())
.map(|(_, ty)| Arc::new(ty.clone()))
}
#[salsa::tracked]
pub fn class_def_at(db: &dyn MirDatabase, file: SourceFile, idx: u32) -> Option<Arc<ClassDef>> {
let defs = collect_file_definitions(db, file);
defs.slice.classes.get(idx as usize).cloned()
}
#[salsa::tracked]
pub fn interface_def_at(
db: &dyn MirDatabase,
file: SourceFile,
idx: u32,
) -> Option<Arc<InterfaceDef>> {
let defs = collect_file_definitions(db, file);
defs.slice.interfaces.get(idx as usize).cloned()
}
#[salsa::tracked]
pub fn trait_def_at(db: &dyn MirDatabase, file: SourceFile, idx: u32) -> Option<Arc<TraitDef>> {
let defs = collect_file_definitions(db, file);
defs.slice.traits.get(idx as usize).cloned()
}
#[salsa::tracked]
pub fn enum_def_at(db: &dyn MirDatabase, file: SourceFile, idx: u32) -> Option<Arc<EnumDef>> {
let defs = collect_file_definitions(db, file);
defs.slice.enums.get(idx as usize).cloned()
}
#[salsa::tracked]
pub fn function_def_at(
db: &dyn MirDatabase,
file: SourceFile,
idx: u32,
) -> Option<Arc<FunctionDef>> {
let defs = collect_file_definitions(db, file);
defs.slice.functions.get(idx as usize).cloned()
}
pub fn find_class_like<'db>(db: &'db dyn MirDatabase, fqcn: Fqcn<'db>) -> Option<ClassLike> {
use crate::db::SymbolLoc;
let key = fqcn.name(db).ascii_lowercase();
let index = crate::db::workspace_index(db);
let loc = index.class_like.get(&key).copied()?;
match loc {
SymbolLoc::Class { file, idx } => class_def_at(db, file, idx as u32).map(ClassLike::Class),
SymbolLoc::Interface { file, idx } => {
interface_def_at(db, file, idx as u32).map(ClassLike::Interface)
}
SymbolLoc::Trait { file, idx } => trait_def_at(db, file, idx as u32).map(ClassLike::Trait),
SymbolLoc::Enum { file, idx } => enum_def_at(db, file, idx as u32).map(ClassLike::Enum),
SymbolLoc::Function { .. } | SymbolLoc::Constant { .. } => None,
}
}
pub fn find_function<'db>(db: &'db dyn MirDatabase, fqn: Fqcn<'db>) -> Option<Arc<FunctionDef>> {
use crate::db::SymbolLoc;
let key = fqn.name(db).ascii_lowercase();
let index = crate::db::workspace_index(db);
let SymbolLoc::Function { file, idx } = index.functions.get(&key).copied()? else {
return None;
};
function_def_at(db, file, idx as u32)
}
pub fn find_global_constant<'db>(
db: &'db dyn MirDatabase,
fqn: Fqcn<'db>,
) -> Option<Arc<mir_types::Type>> {
use crate::db::SymbolLoc;
let key = fqn.name(db);
let index = crate::db::workspace_index(db);
if let Some(SymbolLoc::Constant { file, idx }) = index.constants.get(&key).copied() {
let defs = collect_file_definitions(db, file);
if let Some((_, ty)) = defs.slice.constants.get(idx) {
return Some(Arc::new(ty.clone()));
}
}
let file = source_file_for_fqcn(db, fqn)?;
global_constant_in_file(db, file, fqn)
}
pub fn find_method_in_class<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
) -> Option<Arc<MethodDef>> {
let class = find_class_like(db, fqcn)?;
if let Some(m) = class.own_methods().iter().find_map(|(k, v)| {
if k.as_ref().eq_ignore_ascii_case(name) {
Some(v.clone())
} else {
None
}
}) {
return Some(m);
}
if let ClassLike::Enum(e) = &class {
let lower = name.to_ascii_lowercase();
let is_backed = e.scalar_type.is_some();
let synth = |method_name: &str, params: Arc<[FnParam]>| {
Arc::new(mir_codebase::storage::MethodDef {
fqcn: e.fqcn.clone(),
name: Arc::from(method_name),
params,
return_type: Some(Arc::new(mir_types::Type::mixed())),
inferred_return_type: None,
visibility: mir_codebase::storage::Visibility::Public,
is_static: true,
is_abstract: false,
is_constructor: false,
template_params: vec![],
assertions: vec![],
throws: vec![],
is_final: false,
is_virtual: false,
is_internal: false,
is_pure: false,
deprecated: None,
location: None,
docstring: None,
})
};
if lower == "cases" {
return Some(synth("cases", Arc::from([].as_ref())));
}
if is_backed && (lower == "from" || lower == "tryfrom") {
let value_param = FnParam {
name: Name::from("value"),
ty: e.scalar_type.as_ref().map(|t| Arc::new(t.clone())),
has_default: false,
is_variadic: false,
is_byref: false,
is_optional: false,
};
return Some(synth(name, Arc::from(vec![value_param])));
}
}
None
}
pub fn find_property_in_class<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
) -> Option<PropertyDef> {
let class = find_class_like(db, fqcn)?;
class.own_properties()?.get(name).cloned()
}
pub fn find_class_constant_in_class<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
) -> Option<ConstantDef> {
let class = find_class_like(db, fqcn)?;
if let Some(c) = class.own_constants().get(name) {
return Some(c.clone());
}
if let ClassLike::Enum(e) = &class {
if let Some(case) = e.cases.get(name) {
return Some(mir_codebase::storage::ConstantDef {
name: case.name.clone(),
ty: mir_types::Type::mixed(),
visibility: None,
is_final: false,
location: case.location.clone(),
});
}
}
None
}
#[salsa::tracked]
pub fn class_ancestors_by_fqcn<'db>(db: &'db dyn MirDatabase, fqcn: Fqcn<'db>) -> Arc<[Arc<str>]> {
let mut visited = std::collections::HashSet::<Arc<str>>::new();
let mut order = Vec::<Arc<str>>::new();
let mut queue = std::collections::VecDeque::<Arc<str>>::new();
let initial: Arc<str> = fqcn.name(db).into();
queue.push_back(initial.clone());
visited.insert(initial);
while let Some(name) = queue.pop_front() {
order.push(name.clone());
let here = Fqcn::new(db, Name::new(name.as_ref()));
if let Some(class) = find_class_like(db, here) {
for parent in class.ancestor_fqcns() {
if visited.insert(parent.clone()) {
queue.push_back(parent);
}
}
}
}
order.into()
}
pub fn has_method_in_chain(db: &dyn MirDatabase, fqcn: &str, name: &str) -> bool {
let here = Fqcn::new(db, Name::new(fqcn));
find_method_in_chain(db, here, name).is_some()
}
pub fn find_method_in_chain<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
) -> Option<(Arc<str>, Arc<MethodDef>)> {
for ancestor in class_ancestors_by_fqcn(db, fqcn).iter() {
let here = Fqcn::new(db, Name::new(ancestor.as_ref()));
if let Some(m) = find_method_in_class(db, here, name) {
return Some((ancestor.clone(), m));
}
}
let mut visited_mixins = std::collections::HashSet::<Arc<str>>::new();
find_method_in_mixins(db, fqcn, name, &mut visited_mixins)
}
fn find_method_in_mixins<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
visited: &mut std::collections::HashSet<Arc<str>>,
) -> Option<(Arc<str>, Arc<MethodDef>)> {
let class = find_class_like(db, fqcn)?;
for m in class.mixins() {
let mixin_fqcn: Arc<str> = if let Some(pos) = m.find('<') {
Arc::from(&m[..pos])
} else {
m.clone()
};
if !visited.insert(mixin_fqcn.clone()) {
continue;
}
let mixin_here = Fqcn::new(db, Name::new(mixin_fqcn.as_ref()));
for ancestor in class_ancestors_by_fqcn(db, mixin_here).iter() {
let here = Fqcn::new(db, Name::new(ancestor.as_ref()));
if let Some(m) = find_method_in_class(db, here, name) {
return Some((ancestor.clone(), m));
}
}
if let Some(result) = find_method_in_mixins(db, mixin_here, name, visited) {
return Some(result);
}
}
None
}
pub fn find_property_in_chain<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
) -> Option<(Arc<str>, PropertyDef)> {
for ancestor in class_ancestors_by_fqcn(db, fqcn).iter() {
let here = Fqcn::new(db, Name::new(ancestor.as_ref()));
if let Some(p) = find_property_in_class(db, here, name) {
return Some((ancestor.clone(), p));
}
}
None
}
pub fn is_method_concretely_implemented(
db: &dyn MirDatabase,
fqcn: &str,
method_name: &str,
) -> bool {
let lower = method_name.to_lowercase();
let here = Fqcn::from_str(db, fqcn);
let Some(self_class) = find_class_like(db, here) else {
return false;
};
if self_class.is_interface() {
return false;
}
for ancestor_fqcn in class_ancestors_by_fqcn(db, here).iter() {
let here2 = Fqcn::from_str(db, ancestor_fqcn.as_ref());
let Some(class) = find_class_like(db, here2) else {
continue;
};
if class.is_interface() {
continue;
}
for (k, m) in class.own_methods().iter() {
if k.as_ref().eq_ignore_ascii_case(&lower) && !m.is_abstract {
return true;
}
}
}
false
}
pub fn find_class_constant_in_chain<'db>(
db: &'db dyn MirDatabase,
fqcn: Fqcn<'db>,
name: &str,
) -> Option<(Arc<str>, ConstantDef)> {
for ancestor in class_ancestors_by_fqcn(db, fqcn).iter() {
let here = Fqcn::new(db, Name::new(ancestor.as_ref()));
if let Some(c) = find_class_constant_in_class(db, here, name) {
return Some((ancestor.clone(), c));
}
}
None
}