prebindgen_flat/flat/key.rs
1//! The canonical identity of a type: its normalized token string.
2
3use std::fmt;
4
5use quote::ToTokens;
6
7/// Canonical type-shape key: identity is the token string of the
8/// **normalized** type. Normalization is a closed rule set — group/paren
9/// unwrap, a `crate::`/`self::`/source-module path reduced to its final
10/// segment, and a prelude path read as the bare name the language knows it
11/// by (`std::vec::Vec<Foo>` ≡ `Vec<Foo>`) — and any spelling it does not
12/// cover is kept verbatim.
13///
14/// # A key is an identity, and nothing else
15///
16/// It is what a table is indexed by. It is **not** a route to `syn::Type`: the
17/// only way to reach a type's syntax is
18/// `Conversions::reading` (in the registry layer above) followed by
19/// [`TypeRef`](super::TypeRef), because a
20/// reading is what pairs a spelling with the classification that vouches for
21/// it.
22///
23/// This used to keep the parsed form beside the string and hand it out through
24/// `to_type()`, which let any holder of a key produce tokens for a type the
25/// model never classified — the same capability #280 sealed `TypeRef` against,
26/// granted by the key itself. A caller that wants tokens now has to have gotten
27/// them from somewhere that knows what they mean: the registry's reading, or
28/// the declaration that wrote them (#291).
29///
30/// What a key can still answer about itself is what it is **called** —
31/// [`Self::as_str`], [`Self::ident`], [`Self::short_name`] — because a name is
32/// not syntax.
33#[derive(Clone)]
34pub struct TypeKey {
35 /// Canonical token string — the identity `Eq`/`Hash` compare, and the whole
36 /// of what a key is.
37 canon: std::rc::Rc<str>,
38}
39
40impl PartialEq for TypeKey {
41 fn eq(&self, other: &Self) -> bool {
42 self.canon == other.canon
43 }
44}
45impl Eq for TypeKey {}
46impl std::hash::Hash for TypeKey {
47 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
48 self.canon.hash(state)
49 }
50}
51impl PartialOrd for TypeKey {
52 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
53 Some(self.cmp(other))
54 }
55}
56impl Ord for TypeKey {
57 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
58 self.canon.cmp(&other.canon)
59 }
60}
61// Keep the historical single-field tuple rendering (`TypeKey("Vec < u8 >")`)
62// — error text and test expectations format keys through it.
63impl fmt::Debug for TypeKey {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.debug_tuple("TypeKey").field(&&*self.canon).finish()
66 }
67}
68
69/// Structured failure of [`TypeKey::parse`]: the offending input plus the
70/// underlying `syn` parse error.
71#[derive(Debug)]
72pub struct TypeKeyParseError {
73 pub input: String,
74 pub error: syn::Error,
75}
76
77impl fmt::Display for TypeKeyParseError {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "invalid type `{}`: {}", self.input, self.error)
80 }
81}
82
83impl std::error::Error for TypeKeyParseError {}
84
85impl TypeKey {
86 /// Build a key by parsing the input as a type and normalizing.
87 ///
88 /// The parse is kept for **validation** and then discarded: a key that
89 /// cannot be a type is a mistake worth reporting at the declaration, and a
90 /// key that can is still only its canonical string.
91 pub fn parse(s: &str) -> Result<Self, TypeKeyParseError> {
92 let ty: syn::Type = syn::parse_str(s).map_err(|error| TypeKeyParseError {
93 input: s.to_string(),
94 error,
95 })?;
96 Ok(Self::from_type(&ty))
97 }
98
99 /// Build a key directly from a `syn::Type` (normalizing a clone; the
100 /// input is not modified).
101 pub fn from_type(ty: &syn::Type) -> Self {
102 // Off the shared reduction, so this key and the model's type index
103 // cannot drift apart about what a type is called.
104 Self {
105 canon: crate::flat::canonical_type(ty)
106 .to_token_stream()
107 .to_string()
108 .into(),
109 }
110 }
111
112 /// Build a key for a bare item ident — infallible by construction (an
113 /// ident IS a single-segment path type; nothing to parse or normalize).
114 pub fn from_ident(ident: &syn::Ident) -> Self {
115 Self::from_type(&syn::parse_quote!(#ident))
116 }
117
118 /// The canonical string form.
119 pub fn as_str(&self) -> &str {
120 &self.canon
121 }
122
123 /// The bare item ident this key names — `Foo`, `a::Foo` → `Foo`,
124 /// `a::Foo<u8>::Bar` → `Bar`; `None` when the **last** segment carries
125 /// generic arguments (`Vec<u8>` names no bare item) or the key is not a
126 /// path.
127 /// Matches [`bare_path_ident`](crate::types_util::bare_path_ident) on the
128 /// same type — a correspondence this crate's tests pin.
129 ///
130 /// **A name is not syntax**, which is why this is the key's business and
131 /// producing a `syn::Type` is not. A caller that wants to look a declared
132 /// item up by name was never asking for tokens; it was asking the key what
133 /// it is called (#291).
134 pub fn ident(&self) -> Option<syn::Ident> {
135 let (ident, generic) = self.path_segments()?.pop()?;
136 // `bare_path_ident` reads `PathArguments` on the LAST segment only, so
137 // arguments earlier in the path do not disqualify the name.
138 if generic {
139 return None;
140 }
141 Some(ident)
142 }
143
144 /// The last path segment's ident, **ignoring** its generic arguments —
145 /// `Publisher<'static>` → `"Publisher"`, `a::Foo<u8>::Bar` → `"Bar"`.
146 /// `None` for anything that is not a path.
147 ///
148 /// The looser sibling of [`Self::ident`], for the callers that derive a
149 /// destination-language class name from a Rust type: a declaration writes
150 /// `ptr_class!(Publisher<'static>)` and means the class `Publisher`.
151 pub fn short_name(&self) -> Option<String> {
152 Some(self.path_segments()?.pop()?.0.to_string())
153 }
154
155 /// The **top-level** path segments of the canonical string: each segment's
156 /// ident, and whether that segment carried generic arguments. `None` if the
157 /// canon is not a path at all.
158 ///
159 /// # Read off the canonical string
160 ///
161 /// Deliberately, and not as a shortcut. `canon` is a token-stream
162 /// rendering, so its tokens are space-separated — `Vec < u8 >`, `& Foo`,
163 /// `a :: Foo` — and the structure is recoverable by tracking angle depth.
164 /// Reparsing the whole type instead would make a NAME depend on a
165 /// serialize-then-reparse round trip, which is the dependency #95 removed;
166 /// storing the derived names on the key would put derived state back on a
167 /// value whose whole point is that it carries none.
168 ///
169 /// **Nesting-aware, because a path segment is not the last thing before a
170 /// `<`.** `a::Foo<u8>::Bar` names `Bar`, and `Vec<a::B>` names `Vec` — the
171 /// `::` in the second belongs to the argument. Splitting at the first `<`
172 /// got the second right and the first wrong.
173 ///
174 /// `syn::parse_str::<syn::Ident>` on every segment is the totality check:
175 /// each non-path shape puts something in a segment that is not an ident —
176 /// `& Foo`, `[u8 ; 4]`, `( )`, `* const u8`, `dyn Error`, `fn () -> u8`.
177 fn path_segments(&self) -> Option<Vec<(syn::Ident, bool)>> {
178 let mut rest: &str = &self.canon;
179 // A qualified-self path renders its qualification first
180 // (`< T as Tr > :: Item`) and syn keeps only the tail in
181 // `path.segments` — so drop the group and read the rest as a plain path.
182 if rest.starts_with('<') {
183 rest = rest[close_angle(rest)? + 1..]
184 .trim_start()
185 .strip_prefix("::")?;
186 }
187
188 let bytes = rest.as_bytes();
189 let mut out = Vec::new();
190 let mut depth = 0usize;
191 let mut start = 0usize;
192 let mut ident_end: Option<usize> = None;
193 let mut i = 0usize;
194 while i < bytes.len() {
195 match bytes[i] {
196 b'<' => {
197 if depth == 0 && ident_end.is_none() {
198 ident_end = Some(i);
199 }
200 depth += 1;
201 }
202 // The `>` of a bare fn's `->` is an arrow, not a bracket, and
203 // miscounting it would let a `::` inside `Vec<fn() -> a::B>`
204 // read as a top-level separator.
205 b'>' if i > 0 && bytes[i - 1] == b'-' => {}
206 b'>' => depth = depth.saturating_sub(1),
207 b':' if depth == 0 && bytes.get(i + 1) == Some(&b':') => {
208 out.push(segment(rest, start, ident_end, i)?);
209 i += 2;
210 start = i;
211 ident_end = None;
212 continue;
213 }
214 _ => {}
215 }
216 i += 1;
217 }
218 out.push(segment(rest, start, ident_end, bytes.len())?);
219 Some(out)
220 }
221}
222
223/// One path segment's ident (up to its own generic arguments, if any) and
224/// whether it had them. `None` when the text is not an ident, which is how a
225/// non-path canon is refused.
226fn segment(
227 s: &str,
228 start: usize,
229 ident_end: Option<usize>,
230 end: usize,
231) -> Option<(syn::Ident, bool)> {
232 let text = s[start..ident_end.unwrap_or(end)].trim();
233 Some((
234 syn::parse_str::<syn::Ident>(text).ok()?,
235 ident_end.is_some(),
236 ))
237}
238
239/// Byte index of the `>` closing the angle group `s` opens with.
240fn close_angle(s: &str) -> Option<usize> {
241 let bytes = s.as_bytes();
242 let mut depth = 0usize;
243 for (i, b) in bytes.iter().enumerate() {
244 match b {
245 b'<' => depth += 1,
246 b'>' if i > 0 && bytes[i - 1] == b'-' => {}
247 b'>' => {
248 depth = depth.saturating_sub(1);
249 if depth == 0 {
250 return Some(i);
251 }
252 }
253 _ => {}
254 }
255 }
256 None
257}
258
259impl fmt::Display for TypeKey {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 f.write_str(&self.canon)
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use crate::types_util::bare_path_ident;
269
270 /// Every shape a key can hold, as a build script or a source could spell it.
271 ///
272 /// Generics on a **non-final** segment (`a::Foo<u8>::Bar`) and a `::` inside
273 /// an argument (`Vec<a::B>`) pull in opposite directions, and a `->` inside
274 /// an argument (`Vec<fn() -> a::B>`) breaks naive angle counting — each is a
275 /// way the string walk can be wrong while the easy cases still pass.
276 const SHAPES: &[&str] = &[
277 "Foo",
278 "a::Foo",
279 "a::b::Foo",
280 "std::string::String",
281 "Vec<u8>",
282 "Vec<a::B>",
283 "Vec<Vec<u8>>",
284 "a::Foo<u8>::Bar",
285 "Foo<u8>::Assoc",
286 "<T as Tr>::Item",
287 "Vec<fn() -> a::B>",
288 "Publisher<'static>",
289 "Option<Box<Node>>",
290 "&Foo",
291 "&mut Foo",
292 "&[u8]",
293 "[u8; 4]",
294 "()",
295 "(u8, u8)",
296 "(a::B, c::D)",
297 "*const u8",
298 "dyn Error",
299 "fn() -> u8",
300 "fn(u8) -> a::B",
301 ];
302
303 /// The accessors and the `syn` walks they replace answer identically.
304 ///
305 /// This is the whole warrant for reading names off the canonical string
306 /// instead of off a parsed type. Both walks are the incumbent definition —
307 /// `bare_path_ident` for [`TypeKey::ident`], and `rust_short_name_opt`'s
308 /// last-segment rule (spelled out here rather than imported, since it lives
309 /// under a language adapter) for [`TypeKey::short_name`].
310 #[test]
311 fn key_name_accessors_match_the_syn_walks() {
312 for spec in SHAPES {
313 let ty: syn::Type = syn::parse_str(spec).expect("test shape parses");
314 let key = TypeKey::from_type(&ty);
315
316 assert_eq!(
317 key.ident(),
318 bare_path_ident(&crate::flat::canonical_type(&ty)),
319 "ident() disagrees with bare_path_ident on `{spec}` (canon `{key}`)"
320 );
321
322 // `rust_short_name_opt`: the last path segment's ident, generic
323 // arguments and all.
324 let expected_short = match &crate::flat::canonical_type(&ty) {
325 syn::Type::Path(tp) => tp.path.segments.last().map(|s| s.ident.to_string()),
326 _ => None,
327 };
328 assert_eq!(
329 key.short_name(),
330 expected_short,
331 "short_name() disagrees with the last-segment rule on `{spec}` (canon `{key}`)"
332 );
333 }
334 }
335
336 /// `short_name` is looser than `ident` in exactly one way: generic arguments
337 /// **on the last segment**.
338 #[test]
339 fn short_name_reads_through_last_segment_generics_and_ident_does_not() {
340 let key = TypeKey::from_type(&syn::parse_quote!(Publisher<'static>));
341 assert_eq!(key.short_name().as_deref(), Some("Publisher"));
342 assert_eq!(key.ident(), None);
343
344 // Arguments EARLIER in the path disqualify nothing: the segment being
345 // named is `Bar`, and it has none.
346 let nested = TypeKey::from_type(&syn::parse_quote!(a::Foo<u8>::Bar));
347 assert_eq!(nested.short_name().as_deref(), Some("Bar"));
348 assert_eq!(
349 nested.ident().map(|i| i.to_string()).as_deref(),
350 Some("Bar")
351 );
352 }
353
354 /// A qualified-self path names its tail, like `bare_path_ident` does —
355 /// syn keeps only `Item` in `path.segments`, and so does the string walk.
356 #[test]
357 fn qualified_self_paths_name_their_tail() {
358 let key = TypeKey::from_type(&syn::parse_quote!(<T as Tr>::Item));
359 assert_eq!(key.short_name().as_deref(), Some("Item"));
360 assert_eq!(key.ident().map(|i| i.to_string()).as_deref(), Some("Item"));
361 }
362
363 /// A `::` inside a generic argument is not a path separator, and neither
364 /// angle counting nor the `->` in a bare-fn argument may make it look like
365 /// one.
366 #[test]
367 fn separators_inside_generic_arguments_are_not_path_separators() {
368 for (spec, expected) in [
369 ("Vec<a::B>", Some("Vec")),
370 ("Vec<fn() -> a::B>", Some("Vec")),
371 ("Vec<Vec<a::B>>", Some("Vec")),
372 ] {
373 let key = TypeKey::from_type(&syn::parse_str(spec).expect("test shape"));
374 assert_eq!(key.short_name().as_deref(), expected, "on `{spec}`");
375 }
376 }
377
378 /// A name comes back out as the ident it names — `from_ident` is the inverse.
379 #[test]
380 fn ident_round_trips_through_from_ident() {
381 let ident = syn::Ident::new("ZKeyExpr", proc_macro2::Span::call_site());
382 let key = TypeKey::from_ident(&ident);
383 assert_eq!(key.ident().as_ref(), Some(&ident));
384 assert_eq!(key.short_name().as_deref(), Some("ZKeyExpr"));
385 }
386}