pedant_core/project/
shape.rs1use std::collections::{BTreeMap, BTreeSet};
2use std::rc::Rc;
3use std::sync::Arc;
4
5use crate::check_config::CheckConfig;
6use crate::ir::{FileIr, FnFact, IrSpan};
7
8#[derive(Debug, Clone)]
10pub struct TypeDefSite {
11 pub type_name: Box<str>,
13 pub span: IrSpan,
15}
16
17#[derive(Debug, Clone)]
23pub struct InherentImplSite {
24 pub type_name: Box<str>,
26 pub cfg_predicates: Box<[Box<str>]>,
29 pub first_impl: IrSpan,
31 pub method_count: usize,
33}
34
35#[derive(Debug, Clone)]
41pub struct CfgGatedModule {
42 pub name: Box<str>,
44 pub cfg_predicates: Box<[Box<str>]>,
46}
47
48#[derive(Debug, Clone)]
54pub struct FileShape {
55 pub file_path: Arc<str>,
57 pub type_defs: Box<[TypeDefSite]>,
59 pub inherent_impls: Box<[InherentImplSite]>,
61 pub cfg_gated_modules: Box<[CfgGatedModule]>,
63}
64
65pub fn project_shape(ir: &FileIr, config: &CheckConfig) -> FileShape {
67 FileShape {
68 file_path: Arc::clone(&ir.file_path),
69 type_defs: collect_type_defs(ir),
70 inherent_impls: collect_inherent_impls(ir, config),
71 cfg_gated_modules: collect_cfg_gated_modules(ir),
72 }
73}
74
75pub(super) fn canonical_predicates<'a>(
78 predicates: impl IntoIterator<Item = &'a str>,
79) -> Box<[Box<str>]> {
80 predicates
81 .into_iter()
82 .collect::<BTreeSet<_>>()
83 .into_iter()
84 .map(Box::from)
85 .collect()
86}
87
88fn predicate_key(predicates: &[Rc<str>]) -> Box<[Box<str>]> {
89 canonical_predicates(predicates.iter().map(|predicate| &**predicate))
90}
91
92fn collect_type_defs(ir: &FileIr) -> Box<[TypeDefSite]> {
98 ir.type_defs
99 .iter()
100 .map(|def| TypeDefSite {
101 type_name: Box::from(&*def.name),
102 span: def.span,
103 })
104 .collect()
105}
106
107type SiteKey<'a> = (&'a str, Box<[Box<str>]>);
109
110fn collect_inherent_impls(ir: &FileIr, config: &CheckConfig) -> Box<[InherentImplSite]> {
111 let mut sites: BTreeMap<SiteKey<'_>, (IrSpan, usize)> = BTreeMap::new();
112 seed_impl_blocks(ir, &mut sites);
113 count_methods(ir, config, &mut sites);
114 sites
115 .into_iter()
116 .map(
117 |((type_name, cfg_predicates), (first_impl, method_count))| InherentImplSite {
118 type_name: Box::from(type_name),
119 cfg_predicates,
120 first_impl,
121 method_count,
122 },
123 )
124 .collect()
125}
126
127fn seed_impl_blocks<'a>(ir: &'a FileIr, sites: &mut BTreeMap<SiteKey<'a>, (IrSpan, usize)>) {
130 let inherent = ir.impl_blocks.iter().filter(|imp| imp.trait_name.is_none());
131 for imp in inherent {
132 sites
133 .entry((&imp.self_type, predicate_key(&imp.cfg_predicates)))
134 .or_insert((imp.span, 0));
135 }
136}
137
138fn count_methods<'a>(
141 ir: &'a FileIr,
142 config: &CheckConfig,
143 sites: &mut BTreeMap<SiteKey<'a>, (IrSpan, usize)>,
144) {
145 for func in &ir.functions {
146 let Some(type_name) = &func.inherent_method_of else {
147 continue;
148 };
149 if !counts_toward_surface(func, config) {
150 continue;
151 }
152 let entry = sites
153 .entry((&**type_name, predicate_key(&func.cfg_predicates)))
154 .or_insert((func.span, 0));
155 entry.1 += 1;
156 }
157}
158
159fn counts_toward_surface(func: &FnFact, config: &CheckConfig) -> bool {
164 !func.is_pure_forwarder || config.count_forwarders
165}
166
167fn collect_cfg_gated_modules(ir: &FileIr) -> Box<[CfgGatedModule]> {
168 ir.modules
169 .iter()
170 .filter(|module| !module.cfg_predicates.is_empty())
171 .map(|module| CfgGatedModule {
172 name: module.name.clone(),
173 cfg_predicates: predicate_key(&module.cfg_predicates),
174 })
175 .collect()
176}