use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;
use std::sync::Arc;
use crate::check_config::CheckConfig;
use crate::ir::{FileIr, FnFact, IrSpan};
#[derive(Debug, Clone)]
pub struct TypeDefSite {
pub type_name: Box<str>,
pub span: IrSpan,
}
#[derive(Debug, Clone)]
pub struct InherentImplSite {
pub type_name: Box<str>,
pub cfg_predicates: Box<[Box<str>]>,
pub first_impl: IrSpan,
pub method_count: usize,
}
#[derive(Debug, Clone)]
pub struct CfgGatedModule {
pub name: Box<str>,
pub cfg_predicates: Box<[Box<str>]>,
}
#[derive(Debug, Clone)]
pub struct FileShape {
pub file_path: Arc<str>,
pub type_defs: Box<[TypeDefSite]>,
pub inherent_impls: Box<[InherentImplSite]>,
pub cfg_gated_modules: Box<[CfgGatedModule]>,
}
pub fn project_shape(ir: &FileIr, config: &CheckConfig) -> FileShape {
FileShape {
file_path: Arc::clone(&ir.file_path),
type_defs: collect_type_defs(ir),
inherent_impls: collect_inherent_impls(ir, config),
cfg_gated_modules: collect_cfg_gated_modules(ir),
}
}
pub(super) fn canonical_predicates<'a>(
predicates: impl IntoIterator<Item = &'a str>,
) -> Box<[Box<str>]> {
predicates
.into_iter()
.collect::<BTreeSet<_>>()
.into_iter()
.map(Box::from)
.collect()
}
fn predicate_key(predicates: &[Rc<str>]) -> Box<[Box<str>]> {
canonical_predicates(predicates.iter().map(|predicate| &**predicate))
}
fn collect_type_defs(ir: &FileIr) -> Box<[TypeDefSite]> {
ir.type_defs
.iter()
.map(|def| TypeDefSite {
type_name: Box::from(&*def.name),
span: def.span,
})
.collect()
}
type SiteKey<'a> = (&'a str, Box<[Box<str>]>);
fn collect_inherent_impls(ir: &FileIr, config: &CheckConfig) -> Box<[InherentImplSite]> {
let mut sites: BTreeMap<SiteKey<'_>, (IrSpan, usize)> = BTreeMap::new();
seed_impl_blocks(ir, &mut sites);
count_methods(ir, config, &mut sites);
sites
.into_iter()
.map(
|((type_name, cfg_predicates), (first_impl, method_count))| InherentImplSite {
type_name: Box::from(type_name),
cfg_predicates,
first_impl,
method_count,
},
)
.collect()
}
fn seed_impl_blocks<'a>(ir: &'a FileIr, sites: &mut BTreeMap<SiteKey<'a>, (IrSpan, usize)>) {
let inherent = ir.impl_blocks.iter().filter(|imp| imp.trait_name.is_none());
for imp in inherent {
sites
.entry((&imp.self_type, predicate_key(&imp.cfg_predicates)))
.or_insert((imp.span, 0));
}
}
fn count_methods<'a>(
ir: &'a FileIr,
config: &CheckConfig,
sites: &mut BTreeMap<SiteKey<'a>, (IrSpan, usize)>,
) {
for func in &ir.functions {
let Some(type_name) = &func.inherent_method_of else {
continue;
};
if !counts_toward_surface(func, config) {
continue;
}
let entry = sites
.entry((&**type_name, predicate_key(&func.cfg_predicates)))
.or_insert((func.span, 0));
entry.1 += 1;
}
}
fn counts_toward_surface(func: &FnFact, config: &CheckConfig) -> bool {
!func.is_pure_forwarder || config.count_forwarders
}
fn collect_cfg_gated_modules(ir: &FileIr) -> Box<[CfgGatedModule]> {
ir.modules
.iter()
.filter(|module| !module.cfg_predicates.is_empty())
.map(|module| CfgGatedModule {
name: module.name.clone(),
cfg_predicates: predicate_key(&module.cfg_predicates),
})
.collect()
}