prebindgen_flat/flat/origin.rs
1//! Where a node came from: the syntax it was built from, and the source that
2//! syntax arrived in.
3//!
4//! One uniform property of **every** node in the model — item, parameter,
5//! field, variant, type, array extent alike. Some levels know less than others
6//! (a field has no line of its own), but the shape does not change with the
7//! level, so nothing has to copy a piece of provenance downward by hand.
8
9use std::rc::Rc;
10
11use prebindgen::SourceLocation;
12use quote::ToTokens;
13
14use super::key::TypeKey;
15
16/// The syntax a node was built from, plus where that syntax came from.
17///
18/// # Why the two travel together
19///
20/// `syn` tokens normally carry spans, so in principle the syntax alone could
21/// answer "where was this written". Not here: the proc-macro serializes each
22/// marked item as a **string** into JSONL, and `build.rs` re-parses it, so every
23/// span in [`spell()`](Self::spell) points into an anonymous buffer.
24/// [`SourceLocation::from_span`] captures file, line and column at
25/// macro-expansion time — while real rustc spans still exist — precisely because
26/// they cannot survive that trip.
27///
28/// # Why the location is shared
29///
30/// One captured record is one item, so there is exactly one location per item
31/// and none of its own for any component. An item and everything inside it —
32/// parameters, fields, variants, types, extents — therefore point at the *same*
33/// [`SourceLocation`], which is both the honest answer and the cheap one: a
34/// struct with twenty fields keeps one location, not twenty copies of a path.
35///
36/// `Rc` rather than `Arc`: this holds `syn` values, which are `!Send`, so the
37/// model can never cross a thread boundary and an atomic refcount would only
38/// cost. [`TypeKey`] made the same call for
39/// the same reason.
40///
41/// # The rule about origins
42///
43/// > A reference carries a name; the declaration carries the origin.
44///
45/// `location.crate_name` here is the crate whose source this node was written
46/// *in* — the use site. It is never part of a referenced item's identity:
47/// [`TypeId`](super::TypeId) is a name alone, because `#[prebindgen]` names live
48/// in one flat namespace. [`ConstId`](super::ConstId) is not an exception — the
49/// crate it records is the const's *declaring* crate, obtained by lookup, and
50/// that is exactly what lets an array extent refuse a const from another source.
51/// # The syntax is sealed
52///
53/// > **You may output the source. You may not read it.**
54///
55/// [`spell`](Self::spell) hands out tokens and nothing else, which is all
56/// generated Rust ever needed. The node is reachable only through one
57/// crate-internal accessor, and the field itself is `pub(super)` — so the
58/// model still reads it freely, while everything outside is limited to the
59/// spelling.
60///
61/// It was a public field returning a `syn` node to anyone who asked.
62/// Outside this crate, captured syntax is reachable only through
63/// [`Emit`](crate::flat::emit::Emit), and the compiler enforces it.
64#[derive(Clone, Debug)]
65pub struct Origin<S> {
66 /// The exact tokens this node was built from.
67 ///
68 /// `pub(super)` is the seal: inside the model this is the syntax being
69 /// lowered, classified and round-tripped, and reading it is the work. Outside,
70 /// see [`spell`](Self::spell) and [`as_syn`](Self::as_syn).
71 pub(super) syntax: S,
72 /// The captured item this node belongs to, shared with every sibling.
73 pub location: Rc<SourceLocation>,
74}
75
76impl<S> Origin<S> {
77 pub fn new(syntax: S, location: Rc<SourceLocation>) -> Self {
78 Self { syntax, location }
79 }
80
81 /// The node as `syn` — **the escape**.
82 ///
83 /// Every place that takes the source apart instead of asking the model
84 /// comes through here, and `pub(crate)` is what keeps that
85 /// list short: only [`Emit`](crate::flat::emit::Emit) can reach it.
86 ///
87 /// Naming it is not an accusation. An emitter assembling a `syn::Item`, or a
88 /// signature the generated crate must restate node for node, legitimately
89 /// needs the node. What it stops is reaching for one *by default*.
90 pub(crate) fn as_syn(&self) -> &S {
91 &self.syntax
92 }
93
94 /// The crate this node's source was written in.
95 ///
96 /// The **use site**, not the declaring crate of anything it names.
97 pub fn crate_name(&self) -> Option<&str> {
98 self.location.crate_name.as_deref()
99 }
100
101 /// The same location over different syntax — for building a component's
102 /// origin from the item's.
103 pub fn with<T>(&self, syntax: T) -> Origin<T> {
104 Origin {
105 syntax,
106 location: Rc::clone(&self.location),
107 }
108 }
109}
110
111impl Origin<syn::Type> {
112 /// This type's identity as a table key — the same answer
113 /// [`TypeRef::key`](super::TypeRef::key) gives for a reading.
114 ///
115 /// Here because a **declaration** is an `Origin<syn::Type>`: the type a
116 /// build script wrote, carried with a placeless location. Asking it for its
117 /// identity is not reaching for the node, and it should not have to be
118 /// spelled as one — every `TypeKey::from_type(decl.as_syn())` was a keying
119 /// operation wearing an escape's clothes.
120 pub fn key(&self) -> TypeKey {
121 TypeKey::from_type(&self.syntax)
122 }
123}
124
125impl<S: ToTokens> Origin<S> {
126 /// The node's tokens, for generated Rust to spell.
127 ///
128 /// **The only output route, and deliberately not `ToTokens`.** Implementing
129 /// that trait would make `quote!(#node)` work — and hand every consumer
130 /// `to_token_stream().to_string()` with it, which is a classifier's input in
131 /// a spelling's clothing. A `TokenStream` interpolates just as well one `let`
132 /// earlier, and the string, if a site really wants one, is now a two-call
133 /// pattern that says so.
134 ///
135 /// It does not make a token string *impossible* — `spell().to_string()`
136 /// reaches one, and the ledger still lists that as open. What it makes is
137 /// **visible**: `.to_token_stream().to_string()` was indistinguishable from
138 /// the same call on a type an adapter built itself.
139 ///
140 /// `pub`, not `pub(crate)`: the registry pipeline's own
141 /// tests (now in the separate `prebindgen-registry` crate) call this on a
142 /// captured element's `origin` — see `TypeRef`'s doc for why this seal is
143 /// now a convention rather than a compiler check.
144 pub fn spell(&self) -> proc_macro2::TokenStream {
145 self.syntax.to_token_stream()
146 }
147}
148
149impl Origin<syn::Type> {
150 /// A **declared** type's tokens.
151 ///
152 /// Public where [`Origin::spell`] is sealed, and the difference is what `S`
153 /// is. An `Origin<syn::ItemFn>`'s tokens re-parse to the captured item, so
154 /// handing them out is the item door under another name — that one is
155 /// [`Emit`](crate::flat::emit::Emit)'s to open. An
156 /// `Origin<syn::Type>` in an adapter's declaration holds a type the
157 /// **build script wrote**, which was never captured syntax and which #280
158 /// leaves the model no way to have a reading for.
159 ///
160 /// Still a token route, and still one C3 has to account for when
161 /// [`TypeRef::spell`](super::TypeRef::spell) moves onto `Emit`: a
162 /// declaration is an identity (`key()`), and the two sites that spell one
163 /// do it to splice `#target` into generated Rust.
164 pub fn declared_spelling(&self) -> proc_macro2::TokenStream {
165 self.spell()
166 }
167}