1use crate::ast::ASTRegistry;
27use crate::context::ImHashMap;
28use crate::symbol::{SymbolId, SymbolRegistry};
29use crate::SymbolKind;
30use ryo_source::pure::{
31 PureEnum, PureFields, PureFile, PureFn, PureGenerics, PureImpl, PureItem, PureParam,
32 PureStruct, PureTrait, PureType, PureVis,
33};
34use ryo_symbol::{SymbolPathResolver, WorkspaceFilePath};
35use serde::{Deserialize, Serialize};
36use slotmap::SecondaryMap;
37use std::collections::HashMap;
38use std::sync::Arc;
39
40#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
46pub struct GenericInfo {
47 pub type_params: Vec<String>,
49 pub lifetimes: Vec<String>,
51 pub const_params: Vec<(String, String)>,
53}
54
55impl GenericInfo {
56 pub fn is_empty(&self) -> bool {
58 self.type_params.is_empty() && self.lifetimes.is_empty() && self.const_params.is_empty()
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct ParamInfo {
65 pub name: String,
67 pub ty: String,
69 pub is_self: bool,
71 pub is_mut: bool,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct FieldInfo {
78 pub name: String,
80 pub ty: String,
82 pub is_public: bool,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct FunctionDetail {
93 pub is_async: bool,
96 pub is_const: bool,
98 pub is_unsafe: bool,
100
101 pub params: Vec<ParamInfo>,
104 pub return_type: Option<String>,
106 pub generics: GenericInfo,
108
109 pub is_method: bool,
112 pub self_ty: Option<String>,
114 pub trait_impl: Option<String>,
116 pub has_self: bool,
118
119 #[serde(default)]
122 pub attrs: Vec<String>,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127pub enum StructKind {
128 Named,
130 Tuple,
132 Unit,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct StructDetail {
139 pub fields: Vec<FieldInfo>,
141 pub kind: StructKind,
143 pub generics: GenericInfo,
145 #[serde(default)]
147 pub attrs: Vec<String>,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct VariantInfo {
153 pub name: String,
155 pub fields: Vec<FieldInfo>,
157 pub discriminant: Option<String>,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct EnumDetail {
164 pub variants: Vec<VariantInfo>,
166 pub generics: GenericInfo,
168 #[serde(default)]
170 pub attrs: Vec<String>,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct TraitDetail {
176 pub is_unsafe: bool,
178 pub is_auto: bool,
180 pub supertraits: Vec<String>,
182 pub methods: Vec<String>,
184 pub types: Vec<String>,
186 pub generics: GenericInfo,
188 #[serde(default)]
190 pub attrs: Vec<String>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195pub struct ImplDetail {
196 pub is_unsafe: bool,
198 pub self_ty: String,
200 pub trait_: Option<String>,
202 pub methods: Vec<String>,
204 pub generics: GenericInfo,
206 #[serde(default)]
208 pub attrs: Vec<String>,
209}
210
211#[derive(Clone, Serialize)]
226pub struct DetailStore {
227 functions: SecondaryMap<SymbolId, FunctionDetail>,
228 structs: SecondaryMap<SymbolId, StructDetail>,
229 enums: SecondaryMap<SymbolId, EnumDetail>,
230 traits: SecondaryMap<SymbolId, TraitDetail>,
231 impls: SecondaryMap<SymbolId, ImplDetail>,
232}
233
234impl DetailStore {
235 pub fn new() -> Self {
237 Self {
238 functions: SecondaryMap::new(),
239 structs: SecondaryMap::new(),
240 enums: SecondaryMap::new(),
241 traits: SecondaryMap::new(),
242 impls: SecondaryMap::new(),
243 }
244 }
245
246 #[inline]
252 pub fn function(&self, id: SymbolId) -> Option<&FunctionDetail> {
253 self.functions.get(id)
254 }
255
256 #[inline]
258 pub fn struct_(&self, id: SymbolId) -> Option<&StructDetail> {
259 self.structs.get(id)
260 }
261
262 #[inline]
264 pub fn enum_(&self, id: SymbolId) -> Option<&EnumDetail> {
265 self.enums.get(id)
266 }
267
268 #[inline]
270 pub fn trait_(&self, id: SymbolId) -> Option<&TraitDetail> {
271 self.traits.get(id)
272 }
273
274 #[inline]
276 pub fn impl_(&self, id: SymbolId) -> Option<&ImplDetail> {
277 self.impls.get(id)
278 }
279
280 #[deprecated(
289 since = "0.1.0",
290 note = "Use rebuild_for_symbols() for incremental updates. This is only for initial construction."
291 )]
292 pub fn build_all_workspace(
293 registry: &SymbolRegistry,
294 files: &HashMap<WorkspaceFilePath, PureFile>,
295 crate_name: &str,
296 ) -> Self {
297 let mut store = Self::new();
298 let resolver = SymbolPathResolver::new(crate_name);
299
300 let module_file_map: HashMap<String, (&WorkspaceFilePath, &PureFile)> = files
303 .iter()
304 .map(|(path, file)| {
305 let mod_path = resolver.module_path_str(path);
306 (mod_path, (path, file))
307 })
308 .collect();
309
310 for (id, path) in registry.iter() {
311 if let Some(kind) = registry.kind(id) {
312 let symbol_name = path.name();
313 let parent_path = path.parent().map(|p| p.to_string()).unwrap_or_default();
314
315 let module_path = if parent_path.contains("<impl") {
318 path.parent()
319 .and_then(|p| p.parent())
320 .map(|p| p.to_string())
321 .unwrap_or_default()
322 } else {
323 parent_path
324 };
325
326 if let Some((_path, file)) = module_file_map.get(&module_path) {
328 store.extract_detail_direct(id, kind, symbol_name, file);
329 }
330 }
331 }
332
333 store
334 }
335
336 #[deprecated(
344 since = "0.1.0",
345 note = "Use rebuild_for_symbols() for incremental updates. This is only for initial construction."
346 )]
347 pub fn build_from_arc_files(
348 registry: &SymbolRegistry,
349 files: &ImHashMap<WorkspaceFilePath, Arc<PureFile>>,
350 crate_name: &str,
351 ) -> Self {
352 let mut store = Self::new();
353 let resolver = SymbolPathResolver::new(crate_name);
354
355 let module_file_map: HashMap<String, &PureFile> = files
358 .iter()
359 .map(|(path, file)| {
360 let mod_path = resolver.module_path_str(path);
361 (mod_path, file.as_ref())
362 })
363 .collect();
364
365 for (id, path) in registry.iter() {
366 if let Some(kind) = registry.kind(id) {
367 let symbol_name = path.name();
368 let parent_path = path.parent().map(|p| p.to_string()).unwrap_or_default();
369
370 let module_path = if parent_path.contains("<impl") {
373 path.parent()
374 .and_then(|p| p.parent())
375 .map(|p| p.to_string())
376 .unwrap_or_default()
377 } else {
378 parent_path
379 };
380
381 if let Some(file) = module_file_map.get(&module_path) {
383 store.extract_detail_direct(id, kind, symbol_name, file);
384 }
385 }
386 }
387
388 store
389 }
390
391 pub fn rebuild_affected_workspace(
395 &mut self,
396 affected: &[SymbolId],
397 registry: &SymbolRegistry,
398 files: &ImHashMap<WorkspaceFilePath, Arc<PureFile>>,
399 crate_name: &str,
400 ) {
401 let resolver = SymbolPathResolver::new(crate_name);
402
403 let module_file_map: HashMap<String, &PureFile> = files
405 .iter()
406 .map(|(path, file)| {
407 let mod_path = resolver.module_path_str(path);
408 (mod_path, file.as_ref())
409 })
410 .collect();
411
412 for &id in affected {
413 self.remove(id);
415
416 if let Some(kind) = registry.kind(id) {
418 if let Some(path) = registry.resolve(id) {
419 let symbol_name = path.name();
420 let parent_path = path.parent().map(|p| p.to_string()).unwrap_or_default();
421
422 let module_path = if parent_path.contains("<impl") {
424 path.parent()
425 .and_then(|p| p.parent())
426 .map(|p| p.to_string())
427 .unwrap_or_default()
428 } else {
429 parent_path
430 };
431
432 if let Some(file) = module_file_map.get(&module_path) {
434 self.extract_detail_direct(id, kind, symbol_name, file);
435 }
436 }
437 }
438 }
439 }
440
441 pub fn remove(&mut self, id: SymbolId) {
443 self.functions.remove(id);
444 self.structs.remove(id);
445 self.enums.remove(id);
446 self.traits.remove(id);
447 self.impls.remove(id);
448 }
449
450 pub fn rebuild_for_symbols(&mut self, affected_ids: &[SymbolId], ast_registry: &ASTRegistry) {
459 for &id in affected_ids {
460 self.remove(id);
462
463 if let Some(item) = ast_registry.get(id) {
465 self.extract_from_item(id, item);
466 }
467 }
468 }
469
470 fn extract_from_item(&mut self, id: SymbolId, item: &PureItem) {
472 match item {
473 PureItem::Fn(f) => {
474 let detail = build_function_detail(f, None, None);
476 self.functions.insert(id, detail);
477 }
478 PureItem::Struct(s) => {
479 let detail = build_struct_detail(s);
480 self.structs.insert(id, detail);
481 }
482 PureItem::Enum(e) => {
483 let detail = build_enum_detail(e);
484 self.enums.insert(id, detail);
485 }
486 PureItem::Trait(t) => {
487 let detail = build_trait_detail(t);
488 self.traits.insert(id, detail);
489 }
490 PureItem::Impl(i) => {
491 let detail = build_impl_detail(i);
492 self.impls.insert(id, detail);
493 }
494 _ => {}
496 }
497 }
498
499 fn extract_detail_direct(
501 &mut self,
502 id: SymbolId,
503 kind: SymbolKind,
504 symbol_name: &str,
505 file: &PureFile,
506 ) {
507 match kind {
508 SymbolKind::Function => {
509 if let Some(detail) = extract_toplevel_function_detail(file, symbol_name) {
511 self.functions.insert(id, detail);
512 }
513 }
514 SymbolKind::Method => {
515 if let Some(detail) = extract_method_detail(file, symbol_name) {
517 self.functions.insert(id, detail);
518 }
519 }
520 SymbolKind::Struct => {
521 if let Some(detail) = extract_struct_detail(file, symbol_name) {
522 self.structs.insert(id, detail);
523 }
524 }
525 SymbolKind::Enum => {
526 if let Some(detail) = extract_enum_detail(file, symbol_name) {
527 self.enums.insert(id, detail);
528 }
529 }
530 SymbolKind::Trait => {
531 if let Some(detail) = extract_trait_detail(file, symbol_name) {
532 self.traits.insert(id, detail);
533 }
534 }
535 SymbolKind::Impl => {
536 if let Some(detail) = extract_impl_detail(file, symbol_name) {
537 self.impls.insert(id, detail);
538 }
539 }
540 _ => {}
541 }
542 }
543
544 pub fn len(&self) -> usize {
550 self.functions.len()
551 + self.structs.len()
552 + self.enums.len()
553 + self.traits.len()
554 + self.impls.len()
555 }
556
557 pub fn is_empty(&self) -> bool {
559 self.len() == 0
560 }
561}
562
563impl Default for DetailStore {
564 fn default() -> Self {
565 Self::new()
566 }
567}
568
569fn convert_generics(generics: &PureGenerics) -> GenericInfo {
575 let mut info = GenericInfo::default();
576
577 for param in &generics.params {
578 match param {
579 ryo_source::pure::PureGenericParam::Type { name, .. } => {
580 info.type_params.push(name.clone());
581 }
582 ryo_source::pure::PureGenericParam::Lifetime { name, .. } => {
583 info.lifetimes.push(name.clone());
584 }
585 ryo_source::pure::PureGenericParam::Const { name, ty } => {
586 info.const_params.push((name.clone(), ty.clone()));
587 }
588 }
589 }
590
591 info
592}
593
594fn type_to_string(ty: &PureType) -> String {
596 match ty {
597 PureType::Path(p) => p.clone(),
598 PureType::Ref {
599 lifetime,
600 is_mut,
601 ty,
602 } => {
603 let lt = lifetime
604 .as_ref()
605 .map(|l| format!("{} ", l))
606 .unwrap_or_default();
607 let m = if *is_mut { "mut " } else { "" };
608 format!("&{}{}{}", lt, m, type_to_string(ty))
609 }
610 PureType::Tuple(types) => {
611 let inner: Vec<_> = types.iter().map(type_to_string).collect();
612 format!("({})", inner.join(", "))
613 }
614 PureType::Array { ty, len } => format!("[{}; {}]", type_to_string(ty), len),
615 PureType::Slice(ty) => format!("[{}]", type_to_string(ty)),
616 PureType::Fn { params, ret } => {
617 let ps: Vec<_> = params.iter().map(type_to_string).collect();
618 let r = ret
619 .as_ref()
620 .map(|t| format!(" -> {}", type_to_string(t)))
621 .unwrap_or_default();
622 format!("fn({}){}", ps.join(", "), r)
623 }
624 PureType::ImplTrait(bounds) => format!("impl {}", bounds.join(" + ")),
625 PureType::TraitObject(bounds) => format!("dyn {}", bounds.join(" + ")),
626 PureType::Infer => "_".to_string(),
627 PureType::Never => "!".to_string(),
628 PureType::Other(s) => s.clone(),
629 }
630}
631
632fn is_public(vis: &PureVis) -> bool {
634 matches!(vis, PureVis::Public | PureVis::Crate)
635}
636
637fn extract_toplevel_function_detail(file: &PureFile, symbol_name: &str) -> Option<FunctionDetail> {
639 for item in &file.items {
640 if let PureItem::Fn(f) = item {
641 if f.name == symbol_name {
642 return Some(build_function_detail(f, None, None));
643 }
644 }
645 }
646 None
647}
648
649fn extract_method_detail(file: &PureFile, symbol_name: &str) -> Option<FunctionDetail> {
651 for item in &file.items {
652 if let PureItem::Impl(impl_block) = item {
654 for impl_item in &impl_block.items {
655 if let ryo_source::pure::PureImplItem::Fn(f) = impl_item {
656 if f.name == symbol_name {
657 return Some(build_function_detail(
658 f,
659 Some(&impl_block.self_ty),
660 impl_block.trait_.as_deref(),
661 ));
662 }
663 }
664 }
665 }
666 if let PureItem::Trait(trait_block) = item {
668 for trait_item in &trait_block.items {
669 if let ryo_source::pure::PureTraitItem::Fn(f) = trait_item {
670 if f.name == symbol_name {
671 return Some(build_function_detail(f, None, Some(&trait_block.name)));
672 }
673 }
674 }
675 }
676 }
677 None
678}
679
680#[cfg(test)]
681fn extract_function_detail(
683 file: &PureFile,
684 symbol_name: &str,
685 self_ty: Option<&str>,
686 trait_impl: Option<&str>,
687) -> Option<FunctionDetail> {
688 for item in &file.items {
690 if let PureItem::Fn(f) = item {
691 if f.name == symbol_name {
692 return Some(build_function_detail(f, self_ty, trait_impl));
693 }
694 }
695 if let PureItem::Impl(impl_block) = item {
697 for impl_item in &impl_block.items {
698 if let ryo_source::pure::PureImplItem::Fn(f) = impl_item {
699 if f.name == symbol_name {
700 return Some(build_function_detail(
701 f,
702 Some(&impl_block.self_ty),
703 impl_block.trait_.as_deref(),
704 ));
705 }
706 }
707 }
708 }
709 if let PureItem::Trait(trait_block) = item {
711 for trait_item in &trait_block.items {
712 if let ryo_source::pure::PureTraitItem::Fn(f) = trait_item {
713 if f.name == symbol_name {
714 return Some(build_function_detail(f, None, Some(&trait_block.name)));
715 }
716 }
717 }
718 }
719 }
720 None
721}
722
723fn build_function_detail(
725 f: &PureFn,
726 self_ty: Option<&str>,
727 trait_impl: Option<&str>,
728) -> FunctionDetail {
729 let mut params = Vec::new();
730 let mut has_self = false;
731
732 for param in &f.params {
733 match param {
734 PureParam::SelfValue { is_ref, is_mut } => {
735 has_self = true;
736 let ty = if *is_ref {
737 if *is_mut {
738 "&mut Self"
739 } else {
740 "&Self"
741 }
742 } else {
743 "Self"
744 };
745 params.push(ParamInfo {
746 name: "self".to_string(),
747 ty: ty.to_string(),
748 is_self: true,
749 is_mut: *is_mut,
750 });
751 }
752 PureParam::Typed { name, ty } => {
753 params.push(ParamInfo {
754 name: name.clone(),
755 ty: type_to_string(ty),
756 is_self: false,
757 is_mut: false,
758 });
759 }
760 }
761 }
762
763 FunctionDetail {
764 is_async: f.is_async,
765 is_const: f.is_const,
766 is_unsafe: f.is_unsafe,
767 params,
768 return_type: f.ret.as_ref().map(type_to_string),
769 generics: convert_generics(&f.generics),
770 is_method: self_ty.is_some(),
771 self_ty: self_ty.map(String::from),
772 trait_impl: trait_impl.map(String::from),
773 has_self,
774 attrs: extract_attr_paths(&f.attrs),
775 }
776}
777
778fn extract_struct_detail(file: &PureFile, symbol_name: &str) -> Option<StructDetail> {
780 for item in &file.items {
781 if let PureItem::Struct(s) = item {
782 if s.name == symbol_name {
783 return Some(build_struct_detail(s));
784 }
785 }
786 }
787 None
788}
789
790fn build_struct_detail(s: &PureStruct) -> StructDetail {
792 let (fields, kind) = match &s.fields {
793 PureFields::Named(fs) => {
794 let fields = fs
795 .iter()
796 .map(|f| FieldInfo {
797 name: f.name.clone(),
798 ty: type_to_string(&f.ty),
799 is_public: is_public(&f.vis),
800 })
801 .collect();
802 (fields, StructKind::Named)
803 }
804 PureFields::Tuple(types) => {
805 let fields = types
806 .iter()
807 .enumerate()
808 .map(|(i, ty)| FieldInfo {
809 name: i.to_string(),
810 ty: type_to_string(ty),
811 is_public: true, })
813 .collect();
814 (fields, StructKind::Tuple)
815 }
816 PureFields::Unit => (Vec::new(), StructKind::Unit),
817 };
818
819 StructDetail {
820 fields,
821 kind,
822 generics: convert_generics(&s.generics),
823 attrs: extract_attr_paths(&s.attrs),
824 }
825}
826
827fn extract_enum_detail(file: &PureFile, symbol_name: &str) -> Option<EnumDetail> {
829 for item in &file.items {
830 if let PureItem::Enum(e) = item {
831 if e.name == symbol_name {
832 return Some(build_enum_detail(e));
833 }
834 }
835 }
836 None
837}
838
839fn build_enum_detail(e: &PureEnum) -> EnumDetail {
841 let variants = e
842 .variants
843 .iter()
844 .map(|v| {
845 let fields = match &v.fields {
846 PureFields::Named(fs) => fs
847 .iter()
848 .map(|f| FieldInfo {
849 name: f.name.clone(),
850 ty: type_to_string(&f.ty),
851 is_public: is_public(&f.vis),
852 })
853 .collect(),
854 PureFields::Tuple(types) => types
855 .iter()
856 .enumerate()
857 .map(|(i, ty)| FieldInfo {
858 name: i.to_string(),
859 ty: type_to_string(ty),
860 is_public: true,
861 })
862 .collect(),
863 PureFields::Unit => Vec::new(),
864 };
865
866 VariantInfo {
867 name: v.name.clone(),
868 fields,
869 discriminant: v.discriminant.clone(),
870 }
871 })
872 .collect();
873
874 EnumDetail {
875 variants,
876 generics: convert_generics(&e.generics),
877 attrs: extract_attr_paths(&e.attrs),
878 }
879}
880
881fn extract_trait_detail(file: &PureFile, symbol_name: &str) -> Option<TraitDetail> {
883 for item in &file.items {
884 if let PureItem::Trait(t) = item {
885 if t.name == symbol_name {
886 return Some(build_trait_detail(t));
887 }
888 }
889 }
890 None
891}
892
893fn build_trait_detail(t: &PureTrait) -> TraitDetail {
895 let mut methods = Vec::new();
896 let mut types = Vec::new();
897
898 for item in &t.items {
899 match item {
900 ryo_source::pure::PureTraitItem::Fn(f) => methods.push(f.name.clone()),
901 ryo_source::pure::PureTraitItem::Type { name, .. } => types.push(name.clone()),
902 _ => {}
903 }
904 }
905
906 TraitDetail {
907 is_unsafe: t.is_unsafe,
908 is_auto: t.is_auto,
909 supertraits: t.supertraits.clone(),
910 methods,
911 types,
912 generics: convert_generics(&t.generics),
913 attrs: extract_attr_paths(&t.attrs),
914 }
915}
916
917fn extract_impl_detail(file: &PureFile, symbol_name: &str) -> Option<ImplDetail> {
921 for item in &file.items {
922 if let PureItem::Impl(i) = item {
923 let impl_name = if let Some(ref trait_name) = i.trait_ {
925 format!("<impl {} for {}>", trait_name, i.self_ty)
926 } else {
927 format!("<impl {}>", i.self_ty)
928 };
929 if impl_name == symbol_name {
930 return Some(build_impl_detail(i));
931 }
932 }
933 }
934 None
935}
936
937fn build_impl_detail(i: &PureImpl) -> ImplDetail {
939 let methods = i
940 .items
941 .iter()
942 .filter_map(|item| {
943 if let ryo_source::pure::PureImplItem::Fn(f) = item {
944 Some(f.name.clone())
945 } else {
946 None
947 }
948 })
949 .collect();
950
951 ImplDetail {
952 is_unsafe: i.is_unsafe,
953 self_ty: i.self_ty.clone(),
954 trait_: i.trait_.clone(),
955 methods,
956 generics: convert_generics(&i.generics),
957 attrs: extract_attr_paths(&i.attrs),
958 }
959}
960
961fn extract_attr_paths(attrs: &[ryo_source::pure::PureAttribute]) -> Vec<String> {
968 use ryo_source::pure::PureAttrMeta;
969
970 let mut result = Vec::new();
971 for attr in attrs {
972 result.push(attr.path.clone());
974
975 match &attr.meta {
977 PureAttrMeta::List(args) if !args.is_empty() => {
978 result.push(format!("{}({})", attr.path, args));
979 }
980 PureAttrMeta::NameValue(value) if !value.is_empty() => {
981 result.push(format!("{} = {}", attr.path, value));
982 }
983 _ => {}
984 }
985 }
986 result
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992
993 #[test]
994 fn test_generic_info_is_empty() {
995 let info = GenericInfo::default();
996 assert!(info.is_empty());
997
998 let info = GenericInfo {
999 type_params: vec!["T".to_string()],
1000 ..Default::default()
1001 };
1002 assert!(!info.is_empty());
1003 }
1004
1005 #[test]
1006 fn test_type_to_string() {
1007 assert_eq!(
1008 type_to_string(&PureType::Path("String".to_string())),
1009 "String"
1010 );
1011 assert_eq!(type_to_string(&PureType::Infer), "_");
1012 assert_eq!(type_to_string(&PureType::Never), "!");
1013 }
1014
1015 #[test]
1016 fn test_detail_store_new() {
1017 let store = DetailStore::new();
1018 assert!(store.is_empty());
1019 assert_eq!(store.len(), 0);
1020 }
1021
1022 #[test]
1023 fn test_extract_method_with_mut_self() {
1024 use ryo_source::pure::PureFile;
1025
1026 let source = r#"
1027 struct Foo;
1028 impl Foo {
1029 pub fn get_mut(&mut self) -> &mut i32 {
1030 &mut self.0
1031 }
1032 }
1033 "#;
1034
1035 let file = PureFile::from_source(source).unwrap();
1036 let detail = extract_function_detail(&file, "get_mut", None, None);
1037
1038 assert!(detail.is_some(), "get_mut should be found");
1039 let detail = detail.unwrap();
1040 assert!(detail.has_self, "get_mut should have self");
1041 assert!(!detail.params.is_empty(), "get_mut should have params");
1042
1043 let first_param = &detail.params[0];
1044 assert!(first_param.is_self, "first param should be self");
1045 assert!(first_param.is_mut, "first param should be mut");
1046 assert_eq!(first_param.ty, "&mut Self", "self type should be &mut Self");
1047 }
1048
1049 #[test]
1050 fn test_extract_toplevel_vs_method_same_name() {
1051 use ryo_source::pure::PureFile;
1052
1053 let source = r#"
1056 struct Executor;
1057
1058 impl Executor {
1059 pub fn execute(&self, cmd: &str) -> bool {
1060 true
1061 }
1062 }
1063
1064 pub fn execute(cmd: &str, config: &str) -> bool {
1065 false
1066 }
1067 "#;
1068
1069 let file = PureFile::from_source(source).unwrap();
1070
1071 let toplevel_detail = extract_toplevel_function_detail(&file, "execute");
1073 assert!(
1074 toplevel_detail.is_some(),
1075 "toplevel execute should be found"
1076 );
1077 let toplevel_detail = toplevel_detail.unwrap();
1078 assert!(
1079 !toplevel_detail.has_self,
1080 "toplevel execute should NOT have self"
1081 );
1082 assert!(
1083 !toplevel_detail.params.iter().any(|p| p.is_self),
1084 "toplevel execute params should not contain self"
1085 );
1086
1087 let method_detail = extract_method_detail(&file, "execute");
1089 assert!(method_detail.is_some(), "method execute should be found");
1090 let method_detail = method_detail.unwrap();
1091 assert!(method_detail.has_self, "method execute SHOULD have self");
1092 assert!(
1093 method_detail.params.iter().any(|p| p.is_self),
1094 "method execute params should contain self"
1095 );
1096 assert_eq!(
1097 method_detail.params[0].ty, "&Self",
1098 "method self should be &Self"
1099 );
1100 }
1101
1102 #[test]
1103 fn test_extract_method_with_ref_self() {
1104 use ryo_source::pure::PureFile;
1105
1106 let source = r#"
1107 struct Reader;
1108 impl Reader {
1109 pub fn read(&self, buf: &mut [u8]) -> usize {
1110 0
1111 }
1112 }
1113 "#;
1114
1115 let file = PureFile::from_source(source).unwrap();
1116 let detail = extract_method_detail(&file, "read");
1117
1118 assert!(detail.is_some(), "read should be found");
1119 let detail = detail.unwrap();
1120 assert!(detail.has_self, "read should have self");
1121 assert!(!detail.params.is_empty(), "read should have params");
1122
1123 let first_param = &detail.params[0];
1124 assert!(first_param.is_self, "first param should be self");
1125 assert!(!first_param.is_mut, "first param should NOT be mut");
1126 assert_eq!(first_param.ty, "&Self", "self type should be &Self");
1127 }
1128
1129 #[test]
1130 fn test_extract_method_with_owned_self() {
1131 use ryo_source::pure::PureFile;
1132
1133 let source = r#"
1134 struct Consumer;
1135 impl Consumer {
1136 pub fn consume(self) -> i32 {
1137 42
1138 }
1139 }
1140 "#;
1141
1142 let file = PureFile::from_source(source).unwrap();
1143 let detail = extract_method_detail(&file, "consume");
1144
1145 assert!(detail.is_some(), "consume should be found");
1146 let detail = detail.unwrap();
1147 assert!(detail.has_self, "consume should have self");
1148
1149 let first_param = &detail.params[0];
1150 assert!(first_param.is_self, "first param should be self");
1151 assert!(!first_param.is_mut, "first param should NOT be mut");
1152 assert_eq!(first_param.ty, "Self", "self type should be Self (owned)");
1153 }
1154
1155 #[test]
1156 fn test_extract_attrs_from_function() {
1157 use ryo_source::pure::PureFile;
1158
1159 let source = r#"
1160 #[deprecated(note = "use new_function instead")]
1161 #[inline]
1162 pub fn old_function() {}
1163 "#;
1164
1165 let file = PureFile::from_source(source).unwrap();
1166 let detail = extract_toplevel_function_detail(&file, "old_function");
1167
1168 assert!(detail.is_some(), "old_function should be found");
1169 let detail = detail.unwrap();
1170 assert!(
1171 detail.attrs.contains(&"deprecated".to_string()),
1172 "should have deprecated attr"
1173 );
1174 assert!(
1175 detail.attrs.contains(&"inline".to_string()),
1176 "should have inline attr"
1177 );
1178 }
1179
1180 #[test]
1181 fn test_extract_attrs_from_struct() {
1182 use ryo_source::pure::PureFile;
1183
1184 let source = r#"
1185 #[derive(Debug, Clone)]
1186 #[repr(C)]
1187 pub struct Config {
1188 pub name: String,
1189 }
1190 "#;
1191
1192 let file = PureFile::from_source(source).unwrap();
1193 let detail = extract_struct_detail(&file, "Config");
1194
1195 assert!(detail.is_some(), "Config should be found");
1196 let detail = detail.unwrap();
1197 assert!(
1198 detail.attrs.contains(&"derive".to_string()),
1199 "should have derive attr"
1200 );
1201 assert!(
1202 detail.attrs.contains(&"repr".to_string()),
1203 "should have repr attr"
1204 );
1205 }
1206}