1use std::fmt::{self, Display, Formatter};
4use std::str::FromStr;
5
6use compact_str::CompactString;
7use serde::{Deserialize, Serialize};
8use smallvec::SmallVec;
9
10use crate::crate_name::CrateName;
11use crate::error::ParseError;
12use crate::file_path::WorkspaceFilePath;
13use crate::var_scope::VarScope;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub struct Segment(CompactString);
26
27impl Segment {
28 pub fn new(name: impl AsRef<str>) -> Result<Self, ParseError> {
33 let name = name.as_ref();
34 validate_rust_identifier(name)?;
35 Ok(Self(name.into()))
36 }
37
38 pub(crate) fn new_unchecked(name: impl AsRef<str>) -> Self {
40 Self(name.as_ref().into())
41 }
42
43 pub fn name(&self) -> &str {
45 &self.0
46 }
47
48 pub fn tuple_field(index: usize) -> Self {
57 Self(index.to_string().into())
58 }
59
60 pub fn is_tuple_field(&self) -> bool {
62 !self.0.is_empty() && self.0.chars().all(|c| c.is_ascii_digit())
63 }
64
65 pub fn tuple_field_index(&self) -> Option<usize> {
67 if self.is_tuple_field() {
68 self.0.parse().ok()
69 } else {
70 None
71 }
72 }
73
74 pub fn is_scope_marker(&self) -> bool {
76 self.0.starts_with('$')
77 }
78
79 pub fn as_var_scope(&self) -> Option<VarScope> {
81 VarScope::from_segment(&self.0)
82 }
83
84 pub fn param_scope() -> Self {
88 Self::new_unchecked("$param")
89 }
90
91 pub fn local_scope() -> Self {
93 Self::new_unchecked("$var")
94 }
95
96 pub fn field_scope() -> Self {
98 Self::new_unchecked("$field")
99 }
100
101 pub fn body_scope() -> Self {
103 Self::new_unchecked("$body")
104 }
105
106 pub fn stmt_scope() -> Self {
108 Self::new_unchecked("$stmt")
109 }
110
111 pub fn expr_scope() -> Self {
113 Self::new_unchecked("$expr")
114 }
115
116 pub fn inherent_impl(self_ty: &str) -> Self {
120 Self::new_unchecked(format!("<impl {}>", self_ty))
121 }
122
123 pub fn trait_impl(trait_name: &str, self_ty: &str) -> Self {
125 Self::new_unchecked(format!("<impl {} for {}>", trait_name, self_ty))
126 }
127
128 pub fn is_impl(&self) -> bool {
132 self.0.starts_with("<impl ") && self.0.ends_with('>')
133 }
134
135 pub fn is_trait_impl(&self) -> bool {
137 self.is_impl() && self.0.contains(" for ")
138 }
139
140 pub fn impl_self_ty(&self) -> Option<&str> {
146 if !self.is_impl() {
147 return None;
148 }
149 let inner = &self.0[6..self.0.len() - 1];
151 if let Some(pos) = inner.find(" for ") {
152 Some(&inner[pos + 5..])
153 } else {
154 Some(inner)
155 }
156 }
157
158 pub fn impl_trait(&self) -> Option<&str> {
164 if !self.is_impl() {
165 return None;
166 }
167 let inner = &self.0[6..self.0.len() - 1];
168 inner.find(" for ").map(|pos| &inner[..pos])
169 }
170
171 pub fn is_param_scope(&self) -> bool {
175 self.0.as_str() == "$param"
176 }
177
178 pub fn is_local_scope(&self) -> bool {
180 self.0.as_str() == "$var"
181 }
182
183 pub fn is_field_scope(&self) -> bool {
185 self.0.as_str() == "$field"
186 }
187
188 pub fn is_body_scope(&self) -> bool {
190 self.0.as_str() == "$body"
191 }
192
193 pub fn is_stmt_scope(&self) -> bool {
195 self.0.as_str() == "$stmt"
196 }
197
198 pub fn is_expr_scope(&self) -> bool {
200 self.0.as_str() == "$expr"
201 }
202}
203
204impl Display for Segment {
205 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
206 write!(f, "{}", self.0)
207 }
208}
209
210fn validate_rust_identifier(s: &str) -> Result<(), ParseError> {
214 if s.is_empty() {
215 return Err(ParseError::InvalidIdentifier(s.to_string()));
216 }
217
218 if s.chars().all(|c| c.is_ascii_digit()) {
220 return Ok(());
221 }
222
223 if s.starts_with('$') {
225 if VarScope::from_segment(s).is_some() || matches!(s, "$body" | "$stmt" | "$expr") {
226 return Ok(());
227 }
228 return Err(ParseError::InvalidIdentifier(s.to_string()));
229 }
230
231 if s.starts_with("<impl ") && s.ends_with('>') {
233 return Ok(());
234 }
235
236 let mut chars = s.chars();
238 let first = chars
239 .next()
240 .expect("non-empty checked at function entry (s.is_empty() guard)");
241 if !first.is_ascii_alphabetic() && first != '_' {
242 return Err(ParseError::InvalidIdentifier(s.to_string()));
243 }
244
245 for c in chars {
246 if !c.is_ascii_alphanumeric() && c != '_' {
247 return Err(ParseError::InvalidIdentifier(s.to_string()));
248 }
249 }
250
251 Ok(())
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
269pub struct SymbolPath {
270 segments: SmallVec<[Segment; 12]>,
275
276 crate_root_depth: u8,
282}
283
284impl SymbolPath {
285 pub fn from_file_path(
298 crate_name: &CrateName,
299 path: &WorkspaceFilePath,
300 ) -> Result<Self, ParseError> {
301 let mut builder = Self::builder(crate_name.as_str());
302
303 let relative = path.as_relative();
309 let mut components: Vec<_> = relative
310 .components()
311 .filter_map(|c| c.as_os_str().to_str())
312 .collect();
313
314 if let Some(src_idx) = components.iter().position(|&c| c == "src") {
317 components = components[src_idx..].to_vec();
318 }
319
320 if components.first() == Some(&"src") {
322 components.remove(0);
323 }
324
325 for (i, component) in components.iter().enumerate() {
327 let is_last = i == components.len() - 1;
328
329 if is_last {
330 let name = *component;
332 if name == "lib.rs" || name == "main.rs" || name == "mod.rs" {
333 continue;
335 }
336 if let Some(module_name) = name.strip_suffix(".rs") {
338 builder = builder.push(module_name);
339 }
340 } else {
341 builder = builder.push(*component);
343 }
344 }
345
346 builder.build()
347 }
348
349 pub fn from_workspace_file(path: &WorkspaceFilePath) -> Result<Self, ParseError> {
362 Self::from_file_path(path.crate_name(), path)
363 }
364
365 pub fn item_in_file(path: &WorkspaceFilePath, item_name: &str) -> Result<Self, ParseError> {
378 let module = Self::from_workspace_file(path)?;
379 let full_path = format!("{}::{}", module, item_name);
380 Self::parse(&full_path)
381 }
382
383 pub fn nested_in_file(path: &WorkspaceFilePath, segments: &[&str]) -> Result<Self, ParseError> {
396 let module = Self::from_workspace_file(path)?;
397 let mut full_path = module.to_string();
398 for seg in segments {
399 full_path.push_str("::");
400 full_path.push_str(seg);
401 }
402 Self::parse(&full_path)
403 }
404
405 pub fn module_path_str(path: &WorkspaceFilePath) -> String {
483 let crate_name = path.crate_name().to_module_name();
484 let relative = path.as_relative();
485 let path_str = relative.to_string_lossy();
486
487 let work_path = if let Some(src_idx) = path_str.find("/src/") {
489 &path_str[src_idx + 5..]
491 } else {
492 path_str.strip_prefix("src/").unwrap_or(&path_str)
494 };
495
496 let without_rs = work_path.strip_suffix(".rs").unwrap_or(work_path);
498
499 let module_part = without_rs.strip_suffix("/mod").unwrap_or(without_rs);
501
502 if module_part == "lib" {
504 return crate_name;
505 } else if module_part == "main" {
506 return format!("main::{}", crate_name);
508 }
509
510 let module_path = module_part.replace('/', "::");
512 format!("{}::{}", crate_name, module_path)
513 }
514
515 fn calculate_crate_root_depth(segments: &[Segment]) -> u8 {
527 assert!(
528 !segments.is_empty(),
529 "SymbolPath segments must not be empty"
530 );
531
532 let first = segments[0].name();
533 if first == "main" {
534 if segments.len() < 2 {
536 panic!(
537 "Invalid main symbol path: 'main' prefix requires crate name (e.g., main::my_app), got: main"
538 );
539 }
540 2
541 } else {
542 1
543 }
544 }
545
546 pub fn parse(s: &str) -> Result<Self, ParseError> {
552 if s.is_empty() {
553 return Err(ParseError::Empty);
554 }
555
556 let segments: Result<SmallVec<[Segment; 12]>, _> = split_respecting_angle_brackets(s)
557 .into_iter()
558 .map(Segment::new)
559 .collect();
560
561 let segments = segments?;
562
563 if segments.is_empty() {
564 return Err(ParseError::Empty);
565 }
566
567 let first = segments[0].name();
571 if matches!(first, "crate" | "self" | "super") {
572 return Err(ParseError::SemanticKeyword(first.to_string()));
573 }
574
575 if first == "main" && segments.len() < 2 {
577 return Err(ParseError::InvalidIdentifier(
578 "main:: prefix requires crate name (e.g., main::my_app)".to_string(),
579 ));
580 }
581
582 let crate_root_depth = Self::calculate_crate_root_depth(&segments);
583
584 Ok(Self {
585 segments,
586 crate_root_depth,
587 })
588 }
589
590 pub fn parse_validated(s: &str, registry: &crate::SymbolRegistry) -> Result<Self, ParseError> {
599 let path = Self::parse(s)?;
600
601 let crate_name = path.crate_name();
603 let crate_exists = registry.iter().any(|(_, p)| p.crate_name() == crate_name);
604
605 if !crate_exists {
606 let known: Vec<_> = registry
607 .iter()
608 .map(|(_, p)| p.crate_name())
609 .collect::<std::collections::HashSet<_>>()
610 .into_iter()
611 .collect();
612 return Err(ParseError::UnknownCrate {
613 path: s.to_string(),
614 crate_name: crate_name.to_string(),
615 known: known.join(", "),
616 });
617 }
618
619 Ok(path)
620 }
621
622 pub fn from_segments(
624 segments: impl IntoIterator<Item = impl AsRef<str>>,
625 ) -> Result<Self, ParseError> {
626 let segments: SmallVec<[Segment; 12]> = segments
627 .into_iter()
628 .map(|s| Segment::new(s))
629 .collect::<Result<_, _>>()?;
630
631 if segments.is_empty() {
632 return Err(ParseError::Empty);
633 }
634
635 let crate_root_depth = Self::calculate_crate_root_depth(&segments);
636
637 Ok(Self {
638 segments,
639 crate_root_depth,
640 })
641 }
642
643 pub fn builder(crate_name: impl Into<String>) -> SymbolPathBuilder {
656 SymbolPathBuilder::new(crate_name)
657 }
658
659 pub fn with_var_scope(&self, scope: VarScope, name: &str) -> Result<Self, ParseError> {
669 let mut segments = self.segments.clone();
670 segments.push(Segment::new_unchecked(scope.segment()));
671 segments.push(Segment::new(name)?);
672 Ok(Self {
673 segments,
674 crate_root_depth: self.crate_root_depth, })
676 }
677
678 #[inline]
682 pub fn crate_name(&self) -> &str {
683 self.segments[0].name()
684 }
685
686 #[inline]
700 pub fn is_main_symbol(&self) -> bool {
701 self.crate_name() == "main"
702 }
703
704 pub fn main_target_crate(&self) -> Option<&str> {
718 if self.is_main_symbol() && self.depth() >= 2 {
719 self.segment(1).map(|s| s.name())
720 } else {
721 None
722 }
723 }
724
725 pub fn to_lib_path(&self) -> Option<SymbolPath> {
737 if !self.is_main_symbol() || self.depth() < 2 {
738 return None;
739 }
740 let segments: SmallVec<[Segment; 12]> = self.segments[1..].iter().cloned().collect();
742 Some(Self {
743 segments,
744 crate_root_depth: 1, })
746 }
747
748 #[inline]
769 pub fn is_crate_root(&self) -> bool {
770 self.segments.len() == self.crate_root_depth as usize
771 }
772
773 #[inline]
786 pub fn actual_crate_name(&self) -> &str {
787 if self.crate_root_depth == 2 {
788 self.segments[1].name()
790 } else {
791 self.segments[0].name()
793 }
794 }
795
796 #[inline]
814 pub fn mod_path(&self) -> &[Segment] {
815 &self.segments[self.crate_root_depth as usize..]
816 }
817
818 #[inline]
820 pub fn depth(&self) -> usize {
821 self.segments.len()
822 }
823
824 pub fn segments(&self) -> impl Iterator<Item = &str> {
826 self.segments.iter().map(|s| s.name())
827 }
828
829 pub fn segment_refs(&self) -> &[Segment] {
831 &self.segments
832 }
833
834 pub fn segment(&self, index: usize) -> Option<&Segment> {
836 self.segments.get(index)
837 }
838
839 #[inline]
841 pub fn name(&self) -> &str {
842 self.segments.last().map(|s| s.name()).unwrap_or("")
843 }
844
845 pub fn parent(&self) -> Option<SymbolPath> {
855 if self.segments.len() <= 1 {
856 return None;
857 }
858 let mut segments = self.segments.clone();
859 segments.pop();
860 Some(Self {
861 segments,
862 crate_root_depth: self.crate_root_depth, })
864 }
865
866 pub fn is_ancestor_of(&self, other: &SymbolPath) -> bool {
868 if self.segments.len() >= other.segments.len() {
869 return false;
870 }
871 self.segments
872 .iter()
873 .zip(other.segments.iter())
874 .all(|(a, b)| a == b)
875 }
876
877 pub fn is_descendant_of(&self, other: &SymbolPath) -> bool {
879 other.is_ancestor_of(self)
880 }
881
882 #[inline]
884 pub fn same_crate(&self, other: &SymbolPath) -> bool {
885 self.crate_name() == other.crate_name()
886 }
887
888 pub fn common_ancestor(&self, other: &SymbolPath) -> Option<SymbolPath> {
890 let mut common: SmallVec<[Segment; 12]> = SmallVec::new();
891
892 for (a, b) in self.segments.iter().zip(other.segments.iter()) {
893 if a == b {
894 common.push(a.clone());
895 } else {
896 break;
897 }
898 }
899
900 if common.is_empty() {
901 None
902 } else {
903 let crate_root_depth = Self::calculate_crate_root_depth(&common);
904 Some(Self {
905 segments: common,
906 crate_root_depth,
907 })
908 }
909 }
910
911 pub fn child(&self, name: impl AsRef<str>) -> Result<SymbolPath, ParseError> {
921 let segment = Segment::new(name)?;
922 let mut segments = self.segments.clone();
923 segments.push(segment);
924 Ok(SymbolPath {
925 segments,
926 crate_root_depth: self.crate_root_depth, })
928 }
929
930 pub fn child_inherent_impl(&self, self_ty: &str) -> SymbolPath {
932 let mut segments = self.segments.clone();
933 segments.push(Segment::inherent_impl(self_ty));
934 SymbolPath {
935 segments,
936 crate_root_depth: self.crate_root_depth,
937 }
938 }
939
940 pub fn child_trait_impl(&self, trait_name: &str, self_ty: &str) -> SymbolPath {
942 let mut segments = self.segments.clone();
943 segments.push(Segment::trait_impl(trait_name, self_ty));
944 SymbolPath {
945 segments,
946 crate_root_depth: self.crate_root_depth,
947 }
948 }
949
950 pub fn module_path(&self) -> String {
956 if self.segments.len() <= 1 {
957 self.crate_name().to_string()
958 } else {
959 self.segments[..self.segments.len() - 1]
960 .iter()
961 .map(|s| s.name())
962 .collect::<Vec<_>>()
963 .join("::")
964 }
965 }
966
967 pub fn with_renamed_last_segment(&self, from: &str, to: &str) -> SymbolPath {
972 if self.name() != from {
973 return self.clone();
974 }
975
976 let mut segments = self.segments.clone();
977 if let Some(last) = segments.last_mut() {
978 *last = Segment::new_unchecked(to);
979 }
980 SymbolPath {
981 segments,
982 crate_root_depth: self.crate_root_depth, }
984 }
985
986 pub fn is_in_symbol(&self) -> bool {
990 self.segments.iter().any(|s| s.is_scope_marker())
991 }
992
993 pub fn containing_symbol(&self) -> Option<SymbolPath> {
997 let pos = self.segments.iter().position(|s| s.is_scope_marker())?;
998 if pos == 0 {
999 return None;
1000 }
1001 Some(Self {
1002 segments: self.segments[..pos].into(),
1003 crate_root_depth: self.crate_root_depth, })
1005 }
1006
1007 pub fn var_name(&self) -> Option<&str> {
1011 if !self.is_in_symbol() {
1012 return None;
1013 }
1014 self.segments.last().map(|s| s.name())
1015 }
1016
1017 pub fn var_scope(&self) -> Option<VarScope> {
1019 self.segments
1020 .iter()
1021 .find(|s| s.is_scope_marker())
1022 .and_then(|s| s.as_var_scope())
1023 }
1024
1025 pub fn has_body_segment(&self) -> bool {
1029 self.segments.iter().any(|s| s.is_body_scope())
1030 }
1031
1032 pub fn body_segment_index(&self) -> Option<usize> {
1034 self.segments.iter().position(|s| s.is_body_scope())
1035 }
1036
1037 pub fn body_indices(&self) -> Option<Vec<usize>> {
1049 let body_idx = self.body_segment_index()?;
1050 let indices: Option<Vec<usize>> = self.segments[body_idx + 1..]
1051 .iter()
1052 .map(|s| s.tuple_field_index())
1053 .collect();
1054 indices
1055 }
1056
1057 pub fn function_path(&self) -> Option<SymbolPath> {
1067 let body_idx = self.body_segment_index()?;
1068 if body_idx == 0 {
1069 return None;
1070 }
1071 let segments: SmallVec<[Segment; 12]> = self.segments[..body_idx].iter().cloned().collect();
1072 let crate_root_depth = Self::calculate_crate_root_depth(&segments);
1073 Some(SymbolPath {
1074 segments,
1075 crate_root_depth,
1076 })
1077 }
1078
1079 pub fn split_at_body(&self) -> Option<(SymbolPath, Vec<usize>)> {
1084 let fn_path = self.function_path()?;
1085 let indices = self.body_indices()?;
1086 Some((fn_path, indices))
1087 }
1088}
1089
1090impl Display for SymbolPath {
1091 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1092 let path = self
1093 .segments
1094 .iter()
1095 .map(|s| s.name())
1096 .collect::<Vec<_>>()
1097 .join("::");
1098 write!(f, "{}", path)
1099 }
1100}
1101
1102impl FromStr for SymbolPath {
1103 type Err = ParseError;
1104
1105 fn from_str(s: &str) -> Result<Self, Self::Err> {
1106 Self::parse(s)
1107 }
1108}
1109
1110pub struct SymbolPathBuilder {
1116 segments: SmallVec<[Segment; 12]>,
1117}
1118
1119impl SymbolPathBuilder {
1120 fn new(crate_name: impl Into<String>) -> Self {
1121 let mut segments = SmallVec::new();
1122 segments.push(Segment::new_unchecked(crate_name.into()));
1123 Self { segments }
1124 }
1125
1126 pub fn push(mut self, name: impl AsRef<str>) -> Self {
1128 self.segments.push(Segment::new_unchecked(name));
1129 self
1130 }
1131
1132 pub fn build(self) -> Result<SymbolPath, ParseError> {
1138 if self.segments.is_empty() {
1139 return Err(ParseError::Empty);
1140 }
1141
1142 for seg in &self.segments {
1144 validate_rust_identifier(seg.name())?;
1145 }
1146
1147 let crate_root_depth = SymbolPath::calculate_crate_root_depth(&self.segments);
1148
1149 Ok(SymbolPath {
1150 segments: self.segments,
1151 crate_root_depth,
1152 })
1153 }
1154}
1155
1156fn split_respecting_angle_brackets(s: &str) -> Vec<&str> {
1162 let mut segments = Vec::new();
1163 let mut depth = 0u32;
1164 let mut start = 0;
1165 let bytes = s.as_bytes();
1166 let len = bytes.len();
1167 let mut i = 0;
1168
1169 while i < len {
1170 match bytes[i] {
1171 b'<' => {
1172 depth += 1;
1173 i += 1;
1174 }
1175 b'>' => {
1176 depth = depth.saturating_sub(1);
1177 i += 1;
1178 }
1179 b':' if depth == 0 && i + 1 < len && bytes[i + 1] == b':' => {
1180 segments.push(&s[start..i]);
1181 i += 2; start = i;
1183 }
1184 _ => {
1185 i += 1;
1186 }
1187 }
1188 }
1189
1190 if start <= len {
1192 segments.push(&s[start..len]);
1193 }
1194
1195 segments
1196}
1197
1198#[cfg(test)]
1199mod tests {
1200 use super::*;
1201
1202 #[test]
1203 fn test_parse_simple() {
1204 let path = SymbolPath::parse("tokio::sync::Mutex").unwrap();
1205 assert_eq!(path.crate_name(), "tokio");
1206 assert_eq!(path.depth(), 3);
1207 assert_eq!(path.name(), "Mutex");
1208 assert_eq!(path.to_string(), "tokio::sync::Mutex");
1209 }
1210
1211 #[test]
1212 fn test_builder() {
1213 let path = SymbolPath::builder("tokio")
1214 .push("sync")
1215 .push("Mutex")
1216 .push("lock")
1217 .build()
1218 .unwrap();
1219
1220 assert_eq!(path.to_string(), "tokio::sync::Mutex::lock");
1221 }
1222
1223 #[test]
1224 fn test_parent() {
1225 let path = SymbolPath::parse("tokio::sync::Mutex::lock").unwrap();
1226 let parent = path.parent().unwrap();
1227 assert_eq!(parent.to_string(), "tokio::sync::Mutex");
1228
1229 let crate_path = SymbolPath::parse("tokio").unwrap();
1231 assert!(crate_path.parent().is_none());
1232 }
1233
1234 #[test]
1235 fn test_ancestor() {
1236 let parent = SymbolPath::parse("tokio::sync").unwrap();
1237 let child = SymbolPath::parse("tokio::sync::Mutex::lock").unwrap();
1238
1239 assert!(parent.is_ancestor_of(&child));
1240 assert!(child.is_descendant_of(&parent));
1241 assert!(!child.is_ancestor_of(&parent));
1242 }
1243
1244 #[test]
1245 fn test_common_ancestor() {
1246 let path1 = SymbolPath::parse("tokio::sync::Mutex::lock").unwrap();
1247 let path2 = SymbolPath::parse("tokio::sync::RwLock::read").unwrap();
1248
1249 let common = path1.common_ancestor(&path2).unwrap();
1250 assert_eq!(common.to_string(), "tokio::sync");
1251 }
1252
1253 #[test]
1254 fn test_tuple_field() {
1255 let seg = Segment::tuple_field(0);
1256 assert!(seg.is_tuple_field());
1257 assert_eq!(seg.tuple_field_index(), Some(0));
1258 }
1259
1260 #[test]
1261 fn test_in_symbol() {
1262 let path = SymbolPath::parse("my_crate::my_fn").unwrap();
1263 let var_path = path.with_var_scope(VarScope::Param, "x").unwrap();
1264
1265 assert!(var_path.is_in_symbol());
1266 assert_eq!(var_path.var_name(), Some("x"));
1267 assert_eq!(
1268 var_path.containing_symbol().unwrap().to_string(),
1269 "my_crate::my_fn"
1270 );
1271 }
1272
1273 #[test]
1274 fn test_from_file_path() {
1275 let crate_name = CrateName::new_for_test("my_crate");
1276
1277 let lib_path = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
1279 let sym = SymbolPath::from_file_path(&crate_name, &lib_path).unwrap();
1280 assert_eq!(sym.to_string(), "my_crate");
1281
1282 let mod_path = WorkspaceFilePath::new_for_test("src/foo/bar.rs", "/workspace", "my_crate");
1284 let sym = SymbolPath::from_file_path(&crate_name, &mod_path).unwrap();
1285 assert_eq!(sym.to_string(), "my_crate::foo::bar");
1286
1287 let mod_path = WorkspaceFilePath::new_for_test("src/foo/mod.rs", "/workspace", "my_crate");
1289 let sym = SymbolPath::from_file_path(&crate_name, &mod_path).unwrap();
1290 assert_eq!(sym.to_string(), "my_crate::foo");
1291 }
1292
1293 #[test]
1294 fn test_from_workspace_file() {
1295 let path = WorkspaceFilePath::new_for_test("src/foo/bar.rs", "/workspace", "my_crate");
1297 let sym = SymbolPath::from_workspace_file(&path).unwrap();
1298 assert_eq!(sym.to_string(), "my_crate::foo::bar");
1299
1300 let lib_path = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
1302 let sym = SymbolPath::from_workspace_file(&lib_path).unwrap();
1303 assert_eq!(sym.to_string(), "my_crate");
1304 }
1305
1306 #[test]
1307 fn test_item_in_file() {
1308 let path = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
1309 let sym = SymbolPath::item_in_file(&path, "MyStruct").unwrap();
1310 assert_eq!(sym.to_string(), "my_crate::MyStruct");
1311
1312 let path = WorkspaceFilePath::new_for_test("src/foo/bar.rs", "/workspace", "my_crate");
1313 let sym = SymbolPath::item_in_file(&path, "Baz").unwrap();
1314 assert_eq!(sym.to_string(), "my_crate::foo::bar::Baz");
1315 }
1316
1317 #[test]
1318 fn test_nested_in_file() {
1319 let path = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
1320 let sym = SymbolPath::nested_in_file(&path, &["Foo", "new"]).unwrap();
1321 assert_eq!(sym.to_string(), "my_crate::Foo::new");
1322 }
1323
1324 #[test]
1325 fn test_module_path_str() {
1326 let path = WorkspaceFilePath::new_for_test("src/foo/bar.rs", "/workspace", "my_crate");
1327 assert_eq!(SymbolPath::module_path_str(&path), "my_crate::foo::bar");
1328
1329 let lib_path = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
1330 assert_eq!(SymbolPath::module_path_str(&lib_path), "my_crate");
1331
1332 let mod_path = WorkspaceFilePath::new_for_test("src/foo/mod.rs", "/workspace", "my_crate");
1333 assert_eq!(SymbolPath::module_path_str(&mod_path), "my_crate::foo");
1334
1335 let workspace_path =
1337 WorkspaceFilePath::new_for_test("crates/mylib/src/lib.rs", "/workspace", "mylib");
1338 assert_eq!(SymbolPath::module_path_str(&workspace_path), "mylib");
1339
1340 let workspace_path2 =
1341 WorkspaceFilePath::new_for_test("crates/mylib/src/foo.rs", "/workspace", "mylib");
1342 assert_eq!(SymbolPath::module_path_str(&workspace_path2), "mylib::foo");
1343 }
1344
1345 #[test]
1346 fn test_from_file_path_workspace_pattern() {
1347 let path =
1349 WorkspaceFilePath::new_for_test("crates/mylib/src/lib.rs", "/workspace", "mylib");
1350 let symbol = SymbolPath::from_workspace_file(&path).unwrap();
1351 assert_eq!(symbol.to_string(), "mylib");
1352
1353 let path2 =
1354 WorkspaceFilePath::new_for_test("crates/mylib/src/foo.rs", "/workspace", "mylib");
1355 let symbol2 = SymbolPath::from_workspace_file(&path2).unwrap();
1356 assert_eq!(symbol2.to_string(), "mylib::foo");
1357
1358 let path3 =
1359 WorkspaceFilePath::new_for_test("crates/mylib/src/foo/bar.rs", "/workspace", "mylib");
1360 let symbol3 = SymbolPath::from_workspace_file(&path3).unwrap();
1361 assert_eq!(symbol3.to_string(), "mylib::foo::bar");
1362 }
1363
1364 #[test]
1365 fn test_main_symbol() {
1366 let main_path = SymbolPath::parse("main::my_crate::Config").unwrap();
1368 assert!(main_path.is_main_symbol());
1369 assert_eq!(main_path.main_target_crate(), Some("my_crate"));
1370
1371 let lib_path = SymbolPath::parse("my_crate::Config").unwrap();
1373 assert!(!lib_path.is_main_symbol());
1374 assert_eq!(lib_path.main_target_crate(), None);
1375
1376 let nested_main = SymbolPath::parse("main::my_crate::module::Foo").unwrap();
1378 assert!(nested_main.is_main_symbol());
1379 assert_eq!(nested_main.main_target_crate(), Some("my_crate"));
1380
1381 let just_main = SymbolPath::parse("main");
1383 assert!(just_main.is_err());
1384 }
1385
1386 #[test]
1387 fn test_to_lib_path() {
1388 let main_path = SymbolPath::parse("main::my_crate::Config").unwrap();
1390 let lib_path = main_path.to_lib_path().unwrap();
1391 assert_eq!(lib_path.to_string(), "my_crate::Config");
1392 assert!(!lib_path.is_main_symbol());
1393
1394 let nested = SymbolPath::parse("main::my_crate::module::Foo").unwrap();
1396 let lib = nested.to_lib_path().unwrap();
1397 assert_eq!(lib.to_string(), "my_crate::module::Foo");
1398
1399 let lib_path = SymbolPath::parse("my_crate::Config").unwrap();
1401 assert!(lib_path.to_lib_path().is_none());
1402
1403 let just_main = SymbolPath::parse("main");
1405 assert!(just_main.is_err());
1406 }
1407
1408 #[test]
1409 fn test_is_crate_root() {
1410 let lib_root = SymbolPath::parse("my_lib").unwrap();
1412 assert!(lib_root.is_crate_root());
1413
1414 let lib_mod = SymbolPath::parse("my_lib::utils").unwrap();
1415 assert!(!lib_mod.is_crate_root());
1416
1417 let lib_nested = SymbolPath::parse("my_lib::utils::Helper").unwrap();
1418 assert!(!lib_nested.is_crate_root());
1419
1420 let bin_root = SymbolPath::parse("main::my_app").unwrap();
1422 assert!(bin_root.is_crate_root());
1423
1424 let bin_mod = SymbolPath::parse("main::my_app::utils").unwrap();
1425 assert!(!bin_mod.is_crate_root());
1426
1427 let bin_nested = SymbolPath::parse("main::my_app::utils::Helper").unwrap();
1428 assert!(!bin_nested.is_crate_root());
1429 }
1430
1431 #[test]
1432 fn test_actual_crate_name() {
1433 let lib_path = SymbolPath::parse("my_lib::Config").unwrap();
1435 assert_eq!(lib_path.actual_crate_name(), "my_lib");
1436
1437 let bin_path = SymbolPath::parse("main::my_app::Config").unwrap();
1439 assert_eq!(bin_path.actual_crate_name(), "my_app");
1440
1441 let nested = SymbolPath::parse("main::my_app::utils::Helper").unwrap();
1443 assert_eq!(nested.actual_crate_name(), "my_app");
1444 }
1445
1446 #[test]
1447 fn test_mod_path() {
1448 let lib_root = SymbolPath::parse("my_lib").unwrap();
1450 assert_eq!(lib_root.mod_path().len(), 0);
1451
1452 let lib_mod = SymbolPath::parse("my_lib::utils").unwrap();
1454 assert_eq!(lib_mod.mod_path().len(), 1);
1455 assert_eq!(lib_mod.mod_path()[0].name(), "utils");
1456
1457 let lib_nested = SymbolPath::parse("my_lib::utils::Helper").unwrap();
1458 assert_eq!(lib_nested.mod_path().len(), 2);
1459 assert_eq!(lib_nested.mod_path()[0].name(), "utils");
1460 assert_eq!(lib_nested.mod_path()[1].name(), "Helper");
1461
1462 let bin_root = SymbolPath::parse("main::my_app").unwrap();
1464 assert_eq!(bin_root.mod_path().len(), 0);
1465
1466 let bin_mod = SymbolPath::parse("main::my_app::utils").unwrap();
1468 assert_eq!(bin_mod.mod_path().len(), 1);
1469 assert_eq!(bin_mod.mod_path()[0].name(), "utils");
1470
1471 let bin_nested = SymbolPath::parse("main::my_app::utils::Helper").unwrap();
1472 assert_eq!(bin_nested.mod_path().len(), 2);
1473 assert_eq!(bin_nested.mod_path()[0].name(), "utils");
1474 assert_eq!(bin_nested.mod_path()[1].name(), "Helper");
1475 }
1476
1477 #[test]
1478 fn test_reject_semantic_keywords() {
1479 let result = SymbolPath::parse("crate");
1483 assert!(matches!(result, Err(ParseError::SemanticKeyword(_))));
1484 assert!(
1485 result
1486 .unwrap_err()
1487 .to_string()
1488 .contains("semantic keyword 'crate' not allowed"),
1489 "Error message should mention semantic keyword"
1490 );
1491
1492 let result = SymbolPath::parse("crate::foo");
1494 assert!(matches!(result, Err(ParseError::SemanticKeyword(_))));
1495
1496 let result = SymbolPath::parse("self");
1498 assert!(matches!(result, Err(ParseError::SemanticKeyword(_))));
1499
1500 let result = SymbolPath::parse("self::foo");
1502 assert!(matches!(result, Err(ParseError::SemanticKeyword(_))));
1503
1504 let result = SymbolPath::parse("super");
1506 assert!(matches!(result, Err(ParseError::SemanticKeyword(_))));
1507
1508 let result = SymbolPath::parse("super::foo");
1510 assert!(matches!(result, Err(ParseError::SemanticKeyword(_))));
1511
1512 assert!(SymbolPath::parse("my_crate").is_ok());
1514 assert!(SymbolPath::parse("my_crate::foo").is_ok());
1515 assert!(SymbolPath::parse("tokio::net::TcpStream").is_ok());
1516 }
1517
1518 #[test]
1521 fn test_segment_inherent_impl() {
1522 let seg = Segment::inherent_impl("Counter");
1523 assert_eq!(seg.name(), "<impl Counter>");
1524 assert!(seg.is_impl());
1525 assert!(!seg.is_trait_impl());
1526 assert_eq!(seg.impl_self_ty(), Some("Counter"));
1527 assert_eq!(seg.impl_trait(), None);
1528 }
1529
1530 #[test]
1531 fn test_segment_trait_impl() {
1532 let seg = Segment::trait_impl("Display", "Counter");
1533 assert_eq!(seg.name(), "<impl Display for Counter>");
1534 assert!(seg.is_impl());
1535 assert!(seg.is_trait_impl());
1536 assert_eq!(seg.impl_self_ty(), Some("Counter"));
1537 assert_eq!(seg.impl_trait(), Some("Display"));
1538 }
1539
1540 #[test]
1541 fn test_segment_non_impl() {
1542 let seg = Segment::new("foo").unwrap();
1543 assert!(!seg.is_impl());
1544 assert!(!seg.is_trait_impl());
1545 assert_eq!(seg.impl_self_ty(), None);
1546 assert_eq!(seg.impl_trait(), None);
1547 }
1548
1549 #[test]
1550 fn test_segment_impl_validation_roundtrip() {
1551 let inherent = Segment::inherent_impl("MyStruct");
1553 assert!(Segment::new(inherent.name()).is_ok());
1554
1555 let trait_impl = Segment::trait_impl("Clone", "MyStruct");
1556 assert!(Segment::new(trait_impl.name()).is_ok());
1557 }
1558
1559 #[test]
1560 fn test_symbolpath_child_inherent_impl() {
1561 let module = SymbolPath::parse("my_crate::module").unwrap();
1562 let impl_path = module.child_inherent_impl("TodoList");
1563 assert_eq!(impl_path.to_string(), "my_crate::module::<impl TodoList>");
1564 assert_eq!(impl_path.depth(), 3);
1565
1566 let method = module.child("TodoList").unwrap().child("new").unwrap();
1568 assert_eq!(method.to_string(), "my_crate::module::TodoList::new");
1569 }
1570
1571 #[test]
1572 fn test_symbolpath_child_trait_impl() {
1573 let module = SymbolPath::parse("my_crate::module").unwrap();
1574 let impl_path = module.child_trait_impl("Display", "TodoList");
1575 assert_eq!(
1576 impl_path.to_string(),
1577 "my_crate::module::<impl Display for TodoList>"
1578 );
1579
1580 let method = impl_path.child("fmt").unwrap();
1582 assert_eq!(
1583 method.to_string(),
1584 "my_crate::module::<impl Display for TodoList>::fmt"
1585 );
1586 }
1587
1588 #[test]
1589 fn test_symbolpath_impl_parse_roundtrip() {
1590 let module = SymbolPath::parse("my_crate").unwrap();
1592 let impl_path = module.child_inherent_impl("Foo");
1593 let reparsed = SymbolPath::parse(&impl_path.to_string()).unwrap();
1594 assert_eq!(impl_path, reparsed);
1595
1596 let trait_path = module.child_trait_impl("Bar", "Foo");
1597 let reparsed = SymbolPath::parse(&trait_path.to_string()).unwrap();
1598 assert_eq!(trait_path, reparsed);
1599 }
1600
1601 #[test]
1602 fn test_impl_segment_last_segment_inspection() {
1603 let path = SymbolPath::parse("my_crate::module").unwrap();
1604 let impl_path = path.child_trait_impl("Iterator", "MyIter");
1605
1606 let last = impl_path.segment(impl_path.depth() - 1).unwrap();
1607 assert!(last.is_impl());
1608 assert!(last.is_trait_impl());
1609 assert_eq!(last.impl_self_ty(), Some("MyIter"));
1610 assert_eq!(last.impl_trait(), Some("Iterator"));
1611 }
1612
1613 #[test]
1614 fn test_impl_parent_navigation() {
1615 let path = SymbolPath::parse("my_crate").unwrap();
1616 let impl_path = path.child_trait_impl("Display", "Config");
1617 let method = impl_path.child("fmt").unwrap();
1618
1619 let parent = method.parent().unwrap();
1621 assert_eq!(parent, impl_path);
1622
1623 let grandparent = parent.parent().unwrap();
1625 assert_eq!(grandparent, path);
1626 }
1627
1628 #[test]
1629 fn test_parse_roundtrip_qualified_trait_impl() {
1630 let built = SymbolPath::parse("axum::helpers")
1634 .unwrap()
1635 .child_trait_impl("io::Write", "Writer < '_ >");
1636 let method = built.child("write").unwrap();
1637
1638 let method_str = method.to_string();
1640 assert_eq!(
1641 method_str,
1642 "axum::helpers::<impl io::Write for Writer < '_ >>::write"
1643 );
1644
1645 let reparsed = SymbolPath::parse(&method_str)
1647 .expect("parse must handle :: inside <impl ...> segments");
1648 assert_eq!(reparsed, method, "round-trip must preserve segments");
1649 assert_eq!(reparsed.name(), "write");
1650 assert_eq!(reparsed.depth(), 4); }
1652}