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 {
753 name, ty, is_mut, ..
754 } => {
755 params.push(ParamInfo {
756 name: name.clone(),
757 ty: type_to_string(ty),
758 is_self: false,
759 is_mut: *is_mut,
760 });
761 }
762 }
763 }
764
765 FunctionDetail {
766 is_async: f.is_async,
767 is_const: f.is_const,
768 is_unsafe: f.is_unsafe,
769 params,
770 return_type: f.ret.as_ref().map(type_to_string),
771 generics: convert_generics(&f.generics),
772 is_method: self_ty.is_some(),
773 self_ty: self_ty.map(String::from),
774 trait_impl: trait_impl.map(String::from),
775 has_self,
776 attrs: extract_attr_paths(&f.attrs),
777 }
778}
779
780fn extract_struct_detail(file: &PureFile, symbol_name: &str) -> Option<StructDetail> {
782 for item in &file.items {
783 if let PureItem::Struct(s) = item {
784 if s.name == symbol_name {
785 return Some(build_struct_detail(s));
786 }
787 }
788 }
789 None
790}
791
792fn build_struct_detail(s: &PureStruct) -> StructDetail {
794 let (fields, kind) = match &s.fields {
795 PureFields::Named(fs) => {
796 let fields = fs
797 .iter()
798 .map(|f| FieldInfo {
799 name: f.name.clone(),
800 ty: type_to_string(&f.ty),
801 is_public: is_public(&f.vis),
802 })
803 .collect();
804 (fields, StructKind::Named)
805 }
806 PureFields::Tuple(tuple_fields) => {
807 let fields = tuple_fields
808 .iter()
809 .enumerate()
810 .map(|(i, f)| FieldInfo {
811 name: i.to_string(),
812 ty: type_to_string(&f.ty),
813 is_public: true, })
815 .collect();
816 (fields, StructKind::Tuple)
817 }
818 PureFields::Unit => (Vec::new(), StructKind::Unit),
819 };
820
821 StructDetail {
822 fields,
823 kind,
824 generics: convert_generics(&s.generics),
825 attrs: extract_attr_paths(&s.attrs),
826 }
827}
828
829fn extract_enum_detail(file: &PureFile, symbol_name: &str) -> Option<EnumDetail> {
831 for item in &file.items {
832 if let PureItem::Enum(e) = item {
833 if e.name == symbol_name {
834 return Some(build_enum_detail(e));
835 }
836 }
837 }
838 None
839}
840
841fn build_enum_detail(e: &PureEnum) -> EnumDetail {
843 let variants = e
844 .variants
845 .iter()
846 .map(|v| {
847 let fields = match &v.fields {
848 PureFields::Named(fs) => fs
849 .iter()
850 .map(|f| FieldInfo {
851 name: f.name.clone(),
852 ty: type_to_string(&f.ty),
853 is_public: is_public(&f.vis),
854 })
855 .collect(),
856 PureFields::Tuple(tuple_fields) => tuple_fields
857 .iter()
858 .enumerate()
859 .map(|(i, f)| FieldInfo {
860 name: i.to_string(),
861 ty: type_to_string(&f.ty),
862 is_public: true,
863 })
864 .collect(),
865 PureFields::Unit => Vec::new(),
866 };
867
868 VariantInfo {
869 name: v.name.clone(),
870 fields,
871 discriminant: v.discriminant.clone(),
872 }
873 })
874 .collect();
875
876 EnumDetail {
877 variants,
878 generics: convert_generics(&e.generics),
879 attrs: extract_attr_paths(&e.attrs),
880 }
881}
882
883fn extract_trait_detail(file: &PureFile, symbol_name: &str) -> Option<TraitDetail> {
885 for item in &file.items {
886 if let PureItem::Trait(t) = item {
887 if t.name == symbol_name {
888 return Some(build_trait_detail(t));
889 }
890 }
891 }
892 None
893}
894
895fn build_trait_detail(t: &PureTrait) -> TraitDetail {
897 let mut methods = Vec::new();
898 let mut types = Vec::new();
899
900 for item in &t.items {
901 match item {
902 ryo_source::pure::PureTraitItem::Fn(f) => methods.push(f.name.clone()),
903 ryo_source::pure::PureTraitItem::Type { name, .. } => types.push(name.clone()),
904 _ => {}
905 }
906 }
907
908 TraitDetail {
909 is_unsafe: t.is_unsafe,
910 is_auto: t.is_auto,
911 supertraits: t.supertraits.clone(),
912 methods,
913 types,
914 generics: convert_generics(&t.generics),
915 attrs: extract_attr_paths(&t.attrs),
916 }
917}
918
919fn extract_impl_detail(file: &PureFile, symbol_name: &str) -> Option<ImplDetail> {
923 for item in &file.items {
924 if let PureItem::Impl(i) = item {
925 let impl_name = if let Some(ref trait_name) = i.trait_ {
927 format!("<impl {} for {}>", trait_name, i.self_ty)
928 } else {
929 format!("<impl {}>", i.self_ty)
930 };
931 if impl_name == symbol_name {
932 return Some(build_impl_detail(i));
933 }
934 }
935 }
936 None
937}
938
939fn build_impl_detail(i: &PureImpl) -> ImplDetail {
941 let methods = i
942 .items
943 .iter()
944 .filter_map(|item| {
945 if let ryo_source::pure::PureImplItem::Fn(f) = item {
946 Some(f.name.clone())
947 } else {
948 None
949 }
950 })
951 .collect();
952
953 ImplDetail {
954 is_unsafe: i.is_unsafe,
955 self_ty: i.self_ty.clone(),
956 trait_: i.trait_.clone(),
957 methods,
958 generics: convert_generics(&i.generics),
959 attrs: extract_attr_paths(&i.attrs),
960 }
961}
962
963fn extract_attr_paths(attrs: &[ryo_source::pure::PureAttribute]) -> Vec<String> {
970 use ryo_source::pure::PureAttrMeta;
971
972 let mut result = Vec::new();
973 for attr in attrs {
974 result.push(attr.path.clone());
976
977 match &attr.meta {
979 PureAttrMeta::List(args) if !args.is_empty() => {
980 result.push(format!("{}({})", attr.path, args));
981 }
982 PureAttrMeta::NameValue(value) if !value.is_empty() => {
983 result.push(format!("{} = {}", attr.path, value));
984 }
985 _ => {}
986 }
987 }
988 result
989}
990
991#[cfg(test)]
992mod tests {
993 use super::*;
994
995 #[test]
996 fn test_generic_info_is_empty() {
997 let info = GenericInfo::default();
998 assert!(info.is_empty());
999
1000 let info = GenericInfo {
1001 type_params: vec!["T".to_string()],
1002 ..Default::default()
1003 };
1004 assert!(!info.is_empty());
1005 }
1006
1007 #[test]
1008 fn test_type_to_string() {
1009 assert_eq!(
1010 type_to_string(&PureType::Path("String".to_string())),
1011 "String"
1012 );
1013 assert_eq!(type_to_string(&PureType::Infer), "_");
1014 assert_eq!(type_to_string(&PureType::Never), "!");
1015 }
1016
1017 #[test]
1018 fn test_detail_store_new() {
1019 let store = DetailStore::new();
1020 assert!(store.is_empty());
1021 assert_eq!(store.len(), 0);
1022 }
1023
1024 #[test]
1025 fn test_extract_method_with_mut_self() {
1026 use ryo_source::pure::PureFile;
1027
1028 let source = r#"
1029 struct Foo;
1030 impl Foo {
1031 pub fn get_mut(&mut self) -> &mut i32 {
1032 &mut self.0
1033 }
1034 }
1035 "#;
1036
1037 let file = PureFile::from_source(source).unwrap();
1038 let detail = extract_function_detail(&file, "get_mut", None, None);
1039
1040 assert!(detail.is_some(), "get_mut should be found");
1041 let detail = detail.unwrap();
1042 assert!(detail.has_self, "get_mut should have self");
1043 assert!(!detail.params.is_empty(), "get_mut should have params");
1044
1045 let first_param = &detail.params[0];
1046 assert!(first_param.is_self, "first param should be self");
1047 assert!(first_param.is_mut, "first param should be mut");
1048 assert_eq!(first_param.ty, "&mut Self", "self type should be &mut Self");
1049 }
1050
1051 #[test]
1052 fn test_extract_toplevel_vs_method_same_name() {
1053 use ryo_source::pure::PureFile;
1054
1055 let source = r#"
1058 struct Executor;
1059
1060 impl Executor {
1061 pub fn execute(&self, cmd: &str) -> bool {
1062 true
1063 }
1064 }
1065
1066 pub fn execute(cmd: &str, config: &str) -> bool {
1067 false
1068 }
1069 "#;
1070
1071 let file = PureFile::from_source(source).unwrap();
1072
1073 let toplevel_detail = extract_toplevel_function_detail(&file, "execute");
1075 assert!(
1076 toplevel_detail.is_some(),
1077 "toplevel execute should be found"
1078 );
1079 let toplevel_detail = toplevel_detail.unwrap();
1080 assert!(
1081 !toplevel_detail.has_self,
1082 "toplevel execute should NOT have self"
1083 );
1084 assert!(
1085 !toplevel_detail.params.iter().any(|p| p.is_self),
1086 "toplevel execute params should not contain self"
1087 );
1088
1089 let method_detail = extract_method_detail(&file, "execute");
1091 assert!(method_detail.is_some(), "method execute should be found");
1092 let method_detail = method_detail.unwrap();
1093 assert!(method_detail.has_self, "method execute SHOULD have self");
1094 assert!(
1095 method_detail.params.iter().any(|p| p.is_self),
1096 "method execute params should contain self"
1097 );
1098 assert_eq!(
1099 method_detail.params[0].ty, "&Self",
1100 "method self should be &Self"
1101 );
1102 }
1103
1104 #[test]
1105 fn test_extract_method_with_ref_self() {
1106 use ryo_source::pure::PureFile;
1107
1108 let source = r#"
1109 struct Reader;
1110 impl Reader {
1111 pub fn read(&self, buf: &mut [u8]) -> usize {
1112 0
1113 }
1114 }
1115 "#;
1116
1117 let file = PureFile::from_source(source).unwrap();
1118 let detail = extract_method_detail(&file, "read");
1119
1120 assert!(detail.is_some(), "read should be found");
1121 let detail = detail.unwrap();
1122 assert!(detail.has_self, "read should have self");
1123 assert!(!detail.params.is_empty(), "read should have params");
1124
1125 let first_param = &detail.params[0];
1126 assert!(first_param.is_self, "first param should be self");
1127 assert!(!first_param.is_mut, "first param should NOT be mut");
1128 assert_eq!(first_param.ty, "&Self", "self type should be &Self");
1129 }
1130
1131 #[test]
1132 fn test_extract_method_with_owned_self() {
1133 use ryo_source::pure::PureFile;
1134
1135 let source = r#"
1136 struct Consumer;
1137 impl Consumer {
1138 pub fn consume(self) -> i32 {
1139 42
1140 }
1141 }
1142 "#;
1143
1144 let file = PureFile::from_source(source).unwrap();
1145 let detail = extract_method_detail(&file, "consume");
1146
1147 assert!(detail.is_some(), "consume should be found");
1148 let detail = detail.unwrap();
1149 assert!(detail.has_self, "consume should have self");
1150
1151 let first_param = &detail.params[0];
1152 assert!(first_param.is_self, "first param should be self");
1153 assert!(!first_param.is_mut, "first param should NOT be mut");
1154 assert_eq!(first_param.ty, "Self", "self type should be Self (owned)");
1155 }
1156
1157 #[test]
1158 fn test_extract_attrs_from_function() {
1159 use ryo_source::pure::PureFile;
1160
1161 let source = r#"
1162 #[deprecated(note = "use new_function instead")]
1163 #[inline]
1164 pub fn old_function() {}
1165 "#;
1166
1167 let file = PureFile::from_source(source).unwrap();
1168 let detail = extract_toplevel_function_detail(&file, "old_function");
1169
1170 assert!(detail.is_some(), "old_function should be found");
1171 let detail = detail.unwrap();
1172 assert!(
1173 detail.attrs.contains(&"deprecated".to_string()),
1174 "should have deprecated attr"
1175 );
1176 assert!(
1177 detail.attrs.contains(&"inline".to_string()),
1178 "should have inline attr"
1179 );
1180 }
1181
1182 #[test]
1183 fn test_extract_attrs_from_struct() {
1184 use ryo_source::pure::PureFile;
1185
1186 let source = r#"
1187 #[derive(Debug, Clone)]
1188 #[repr(C)]
1189 pub struct Config {
1190 pub name: String,
1191 }
1192 "#;
1193
1194 let file = PureFile::from_source(source).unwrap();
1195 let detail = extract_struct_detail(&file, "Config");
1196
1197 assert!(detail.is_some(), "Config should be found");
1198 let detail = detail.unwrap();
1199 assert!(
1200 detail.attrs.contains(&"derive".to_string()),
1201 "should have derive attr"
1202 );
1203 assert!(
1204 detail.attrs.contains(&"repr".to_string()),
1205 "should have repr attr"
1206 );
1207 }
1208}