Skip to main content

shape_ast/ast/
type_path.rs

1//! Module-qualified type path for Shape AST
2//!
3//! `TypePath` represents a potentially module-qualified type reference as structured
4//! segments. For example, `foo::Bar` is represented as `["foo", "Bar"]` and plain
5//! `Bar` as `["Bar"]`.
6
7use serde::{Deserialize, Serialize};
8use std::borrow::Borrow;
9use std::fmt;
10use std::hash::{Hash, Hasher};
11use std::ops::Deref;
12
13/// A potentially module-qualified type reference.
14///
15/// Stores structured segments (e.g. `["foo", "Bar"]` for `foo::Bar`) along with
16/// a cached `qualified` string (`"foo::Bar"`).
17///
18/// Key trait impls make migration mechanical:
19/// - `Deref<Target=str>` returns `&self.qualified`
20/// - `Borrow<str>` enables `HashMap<String,..>::get(&type_path)`
21/// - `PartialEq<str>`, `PartialEq<&str>`, `PartialEq<String>` for comparisons
22/// - `From<String>`, `From<&str>` for construction
23/// - Serializes as plain string for backward compatibility
24#[derive(Clone, Debug)]
25pub struct TypePath {
26    segments: Vec<String>,
27    qualified: String,
28}
29
30impl TypePath {
31    /// Create a single-segment (unqualified) type path.
32    pub fn simple(name: impl Into<String>) -> Self {
33        let name = name.into();
34        TypePath {
35            segments: vec![name.clone()],
36            qualified: name,
37        }
38    }
39
40    /// Create a multi-segment (potentially qualified) type path.
41    pub fn from_segments(segments: Vec<String>) -> Self {
42        let qualified = segments.join("::");
43        TypePath {
44            segments,
45            qualified,
46        }
47    }
48
49    /// Create from a qualified string, splitting on `::`.
50    pub fn from_qualified(s: impl Into<String>) -> Self {
51        let s = s.into();
52        let segments: Vec<String> = s.split("::").map(|seg| seg.to_string()).collect();
53        TypePath {
54            segments,
55            qualified: s,
56        }
57    }
58
59    /// The type's own name (last segment).
60    pub fn name(&self) -> &str {
61        self.segments.last().map(|s| s.as_str()).unwrap_or("")
62    }
63
64    /// Module segments (everything before the last).
65    pub fn module_segments(&self) -> &[String] {
66        if self.segments.len() > 1 {
67            &self.segments[..self.segments.len() - 1]
68        } else {
69            &[]
70        }
71    }
72
73    /// Whether this path has more than one segment.
74    pub fn is_qualified(&self) -> bool {
75        self.segments.len() > 1
76    }
77
78    /// The full qualified string.
79    pub fn as_str(&self) -> &str {
80        &self.qualified
81    }
82
83    /// The individual segments.
84    pub fn segments(&self) -> &[String] {
85        &self.segments
86    }
87}
88
89// ---- Deref to &str ----
90
91impl Deref for TypePath {
92    type Target = str;
93    fn deref(&self) -> &str {
94        &self.qualified
95    }
96}
97
98impl Borrow<str> for TypePath {
99    fn borrow(&self) -> &str {
100        &self.qualified
101    }
102}
103
104impl AsRef<str> for TypePath {
105    fn as_ref(&self) -> &str {
106        &self.qualified
107    }
108}
109
110// ---- Equality / Hash (based on qualified string) ----
111
112impl PartialEq for TypePath {
113    fn eq(&self, other: &Self) -> bool {
114        self.qualified == other.qualified
115    }
116}
117
118impl Eq for TypePath {}
119
120impl Hash for TypePath {
121    fn hash<H: Hasher>(&self, state: &mut H) {
122        self.qualified.hash(state);
123    }
124}
125
126impl PartialEq<str> for TypePath {
127    fn eq(&self, other: &str) -> bool {
128        self.qualified == other
129    }
130}
131
132impl PartialEq<&str> for TypePath {
133    fn eq(&self, other: &&str) -> bool {
134        self.qualified.as_str() == *other
135    }
136}
137
138impl PartialEq<String> for TypePath {
139    fn eq(&self, other: &String) -> bool {
140        self.qualified == *other
141    }
142}
143
144impl PartialEq<TypePath> for str {
145    fn eq(&self, other: &TypePath) -> bool {
146        self == other.qualified
147    }
148}
149
150impl PartialEq<TypePath> for &str {
151    fn eq(&self, other: &TypePath) -> bool {
152        *self == other.qualified.as_str()
153    }
154}
155
156impl PartialEq<TypePath> for String {
157    fn eq(&self, other: &TypePath) -> bool {
158        *self == other.qualified
159    }
160}
161
162// ---- Display ----
163
164impl fmt::Display for TypePath {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        f.write_str(&self.qualified)
167    }
168}
169
170// ---- From conversions ----
171
172impl From<String> for TypePath {
173    fn from(s: String) -> Self {
174        TypePath::from_qualified(s)
175    }
176}
177
178impl From<&str> for TypePath {
179    fn from(s: &str) -> Self {
180        TypePath::from_qualified(s)
181    }
182}
183
184// ---- Serialize as plain string, Deserialize from plain string ----
185
186impl Serialize for TypePath {
187    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
188        self.qualified.serialize(serializer)
189    }
190}
191
192impl<'de> Deserialize<'de> for TypePath {
193    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
194        let s = String::deserialize(deserializer)?;
195        Ok(TypePath::from_qualified(s))
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_simple_path() {
205        let p = TypePath::simple("Foo");
206        assert_eq!(p.as_str(), "Foo");
207        assert_eq!(p.name(), "Foo");
208        assert!(!p.is_qualified());
209        assert!(p.module_segments().is_empty());
210    }
211
212    #[test]
213    fn test_qualified_path() {
214        let p = TypePath::from_segments(vec!["foo".into(), "Bar".into()]);
215        assert_eq!(p.as_str(), "foo::Bar");
216        assert_eq!(p.name(), "Bar");
217        assert!(p.is_qualified());
218        assert_eq!(p.module_segments(), &["foo".to_string()]);
219    }
220
221    #[test]
222    fn test_deeply_qualified() {
223        let p = TypePath::from_segments(vec!["a".into(), "b".into(), "C".into()]);
224        assert_eq!(p.as_str(), "a::b::C");
225        assert_eq!(p.name(), "C");
226        assert_eq!(p.module_segments(), &["a".to_string(), "b".to_string()]);
227    }
228
229    #[test]
230    fn test_deref_str() {
231        let p = TypePath::simple("Foo");
232        let s: &str = &p;
233        assert_eq!(s, "Foo");
234    }
235
236    #[test]
237    fn test_eq_str() {
238        let p = TypePath::simple("Foo");
239        assert!(p == "Foo");
240        assert!("Foo" == p);
241        assert!(p == "Foo".to_string());
242    }
243
244    #[test]
245    fn test_from_string() {
246        let p: TypePath = "foo::Bar".to_string().into();
247        assert!(p.is_qualified());
248        assert_eq!(p.name(), "Bar");
249    }
250
251    #[test]
252    fn test_from_str() {
253        let p: TypePath = "Baz".into();
254        assert!(!p.is_qualified());
255    }
256
257    #[test]
258    fn test_serde_roundtrip() {
259        let p = TypePath::from_segments(vec!["mod".into(), "Type".into()]);
260        let json = serde_json::to_string(&p).unwrap();
261        assert_eq!(json, "\"mod::Type\"");
262        let p2: TypePath = serde_json::from_str(&json).unwrap();
263        assert_eq!(p, p2);
264    }
265
266    #[test]
267    fn test_hashmap_lookup() {
268        use std::collections::HashMap;
269        let mut map: HashMap<String, i32> = HashMap::new();
270        map.insert("foo::Bar".to_string(), 42);
271        let p = TypePath::from_qualified("foo::Bar");
272        // Use Borrow<str> to look up
273        assert_eq!(map.get(p.as_str()), Some(&42));
274    }
275}