use super::spec::CascadeSpec;
use super::strategy::CascadeStrategy;
use crate::query::DeriveIndex;
use crate::{CodeGraphV2, SymbolPath, SymbolRegistry, TypeFlowGraphV2};
pub struct CascadeAnalyzer<'a> {
graph: &'a CodeGraphV2,
typeflow: &'a TypeFlowGraphV2,
registry: &'a SymbolRegistry,
derive_index: &'a DeriveIndex,
}
impl<'a> CascadeAnalyzer<'a> {
pub fn new(
graph: &'a CodeGraphV2,
typeflow: &'a TypeFlowGraphV2,
registry: &'a SymbolRegistry,
derive_index: &'a DeriveIndex,
) -> Self {
Self {
graph,
typeflow,
registry,
derive_index,
}
}
pub fn cascade_add_derive(
&self,
target: &SymbolPath,
derives: &[String],
strategy: &CascadeStrategy,
) -> Vec<CascadeSpec> {
let mut specs = Vec::new();
if let Some(symbol_id) = self.registry.lookup(target) {
specs.push(CascadeSpec::add_derive(symbol_id, derives.to_vec()));
if strategy.is_eager() {
if let Some(field_types) = self.field_types_of(target) {
for field_type in field_types {
if self.needs_derive(&field_type, derives) {
specs.extend(self.cascade_add_derive(&field_type, derives, strategy));
}
}
}
}
}
specs
}
fn field_types_of(&self, target: &SymbolPath) -> Option<Vec<SymbolPath>> {
let symbol_id = self.registry.lookup(target)?;
let mut field_types = Vec::new();
for child_id in self.graph.children_of(symbol_id) {
for used_id in self.typeflow.types_used_by(child_id) {
if let Some(used_path) = self.registry.resolve(used_id) {
field_types.push(used_path.clone());
}
}
}
if field_types.is_empty() {
None
} else {
Some(field_types)
}
}
fn needs_derive(&self, target: &SymbolPath, derives: &[String]) -> bool {
if !target.crate_name().starts_with("crate") {
return false;
}
if let Some(symbol_id) = self.registry.lookup(target) {
let all_derived = derives
.iter()
.all(|d| self.derive_index.has_derive(symbol_id, d));
if all_derived {
return false;
}
}
true
}
}