ryo_analysis/query/
type_impact.rs1use super::{TypeFlowGraphV2, TypeImpactV2, TypeNodeId, UsageContext};
18use crate::SymbolId;
19
20#[derive(Debug, Clone)]
22pub struct TypeImpactResult {
23 pub changed_symbol: SymbolId,
25
26 pub direct_usage_count: usize,
28
29 pub bound_usage_count: usize,
31
32 pub containing_types: Vec<SymbolId>,
34
35 pub issues: Vec<TypeImpactIssue>,
37}
38
39impl TypeImpactResult {
40 pub fn has_issues(&self) -> bool {
42 !self.issues.is_empty()
43 }
44
45 pub fn has_impact(&self) -> bool {
47 self.direct_usage_count > 0
48 || self.bound_usage_count > 0
49 || !self.containing_types.is_empty()
50 }
51}
52
53#[derive(Debug, Clone)]
55pub enum TypeImpactIssue {
56 FieldUsageInType {
58 container_type: SymbolId,
60 },
61
62 TraitBoundUsage {
64 bound_count: usize,
66 },
67
68 ParameterUsage {
70 usage_count: usize,
72 },
73
74 ReturnTypeUsage {
76 usage_count: usize,
78 },
79
80 ImplUsage {
82 usage_count: usize,
84 },
85}
86
87pub struct TypeImpactChecker<'a> {
106 typeflow: &'a TypeFlowGraphV2,
107}
108
109impl<'a> TypeImpactChecker<'a> {
110 pub fn new(typeflow: &'a TypeFlowGraphV2) -> Self {
112 Self { typeflow }
113 }
114
115 pub fn analyze_impact(&self, symbol_id: SymbolId) -> TypeImpactResult {
119 let impact = self.typeflow.impact(symbol_id);
120 let mut issues = Vec::new();
121
122 let (param_count, return_count, impl_count) = self.categorize_usages(&impact);
124
125 if param_count > 0 {
126 issues.push(TypeImpactIssue::ParameterUsage {
127 usage_count: param_count,
128 });
129 }
130
131 if return_count > 0 {
132 issues.push(TypeImpactIssue::ReturnTypeUsage {
133 usage_count: return_count,
134 });
135 }
136
137 if impl_count > 0 {
138 issues.push(TypeImpactIssue::ImplUsage {
139 usage_count: impl_count,
140 });
141 }
142
143 if !impact.bound_usages.is_empty() {
145 issues.push(TypeImpactIssue::TraitBoundUsage {
146 bound_count: impact.bound_usages.len(),
147 });
148 }
149
150 for container in &impact.containing_types {
152 issues.push(TypeImpactIssue::FieldUsageInType {
153 container_type: *container,
154 });
155 }
156
157 TypeImpactResult {
158 changed_symbol: symbol_id,
159 direct_usage_count: impact.direct_usages.len(),
160 bound_usage_count: impact.bound_usages.len(),
161 containing_types: impact.containing_types,
162 issues,
163 }
164 }
165
166 pub fn affected_symbols(&self, symbol_id: SymbolId) -> Vec<SymbolId> {
173 let impact = self.typeflow.impact(symbol_id);
174 let mut affected = Vec::new();
175
176 affected.extend(impact.containing_types.iter().copied());
178
179 for usage_idx in &impact.direct_usages {
181 if let Some(usage) = self.typeflow.get_usage(TypeNodeId::usage(*usage_idx)) {
182 if let Some(resolved) = usage.resolved {
183 affected.push(resolved);
184 }
185 }
186 }
187
188 affected.sort();
189 affected.dedup();
190 affected
191 }
192
193 fn categorize_usages(&self, impact: &TypeImpactV2) -> (usize, usize, usize) {
197 let mut param_count = 0;
198 let mut return_count = 0;
199 let mut impl_count = 0;
200
201 for usage_idx in &impact.direct_usages {
202 if let Some(usage) = self.typeflow.get_usage(TypeNodeId::usage(*usage_idx)) {
203 match usage.context {
204 UsageContext::ParamType => param_count += 1,
205 UsageContext::ReturnType => return_count += 1,
206 UsageContext::ImplTarget | UsageContext::ImplTrait => impl_count += 1,
207 _ => {}
208 }
209 }
210 }
211
212 (param_count, return_count, impl_count)
213 }
214}
215
216#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::symbol::{SymbolPath, SymbolRegistry};
224 use crate::SymbolKind;
225 use crate::TypeDefKind;
226
227 fn create_test_setup() -> (SymbolRegistry, TypeFlowGraphV2, SymbolId, SymbolId) {
228 let mut registry = SymbolRegistry::new();
229 let foo_id = registry
230 .register(SymbolPath::parse("test::Foo").unwrap(), SymbolKind::Struct)
231 .unwrap();
232 let bar_id = registry
233 .register(SymbolPath::parse("test::Bar").unwrap(), SymbolKind::Struct)
234 .unwrap();
235
236 let mut typeflow = TypeFlowGraphV2::new();
237
238 typeflow.add_definition(foo_id, TypeDefKind::Struct);
240
241 typeflow.add_definition(bar_id, TypeDefKind::Struct);
243
244 typeflow.add_contains(bar_id, foo_id);
246
247 (registry, typeflow, foo_id, bar_id)
248 }
249
250 #[test]
251 fn test_analyze_impact_no_usages() {
252 let (_, typeflow, _, bar_id) = create_test_setup();
253
254 let checker = TypeImpactChecker::new(&typeflow);
255 let result = checker.analyze_impact(bar_id);
256
257 assert_eq!(result.direct_usage_count, 0);
259 assert!(result.containing_types.is_empty());
260 }
261
262 #[test]
263 fn test_analyze_impact_with_containing_type() {
264 let (_, typeflow, foo_id, bar_id) = create_test_setup();
265
266 let checker = TypeImpactChecker::new(&typeflow);
267 let result = checker.analyze_impact(foo_id);
268
269 assert!(result.containing_types.contains(&bar_id));
271 assert!(result.has_impact());
272 }
273
274 #[test]
275 fn test_affected_symbols() {
276 let (_, typeflow, foo_id, bar_id) = create_test_setup();
277
278 let checker = TypeImpactChecker::new(&typeflow);
279 let affected = checker.affected_symbols(foo_id);
280
281 assert!(affected.contains(&bar_id));
283 }
284}