1use std::borrow::Cow;
86use std::fmt;
87use std::str::FromStr;
88use std::sync::Arc;
89
90use serde::{Deserialize, Deserializer, Serialize, Serializer};
91
92pub const SYM_PREFIX: &str = "sym:";
94
95const EXTERN: &str = "extern";
97
98#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
108pub struct SymbolId(Arc<str>);
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112enum Repr {
113 Global { package: Package, descriptors: Vec<Descriptor> },
114 Path(Vec<String>),
115 Unresolved(String),
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
130pub struct Package {
131 manager: String,
132 name: String,
133 version: Version,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138pub enum Version {
139 Current,
141 Release(String),
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
156pub struct Descriptor {
157 name: String,
158 suffix: Suffix,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
163pub enum Suffix {
164 Namespace,
166 Type,
168 Term,
170 Method {
173 disambiguator: Option<String>,
175 },
176 TypeParameter,
180 Parameter,
182 Meta,
184 Macro,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
190pub enum IdError {
191 #[error("symbol id must start with `sym:`")]
193 MissingPrefix,
194 #[error("invalid package manager `{0}`")]
196 Manager(String),
197 #[error("invalid package field `{0}`")]
200 Field(String),
201 #[error("invalid method disambiguator `{0}`")]
203 Disambiguator(String),
204 #[error("a path id needs at least one segment")]
206 EmptyPath,
207 #[error("malformed symbol id at byte {at}: {why}")]
209 Syntax {
210 at: usize,
212 why: &'static str,
214 },
215}
216
217impl Package {
220 pub fn new(manager: impl Into<String>, name: impl Into<String>, version: Version) -> Result<Self, IdError> {
222 let manager = manager.into();
223 let name = name.into();
224 check_manager(&manager)?;
225 check_field(&name)?;
226 if name == "." {
227 return Err(IdError::Field(name));
228 }
229 if let Version::Release(v) = &version {
230 check_field(v)?;
231 if v == "." {
232 return Err(IdError::Field(v.clone()));
233 }
234 }
235 Ok(Self { manager, name, version })
236 }
237
238 pub fn current(manager: impl Into<String>, name: impl Into<String>) -> Result<Self, IdError> {
240 Self::new(manager, name, Version::Current)
241 }
242
243 pub fn manager(&self) -> &str {
245 &self.manager
246 }
247
248 pub fn name(&self) -> &str {
250 &self.name
251 }
252
253 pub fn version(&self) -> &Version {
255 &self.version
256 }
257}
258
259fn check_manager(m: &str) -> Result<(), IdError> {
260 let mut cs = m.chars();
261 let ok = matches!(cs.next(), Some('a'..='z'))
262 && cs.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
263 && m != EXTERN;
264 if ok { Ok(()) } else { Err(IdError::Manager(m.to_owned())) }
265}
266
267fn check_field(f: &str) -> Result<(), IdError> {
268 let ok = !f.is_empty() && !f.starts_with(' ') && !f.ends_with(' ') && !f.chars().any(char::is_control);
269 if ok { Ok(()) } else { Err(IdError::Field(f.to_owned())) }
270}
271
272impl Descriptor {
275 pub fn new(name: impl Into<String>, suffix: Suffix) -> Result<Self, IdError> {
277 if let Suffix::Method { disambiguator: Some(d) } = &suffix {
278 if d.is_empty() || !d.chars().all(is_simple) {
279 return Err(IdError::Disambiguator(d.clone()));
280 }
281 }
282 Ok(Self { name: name.into(), suffix })
283 }
284
285 pub fn namespace(name: impl Into<String>) -> Self {
287 Self { name: name.into(), suffix: Suffix::Namespace }
288 }
289
290 pub fn r#type(name: impl Into<String>) -> Self {
292 Self { name: name.into(), suffix: Suffix::Type }
293 }
294
295 pub fn term(name: impl Into<String>) -> Self {
297 Self { name: name.into(), suffix: Suffix::Term }
298 }
299
300 pub fn method(name: impl Into<String>) -> Self {
302 Self { name: name.into(), suffix: Suffix::Method { disambiguator: None } }
303 }
304
305 pub fn type_parameter(name: impl Into<String>) -> Self {
307 Self { name: name.into(), suffix: Suffix::TypeParameter }
308 }
309
310 pub fn parameter(name: impl Into<String>) -> Self {
312 Self { name: name.into(), suffix: Suffix::Parameter }
313 }
314
315 pub fn meta(name: impl Into<String>) -> Self {
317 Self { name: name.into(), suffix: Suffix::Meta }
318 }
319
320 pub fn r#macro(name: impl Into<String>) -> Self {
322 Self { name: name.into(), suffix: Suffix::Macro }
323 }
324
325 pub fn name(&self) -> &str {
327 &self.name
328 }
329
330 pub fn suffix(&self) -> &Suffix {
332 &self.suffix
333 }
334
335 fn write(&self, out: &mut String) {
336 match &self.suffix {
337 Suffix::TypeParameter => {
338 out.push('[');
339 write_name(&self.name, out);
340 out.push(']');
341 }
342 Suffix::Parameter => {
343 out.push('(');
344 write_name(&self.name, out);
345 out.push(')');
346 }
347 other => {
348 write_name(&self.name, out);
349 match other {
350 Suffix::Namespace => out.push('/'),
351 Suffix::Type => out.push('#'),
352 Suffix::Term => out.push('.'),
353 Suffix::Meta => out.push(':'),
354 Suffix::Macro => out.push('!'),
355 Suffix::Method { disambiguator } => {
356 out.push('(');
357 out.push_str(disambiguator.as_deref().unwrap_or(""));
358 out.push_str(").");
359 }
360 Suffix::TypeParameter | Suffix::Parameter => unreachable!("handled above"),
361 }
362 }
363 }
364 }
365}
366
367fn is_simple(c: char) -> bool {
369 c.is_ascii_alphanumeric() || matches!(c, '_' | '+' | '-' | '$')
370}
371
372fn write_name(name: &str, out: &mut String) {
373 if !name.is_empty() && name.chars().all(is_simple) {
374 out.push_str(name);
375 return;
376 }
377 out.push('`');
378 for c in name.chars() {
379 if c == '`' {
380 out.push('`');
381 }
382 out.push(c);
383 }
384 out.push('`');
385}
386
387fn write_field(field: &str, out: &mut String) {
388 for c in field.chars() {
389 if c == ' ' {
390 out.push(' ');
391 }
392 out.push(c);
393 }
394}
395
396impl SymbolId {
399 fn from_repr(repr: &Repr) -> Self {
400 Self(print(repr).into())
401 }
402
403 pub fn global(package: Package, descriptors: Vec<Descriptor>) -> Self {
406 Self::from_repr(&Repr::Global { package, descriptors })
407 }
408
409 pub fn package_root(package: Package) -> Self {
411 Self::global(package, Vec::new())
412 }
413
414 pub fn path<I, S>(segments: I) -> Result<Self, IdError>
416 where
417 I: IntoIterator<Item = S>,
418 S: Into<String>,
419 {
420 let segs: Vec<String> = segments.into_iter().map(Into::into).collect();
421 if segs.is_empty() {
422 return Err(IdError::EmptyPath);
423 }
424 Ok(Self::from_repr(&Repr::Path(segs)))
425 }
426
427 pub fn unresolved(name: impl Into<String>) -> Self {
429 Self::from_repr(&Repr::Unresolved(name.into()))
430 }
431
432 pub fn parse(text: &str) -> Result<Self, IdError> {
437 let repr = Parser { s: text, pos: 0 }.id()?;
438 if print(&repr) != text {
441 return Err(IdError::Syntax { at: 0, why: "not in canonical form" });
442 }
443 Ok(Self(text.into()))
444 }
445
446 pub(crate) fn min_value() -> Self {
448 Self("".into())
449 }
450
451 pub fn as_str(&self) -> &str {
453 &self.0
454 }
455
456 fn repr(&self) -> Repr {
457 Parser { s: &self.0, pos: 0 }.id().expect("a SymbolId always holds canonical text")
458 }
459
460 fn shape(&self) -> Shape<'_> {
461 Shape::of(&self.0)
462 }
463
464 pub fn is_global(&self) -> bool {
466 matches!(self.shape(), Shape::Global { .. })
467 }
468
469 pub fn is_unresolved(&self) -> bool {
471 matches!(self.shape(), Shape::Unresolved(_))
472 }
473
474 pub fn package(&self) -> Option<Package> {
476 match self.repr() {
477 Repr::Global { package, .. } => Some(package),
478 _ => None,
479 }
480 }
481
482 pub fn descriptors(&self) -> Vec<Descriptor> {
484 match self.repr() {
485 Repr::Global { descriptors, .. } => descriptors,
486 _ => Vec::new(),
487 }
488 }
489
490 pub fn segments(&self) -> Option<Vec<String>> {
492 match self.repr() {
493 Repr::Path(s) => Some(s),
494 _ => None,
495 }
496 }
497
498 pub fn last(&self) -> Option<Descriptor> {
500 self.descriptors().pop()
501 }
502
503 pub fn name(&self) -> Cow<'_, str> {
512 match self.shape() {
513 Shape::Global { name, descriptors, .. } => {
514 scan_descriptors(descriptors).last().map_or_else(|| unfield(name), |d| unquote(d.name))
515 }
516 Shape::Path(body) => scan_path(body).last().map_or(Cow::Borrowed(""), |s| unquote(s)),
517 Shape::Unresolved(raw) => unquote(raw),
518 }
519 }
520
521 pub fn root(&self) -> Option<Cow<'_, str>> {
524 match self.shape() {
525 Shape::Global { name, .. } => Some(unfield(name)),
526 Shape::Path(body) => scan_path(body).first().map(|s| unquote(s)),
527 Shape::Unresolved(_) => None,
528 }
529 }
530
531 pub fn parent(&self) -> Option<SymbolId> {
545 let text: &str = &self.0;
546 match self.shape() {
547 Shape::Global { descriptors, .. } => {
548 let mut spans = scan_descriptors(descriptors);
549 spans.pop()?;
550 while spans.last().is_some_and(|d| d.kind == b'[') {
551 spans.pop();
552 }
553 let base = text.len() - descriptors.len();
554 let end = match spans.last() {
555 Some(d) => base + d.end,
556 None => base - 1,
558 };
559 Some(Self(text[..end].into()))
560 }
561 Shape::Path(body) => {
562 let segs = scan_path(body);
563 if segs.len() < 2 {
564 return None;
565 }
566 let last = segs[segs.len() - 1];
567 let end = text.len() - last.len() - "::".len();
568 Some(Self(text[..end].into()))
569 }
570 Shape::Unresolved(_) => None,
571 }
572 }
573
574 pub fn child(&self, descriptor: Descriptor) -> Option<SymbolId> {
577 let Shape::Global { descriptors, .. } = self.shape() else { return None };
578 let mut out = String::with_capacity(self.0.len() + descriptor.name.len() + 4);
579 out.push_str(&self.0);
580 if descriptors.is_empty() {
581 out.push(' ');
582 }
583 descriptor.write(&mut out);
584 Some(Self(out.into()))
585 }
586
587 pub fn names(&self) -> Vec<Cow<'_, str>> {
590 match self.shape() {
591 Shape::Global { name, descriptors, .. } => std::iter::once(unfield(name))
592 .chain(scan_descriptors(descriptors).into_iter().map(|d| unquote(d.name)))
593 .collect(),
594 Shape::Path(body) => scan_path(body).into_iter().map(unquote).collect(),
595 Shape::Unresolved(raw) => vec![unquote(raw)],
596 }
597 }
598
599 pub fn extend_path(&self, segment: &str) -> SymbolId {
610 let mut s: Vec<String> = self.names().into_iter().map(Cow::into_owned).collect();
611 s.push(segment.to_owned());
612 Self::from_repr(&Repr::Path(s))
613 }
614
615 pub fn view(&self) -> IdView<'_> {
635 match self.shape() {
636 Shape::Global { manager, name, version, descriptors } => IdView::Global(GlobalView {
637 manager,
638 package: unfield(name),
639 version: (version != ".").then(|| unfield(version)),
640 descriptors: scan_descriptors(descriptors)
641 .into_iter()
642 .map(|d| DescriptorView {
643 name: unquote(d.name),
644 kind: match d.kind {
645 b'/' => DescriptorKind::Namespace,
646 b'#' => DescriptorKind::Type,
647 b'(' => DescriptorKind::Method,
648 b'[' => DescriptorKind::TypeParameter,
649 b')' => DescriptorKind::Parameter,
650 b':' => DescriptorKind::Meta,
651 b'!' => DescriptorKind::Macro,
652 _ => DescriptorKind::Term,
653 },
654 disambiguator: (!d.disambiguator.is_empty()).then_some(d.disambiguator),
655 })
656 .collect(),
657 }),
658 Shape::Path(body) => IdView::Path(scan_path(body).into_iter().map(unquote).collect()),
659 Shape::Unresolved(raw) => IdView::Unresolved(unquote(raw)),
660 }
661 }
662
663 pub fn display_path(&self) -> String {
666 self.names().join("::")
667 }
668}
669
670impl fmt::Debug for SymbolId {
671 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672 write!(f, "SymbolId({:?})", &*self.0)
673 }
674}
675
676impl fmt::Display for SymbolId {
677 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678 f.write_str(&self.0)
679 }
680}
681
682impl FromStr for SymbolId {
683 type Err = IdError;
684 fn from_str(s: &str) -> Result<Self, Self::Err> {
685 Self::parse(s)
686 }
687}
688
689impl Serialize for SymbolId {
690 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
691 serializer.serialize_str(&self.0)
692 }
693}
694
695impl<'de> Deserialize<'de> for SymbolId {
696 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
697 let s = String::deserialize(deserializer)?;
698 Self::parse(&s).map_err(serde::de::Error::custom)
699 }
700}
701
702fn print(repr: &Repr) -> String {
704 let mut out = String::from(SYM_PREFIX);
705 match repr {
706 Repr::Global { package, descriptors } => {
707 out.push_str(&package.manager);
708 out.push(' ');
709 write_field(&package.name, &mut out);
710 out.push(' ');
711 match &package.version {
712 Version::Current => out.push('.'),
713 Version::Release(v) => write_field(v, &mut out),
714 }
715 if !descriptors.is_empty() {
716 out.push(' ');
717 for d in descriptors {
718 d.write(&mut out);
719 }
720 }
721 }
722 Repr::Path(segs) => {
723 out.push_str(EXTERN);
724 out.push(' ');
725 for (i, s) in segs.iter().enumerate() {
726 if i > 0 {
727 out.push_str("::");
728 }
729 write_name(s, &mut out);
730 }
731 }
732 Repr::Unresolved(n) => {
733 out.push_str("? ");
734 write_name(n, &mut out);
735 }
736 }
737 out
738}
739
740#[derive(Debug, Clone, PartialEq, Eq)]
742pub enum IdView<'a> {
743 Global(GlobalView<'a>),
745 Path(Vec<Cow<'a, str>>),
747 Unresolved(Cow<'a, str>),
749}
750
751#[derive(Debug, Clone, PartialEq, Eq)]
753pub struct GlobalView<'a> {
754 pub manager: &'a str,
756 pub package: Cow<'a, str>,
758 pub version: Option<Cow<'a, str>>,
760 pub descriptors: Vec<DescriptorView<'a>>,
762}
763
764#[derive(Debug, Clone, PartialEq, Eq)]
766pub struct DescriptorView<'a> {
767 pub name: Cow<'a, str>,
769 pub kind: DescriptorKind,
771 pub disambiguator: Option<&'a str>,
773}
774
775#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
777pub enum DescriptorKind {
778 Namespace,
780 Type,
782 Term,
784 Method,
786 TypeParameter,
788 Parameter,
790 Meta,
792 Macro,
794}
795
796enum Shape<'a> {
803 Global { manager: &'a str, name: &'a str, version: &'a str, descriptors: &'a str },
807 Path(&'a str),
809 Unresolved(&'a str),
811}
812
813impl<'a> Shape<'a> {
814 fn of(text: &'a str) -> Self {
815 let body = text.strip_prefix(SYM_PREFIX).unwrap_or(text);
816 if let Some(raw) = body.strip_prefix("? ") {
817 return Shape::Unresolved(raw);
818 }
819 if let Some(raw) = body.strip_prefix("extern ") {
820 return Shape::Path(raw);
821 }
822 let bytes = body.as_bytes();
825 let mut seps = Vec::with_capacity(3);
826 let mut i = 0;
827 while i < bytes.len() && seps.len() < 3 {
828 if bytes[i] == b' ' {
829 let run = bytes[i..].iter().take_while(|&&b| b == b' ').count();
830 if run % 2 == 1 {
831 seps.push(i + run - 1);
832 }
833 i += run;
834 } else {
835 i += 1;
836 }
837 }
838 let (a, b) = match (seps.first(), seps.get(1)) {
839 (Some(&a), Some(&b)) => (a, b),
840 _ => (0, 0),
841 };
842 let manager = &body[..a];
843 let name = body.get(a + 1..b).unwrap_or("");
844 let version = seps.get(2).map_or(body.get(b + 1..).unwrap_or(""), |&c| &body[b + 1..c]);
845 let descriptors = seps.get(2).map_or("", |&c| &body[c + 1..]);
846 Shape::Global { manager, name, version, descriptors }
847 }
848}
849
850struct DescSpan<'a> {
853 end: usize,
854 name: &'a str,
855 kind: u8,
858 disambiguator: &'a str,
860}
861
862fn raw_name_len(s: &str) -> usize {
864 let b = s.as_bytes();
865 if b.first() == Some(&b'`') {
866 let mut i = 1;
867 while i < b.len() {
868 if b[i] == b'`' {
869 if b.get(i + 1) == Some(&b'`') {
870 i += 2;
871 continue;
872 }
873 return i + 1;
874 }
875 i += 1;
876 }
877 return b.len();
878 }
879 s.char_indices().find(|&(_, c)| !is_simple(c)).map_or(s.len(), |(i, _)| i)
880}
881
882fn scan_descriptors(s: &str) -> Vec<DescSpan<'_>> {
883 let mut out = Vec::new();
884 let mut pos = 0;
885 while pos < s.len() {
886 let rest = &s[pos..];
887 let (kind, name, len, disambiguator) = match rest.as_bytes()[0] {
888 open @ (b'[' | b'(') => {
889 let n = raw_name_len(&rest[1..]);
890 (if open == b'(' { b')' } else { b'[' }, &rest[1..1 + n], n + 2, "")
891 }
892 _ => {
893 let n = raw_name_len(rest);
894 let kind = rest.as_bytes().get(n).copied().unwrap_or(b'.');
895 if kind == b'(' {
896 let close = rest[n..].find(").").unwrap_or(rest.len() - n);
898 (kind, &rest[..n], n + close + 2, rest.get(n + 1..n + close).unwrap_or(""))
899 } else {
900 (kind, &rest[..n], n + 1, "")
901 }
902 }
903 };
904 pos += len.max(1);
905 out.push(DescSpan { end: pos.min(s.len()), name, kind, disambiguator });
906 }
907 out
908}
909
910fn scan_path(s: &str) -> Vec<&str> {
911 let mut out = Vec::new();
912 let mut pos = 0;
913 loop {
914 let n = raw_name_len(&s[pos..]);
915 out.push(&s[pos..pos + n]);
916 pos += n;
917 if s[pos..].starts_with("::") {
918 pos += 2;
919 } else {
920 return out;
921 }
922 }
923}
924
925fn unquote(raw: &str) -> Cow<'_, str> {
927 match raw.strip_prefix('`').and_then(|r| r.strip_suffix('`')) {
928 Some(inner) if inner.contains("``") => Cow::Owned(inner.replace("``", "`")),
929 Some(inner) => Cow::Borrowed(inner),
930 None => Cow::Borrowed(raw),
931 }
932}
933
934fn unfield(raw: &str) -> Cow<'_, str> {
936 if raw.contains(" ") { Cow::Owned(raw.replace(" ", " ")) } else { Cow::Borrowed(raw) }
937}
938
939struct Parser<'a> {
942 s: &'a str,
943 pos: usize,
944}
945
946impl Parser<'_> {
947 fn err(&self, why: &'static str) -> IdError {
948 IdError::Syntax { at: self.pos, why }
949 }
950
951 fn rest(&self) -> &str {
952 &self.s[self.pos..]
953 }
954
955 fn peek(&self) -> Option<char> {
956 self.rest().chars().next()
957 }
958
959 fn eat(&mut self, lit: &str) -> bool {
960 if self.rest().starts_with(lit) {
961 self.pos += lit.len();
962 true
963 } else {
964 false
965 }
966 }
967
968 fn expect(&mut self, lit: &str, why: &'static str) -> Result<(), IdError> {
969 if self.eat(lit) { Ok(()) } else { Err(self.err(why)) }
970 }
971
972 fn done(&self) -> Result<(), IdError> {
973 if self.pos == self.s.len() { Ok(()) } else { Err(self.err("unexpected trailing text")) }
974 }
975
976 fn id(mut self) -> Result<Repr, IdError> {
977 if !self.eat(SYM_PREFIX) {
978 return Err(IdError::MissingPrefix);
979 }
980 if self.eat("? ") {
981 let name = self.name()?;
982 self.done()?;
983 return Ok(Repr::Unresolved(name));
984 }
985 if self.eat("extern ") {
986 let mut segs = vec![self.name()?];
987 while self.eat("::") {
988 segs.push(self.name()?);
989 }
990 self.done()?;
991 return Ok(Repr::Path(segs));
992 }
993 let manager = self.field()?;
994 self.expect(" ", "expected a space after the manager")?;
995 let name = self.field()?;
996 self.expect(" ", "expected a space after the package name")?;
997 let version = self.field()?;
998 let version = if version == "." { Version::Current } else { Version::Release(version) };
999 let package = Package::new(manager, name, version)?;
1000 let mut descriptors = Vec::new();
1001 if self.pos < self.s.len() {
1002 self.expect(" ", "expected a space before the descriptors")?;
1003 if self.pos == self.s.len() {
1004 return Err(self.err("expected a descriptor"));
1005 }
1006 while self.pos < self.s.len() {
1007 descriptors.push(self.descriptor()?);
1008 }
1009 }
1010 Ok(Repr::Global { package, descriptors })
1011 }
1012
1013 fn field(&mut self) -> Result<String, IdError> {
1016 let mut out = String::new();
1017 loop {
1018 let rest = self.rest();
1019 if rest.starts_with(" ") {
1020 out.push(' ');
1021 self.pos += 2;
1022 continue;
1023 }
1024 match rest.chars().next() {
1025 None | Some(' ') => break,
1026 Some(c) => {
1027 out.push(c);
1028 self.pos += c.len_utf8();
1029 }
1030 }
1031 }
1032 if out.is_empty() {
1033 return Err(self.err("empty package field"));
1034 }
1035 Ok(out)
1036 }
1037
1038 fn name(&mut self) -> Result<String, IdError> {
1039 if self.eat("`") {
1040 let mut out = String::new();
1041 loop {
1042 match self.peek() {
1043 None => return Err(self.err("unterminated quoted name")),
1044 Some('`') => {
1045 if self.rest().starts_with("``") {
1046 out.push('`');
1047 self.pos += 2;
1048 } else {
1049 if !out.is_empty() && out.chars().all(is_simple) {
1050 return Err(self.err("a simple name must not be quoted"));
1051 }
1052 self.pos += 1;
1053 return Ok(out);
1054 }
1055 }
1056 Some(c) => {
1057 out.push(c);
1058 self.pos += c.len_utf8();
1059 }
1060 }
1061 }
1062 }
1063 let start = self.pos;
1064 while self.peek().is_some_and(is_simple) {
1065 self.pos += 1;
1066 }
1067 if self.pos == start {
1068 return Err(self.err("expected a name"));
1069 }
1070 Ok(self.s[start..self.pos].to_owned())
1071 }
1072
1073 fn descriptor(&mut self) -> Result<Descriptor, IdError> {
1074 if self.eat("[") {
1075 let name = self.name()?;
1076 self.expect("]", "expected `]`")?;
1077 return Ok(Descriptor::type_parameter(name));
1078 }
1079 if self.eat("(") {
1080 let name = self.name()?;
1081 self.expect(")", "expected `)`")?;
1082 return Ok(Descriptor::parameter(name));
1083 }
1084 let name = self.name()?;
1085 let suffix = match self.peek() {
1086 Some('/') => Suffix::Namespace,
1087 Some('#') => Suffix::Type,
1088 Some('.') => Suffix::Term,
1089 Some(':') => Suffix::Meta,
1090 Some('!') => Suffix::Macro,
1091 Some('(') => {
1092 self.pos += 1;
1093 let start = self.pos;
1094 while self.peek().is_some_and(is_simple) {
1095 self.pos += 1;
1096 }
1097 let d = &self.s[start..self.pos];
1098 let disambiguator = (!d.is_empty()).then(|| d.to_owned());
1099 self.expect(").", "expected `).`")?;
1100 return Descriptor::new(name, Suffix::Method { disambiguator });
1101 }
1102 _ => return Err(self.err("expected a descriptor suffix")),
1103 };
1104 self.pos += 1;
1105 Ok(Descriptor { name, suffix })
1106 }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111 use super::*;
1112
1113 fn rt(s: &str) {
1114 let id = SymbolId::parse(s).unwrap_or_else(|e| panic!("{s}: {e}"));
1115 assert_eq!(id.to_string(), s);
1116 }
1117
1118 #[test]
1119 fn round_trips_every_form() {
1120 rt("sym:cargo a .");
1121 rt("sym:cargo a . b/");
1122 rt("sym:cargo a . b/C#d().");
1123 rt("sym:cargo a 1.2.0 b/C#d(+1).");
1124 rt("sym:cargo a . T#[`From<a::B>`]from().");
1125 rt("sym:npm @x/y . index/`POST /api`().");
1126 rt("sym:npm a b . c/");
1127 rt("sym:cargo a . f().(x)[T]m:k!v.");
1128 rt("sym:cargo a . ``#```a``b```.");
1129 rt("sym:extern tokio::spawn");
1130 rt("sym:extern `?`::`a b`");
1131 rt("sym:? insert");
1132 rt("sym:? ``");
1133 }
1134
1135 #[test]
1136 fn rejects_malformed() {
1137 for bad in [
1138 "",
1139 "cargo a .",
1140 "sym:",
1141 "sym:cargo",
1142 "sym:cargo a",
1143 "sym:cargo a . ",
1144 "sym:Cargo a .",
1145 "sym:extern",
1146 "sym:extern a::",
1147 "sym:cargo a . b",
1148 "sym:cargo a . `b",
1149 "sym:cargo a . b(x y).",
1150 "sym:cargo a . b().c",
1151 "sym:cargo . .",
1152 "sym:cargo a .",
1153 "sym:? a b",
1154 "sym:? `a`",
1155 ] {
1156 assert!(SymbolId::parse(bad).is_err(), "accepted {bad:?}");
1157 }
1158 }
1159
1160 #[test]
1161 fn json_is_the_canonical_string() {
1162 let id = SymbolId::parse("sym:cargo a . b/C#").unwrap();
1163 let json = serde_json::to_string(&id).unwrap();
1164 assert_eq!(json, "\"sym:cargo a . b/C#\"");
1165 assert_eq!(serde_json::from_str::<SymbolId>(&json).unwrap(), id);
1166 assert!(serde_json::from_str::<SymbolId>("\"a::b\"").is_err());
1167 }
1168}