Skip to main content

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}