use super::{CodeGraphV2, StdImplCache, TypeFlowGraphV2};
use crate::ast::ASTRegistry;
use crate::symbol::SymbolRegistry;
use crate::SymbolId;
use ryo_source::pure::{PureAttrMeta, PureFields, PureItem, PureType};
use serde::Serialize;
use slotmap::SecondaryMap;
use smallvec::SmallVec;
#[derive(Clone, Default, Debug, Serialize)]
pub struct DeriveIndex {
symbol_derives: SecondaryMap<SymbolId, SmallVec<[String; 4]>>,
field_type_names: SecondaryMap<SymbolId, SmallVec<[String; 8]>>,
}
impl DeriveIndex {
pub fn new() -> Self {
Self::default()
}
pub fn build(
ast_registry: &ASTRegistry,
code_graph: &CodeGraphV2,
typeflow: &TypeFlowGraphV2,
symbol_registry: &SymbolRegistry,
) -> Self {
let mut index = Self::new();
index.rebuild_all(ast_registry, code_graph, typeflow, symbol_registry);
index
}
pub fn rebuild_all(
&mut self,
ast_registry: &ASTRegistry,
code_graph: &CodeGraphV2,
typeflow: &TypeFlowGraphV2,
symbol_registry: &SymbolRegistry,
) {
self.symbol_derives.clear();
self.field_type_names.clear();
for (id, item) in ast_registry.iter() {
self.index_item(id, item, code_graph, typeflow, symbol_registry);
}
}
pub fn rebuild_for_symbols(
&mut self,
symbols: &[SymbolId],
ast_registry: &ASTRegistry,
code_graph: &CodeGraphV2,
typeflow: &TypeFlowGraphV2,
symbol_registry: &SymbolRegistry,
) {
for &symbol_id in symbols {
self.symbol_derives.remove(symbol_id);
self.field_type_names.remove(symbol_id);
if let Some(item) = ast_registry.get(symbol_id) {
self.index_item(symbol_id, item, code_graph, typeflow, symbol_registry);
}
}
}
fn index_item(
&mut self,
id: SymbolId,
item: &PureItem,
code_graph: &CodeGraphV2,
typeflow: &TypeFlowGraphV2,
symbol_registry: &SymbolRegistry,
) {
let attrs = match item {
PureItem::Struct(s) => &s.attrs,
PureItem::Enum(e) => &e.attrs,
_ => return,
};
let mut derives: SmallVec<[String; 4]> = SmallVec::new();
for attr in attrs {
if attr.path == "derive" {
if let PureAttrMeta::List(args) = &attr.meta {
for trait_name in args.split(',').map(|s| s.trim()) {
if !trait_name.is_empty() {
derives.push(trait_name.to_string());
}
}
}
}
}
if !derives.is_empty() {
self.symbol_derives.insert(id, derives);
}
let mut field_types: SmallVec<[String; 8]> = SmallVec::new();
for child_id in code_graph.children_of(id) {
if !matches!(
symbol_registry.kind(child_id),
Some(crate::SymbolKind::Field)
) {
continue;
}
for use_id in typeflow.types_used_by(child_id) {
if let Some(path) = symbol_registry.resolve(use_id) {
field_types.push(path.name().to_string());
break; }
}
}
let std_impls = StdImplCache::default();
let mut push_if_relevant = |field_types: &mut SmallVec<[String; 8]>, ty: &PureType| {
walk_type_for_std_heads(ty, &std_impls, &mut |head| {
if !field_types.iter().any(|existing| existing == &head) {
field_types.push(head);
}
});
walk_type_for_user_heads(ty, &std_impls, &mut |head| {
if !field_types.iter().any(|existing| existing == &head) {
field_types.push(head);
}
});
};
match item {
PureItem::Struct(s) => {
collect_field_types_from_fields(&s.fields, &mut field_types, &mut push_if_relevant)
}
PureItem::Enum(e) => {
for variant in &e.variants {
collect_field_types_from_fields(
&variant.fields,
&mut field_types,
&mut push_if_relevant,
);
}
}
_ => {}
}
if !field_types.is_empty() {
self.field_type_names.insert(id, field_types);
}
}
#[doc(hidden)]
pub fn insert_for_test(
&mut self,
id: SymbolId,
derives: impl IntoIterator<Item = String>,
field_types: impl IntoIterator<Item = String>,
) {
let derives: SmallVec<[String; 4]> = derives.into_iter().collect();
let field_types: SmallVec<[String; 8]> = field_types.into_iter().collect();
if !derives.is_empty() {
self.symbol_derives.insert(id, derives);
}
if !field_types.is_empty() {
self.field_type_names.insert(id, field_types);
}
}
pub fn iter_derives(&self) -> impl Iterator<Item = (SymbolId, &SmallVec<[String; 4]>)> {
self.symbol_derives.iter()
}
pub fn get_derives(&self, id: SymbolId) -> Option<&SmallVec<[String; 4]>> {
self.symbol_derives.get(id)
}
pub fn get_field_types(&self, id: SymbolId) -> Option<&SmallVec<[String; 8]>> {
self.field_type_names.get(id)
}
pub fn has_derive(&self, id: SymbolId, trait_name: &str) -> bool {
self.symbol_derives
.get(id)
.map(|derives| derives.iter().any(|d| d == trait_name))
.unwrap_or(false)
}
pub fn symbols_deriving(&self, trait_name: &str) -> Vec<SymbolId> {
self.symbol_derives
.iter()
.filter(|(_, derives)| derives.iter().any(|d| d == trait_name))
.map(|(id, _)| id)
.collect()
}
pub fn stats(&self) -> DeriveIndexStats {
let total_derives: usize = self.symbol_derives.values().map(|v| v.len()).sum();
let total_fields: usize = self.field_type_names.values().map(|v| v.len()).sum();
DeriveIndexStats {
symbols_with_derives: self.symbol_derives.len(),
total_derives,
symbols_with_fields: self.field_type_names.len(),
total_field_types: total_fields,
}
}
}
fn collect_field_types_from_fields(
fields: &PureFields,
field_types: &mut SmallVec<[String; 8]>,
push: &mut dyn FnMut(&mut SmallVec<[String; 8]>, &PureType),
) {
match fields {
PureFields::Named(named) => {
for f in named {
push(field_types, &f.ty);
}
}
PureFields::Tuple(tuple_fields) => {
for f in tuple_fields {
push(field_types, &f.ty);
}
}
PureFields::Unit => {}
}
}
fn walk_type_for_std_heads(ty: &PureType, std_impls: &StdImplCache, sink: &mut dyn FnMut(String)) {
match ty {
PureType::Path(p) => {
let pre_generic = p.split('<').next().unwrap_or(p).trim();
let head = pre_generic
.rsplit("::")
.next()
.unwrap_or(pre_generic)
.trim();
if !head.is_empty()
&& (std_impls.is_primitive(head) || std_impls.is_std_container(head))
{
sink(head.to_string());
}
}
PureType::Ref { ty, .. } => walk_type_for_std_heads(ty, std_impls, sink),
PureType::Tuple(types) => {
for t in types {
walk_type_for_std_heads(t, std_impls, sink);
}
}
PureType::Array { ty, .. } => walk_type_for_std_heads(ty, std_impls, sink),
PureType::Slice(inner) => walk_type_for_std_heads(inner, std_impls, sink),
_ => {}
}
}
fn walk_type_for_user_heads(ty: &PureType, std_impls: &StdImplCache, sink: &mut dyn FnMut(String)) {
match ty {
PureType::Path(p) => {
let pre_generic = p.split('<').next().unwrap_or(p).trim();
let head = pre_generic
.rsplit("::")
.next()
.unwrap_or(pre_generic)
.trim();
if head.is_empty() || std_impls.is_primitive(head) || std_impls.is_std_container(head) {
return; }
if is_likely_generic_param(head) {
return;
}
sink(head.to_string());
}
PureType::Ref { ty, .. } => walk_type_for_user_heads(ty, std_impls, sink),
PureType::Tuple(types) => {
for t in types {
walk_type_for_user_heads(t, std_impls, sink);
}
}
PureType::Array { ty, .. } => walk_type_for_user_heads(ty, std_impls, sink),
PureType::Slice(inner) => walk_type_for_user_heads(inner, std_impls, sink),
_ => {}
}
}
fn is_likely_generic_param(name: &str) -> bool {
let bytes = name.as_bytes();
bytes.len() == 1 && bytes[0].is_ascii_alphabetic()
}
#[derive(Debug, Clone)]
pub struct DeriveIndexStats {
pub symbols_with_derives: usize,
pub total_derives: usize,
pub symbols_with_fields: usize,
pub total_field_types: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_derive_index_creation() {
let index = DeriveIndex::new();
assert_eq!(index.stats().symbols_with_derives, 0);
}
#[cfg(feature = "testing")]
#[test]
fn test_unit_struct_with_impl_methods_has_no_field_types() {
use crate::testing::ContextBuilder;
let source = r#"
#[derive(Debug, Clone, Default)]
pub struct UnitWithMethods;
impl UnitWithMethods {
pub fn parse_a() -> Result<i32, String> { Ok(0) }
pub fn parse_b() -> Result<i32, String> { Ok(0) }
}
"#;
let ctx = ContextBuilder::new()
.with_file("src/lib.rs", source)
.build();
let struct_id = ctx
.registry
.iter()
.find(|(_, p)| p.name() == "UnitWithMethods")
.map(|(id, _)| id)
.expect("UnitWithMethods symbol should exist");
let derives = ctx
.derive_index
.get_derives(struct_id)
.expect("derives should be indexed");
assert!(
derives.iter().any(|d| d == "Default"),
"Default should be among the derives, got {:?}",
derives
);
assert!(
ctx.derive_index.get_field_types(struct_id).is_none(),
"unit struct must not record any field types, got {:?}",
ctx.derive_index.get_field_types(struct_id)
);
}
}