pub struct Input {
pub format: Option<String>,
pub options: Option<TokenStream>,
pub list: ContList,
pub entries: Vec<Entry>,
pub rest: TokenStream,
}Expand description
What a callback receives: @announce_format "1" (OPTIONS)? [LIST] ENTRY* REST.
§The wire format
A callback receives these tokens:
@announce_format "1" (OPTIONS)? [LIST] ENTRY* REST@announce_format "1": the format prefix. Every call that announce makes to the next element of a chain starts with it, whatever the element is: a trampoline, a transformer, a callback, a check."1"is the version of the wire format (ANNOUNCE_FORMAT); it changes when this format changes in a way an old reader cannot read. A call written by hand does not need the prefix, and every parser of announce reads a call with or without it (Input::formatis the version found). A call with another version is one compile error that names both versions, because the item and the macro that reads it were built with incompatible versions of announce. How a macro of your own detects the prefix. It starts every call that announce makes, also the call of the sentinel of the runner,NAME! { @announce_format "1" @__announce_exists }. (The runner is the hidden attribute that#[announce]puts on an item that has other attributes. Its arguments have their own marker,__announce_format_version = "1": it versions the encoding of the arguments of an attribute, which the runners of two versions of announce compare when they meet on one item. The runner is not a call of a chain, and has no@announce_format; the calls of a chain are not attributes, and have no__announce_format_version.)OPTIONS: global options, forwarded unchanged by every hop. Only a leadingexprhas a meaning: it puts the chain in expression position, where derive and attribute elements are refused (is_expression_position). The rest is free for helpers (Input::has_option).LIST: the continuation list, the calls still to make, head first. The head is the macro that is called. An element (Cont) ispath!,#[derive(path)] Nameor#[attr] Name. The last two are the derive and attribute route:Nameis a dummy enum that carries the tokens, and a derive or attribute macro hands them on withredirect.ENTRY:KEY => [META] { ITEM }, one per definition (Entry).KEYis the list element that produced the entry, as written.REST: tokens that the protocol never touches. A parser reads entries greedily, so wrap aRESTthat starts like an entry.
How a chain runs:
- Each macro drops its own element (the head) from the list, adds an entry, and calls the next element with the rest of the list. The last element is the callback: it runs once, with one entry per trampoline.
- New entries go in front, so they are newest first.
Input::entries_in_ordergives oldest first. [META]is always written, even when empty ([]).- A trampoline can get the prefixes
@__announce_self { PATH }and@__announce_dollar { $ }after the format prefix, before the rest of this input:@announce_format "1" @__announce_self { PATH } @__announce_dollar { $ } ... Only trampolines andredirectuse them. Parsing anInputskips them. - The calls that announce makes to a check have the prefix too: the query
@announce_format "1" @__announce_attrs [..] { STATE }and the check itself,@announce_format "1" (ARGS), ITEM(attrs).
META holds these elements, as the definer wrote them:
pathorpath(args): a check (#[announce(path(args))]);path(args): ameta = path(args)entry, never called (Entry::find_meta);mock(path): the item stands in for the item atpath(Entry::mock);attrs(a, b): the attribute names that checks own (Entry::owned_attrs);type_leak(..): the type table of arepeater(Entry::type_leak).
Ignore elements you do not know.
use announce::helper::Input;
let input: Input = syn::parse_quote! {
@announce_format "1"
[cb!]
a::F! => [my_lib::check, mock(::core::iter::Iterator)] { pub trait F {} }
REST tokens
};
assert_eq!(input.format.as_deref(), Some("1"));
let entry = &input.entries[0];
assert!(entry.has_check("check"));
assert!(entry.mock().is_some());
assert_eq!(input.rest.to_string(), "REST tokens");
// A call by hand has no prefix, and parses as well.
let by_hand: Input = syn::parse_quote! { [cb!] a::F! => [] { pub trait F {} } };
assert_eq!(by_hand.format, None);
// Another version is one error, which names the version found and the one read.
let err = syn::parse2::<Input>(syn::parse_quote! { @announce_format "2" [cb!] }).unwrap_err();
assert!(err.to_string().contains("found `@announce_format \"2\"`"));
assert!(err.to_string().contains("expected `@announce_format \"1\"`"));§Detecting a call from announce
A macro of your own, a macro_rules! callback, check or transformer, can match
the prefix to know that announce called it, and which version of the wire format
the call has. Put the arm for the prefix first. The arm for the call by hand
(without it) stays, if you want one:
macro_rules! who_calls {
// Called by announce: the prefix, then `[LIST] ENTRY* REST`.
(@announce_format "1" [$($list:tt)*] $($rest:tt)*) => {
const CALLER: &str = "announce";
};
// Called by hand, or with a version that this macro does not know.
($($rest:tt)*) => {
const CALLER: &str = "not announce, or another format";
};
}
who_calls! { @announce_format "1" [who_calls!] }
assert_eq!(CALLER, "announce");A macro that forwards the call to the next element starts the forwarded call with
the prefix again; Input::forward does it, and format_prefix gives the
tokens to a proc macro that builds the call itself. A macro_rules! callback
that is written for one version only says so in its pattern, as above: a call
with another version matches no arm, and rustc reports the call.
§A real example
This is what a callback gets from a real chain, captured by the test
tests/user/tests/wire.rs. The definitions, in tests/defs/src/wire.rs
(Meters is private to the defining crate, so Length has a repeater):
::announce::define_repeater!(pub Repeater);
/// A point.
#[announce(announce_test_derive::lib_check, meta = crate::info(1))]
pub struct Point {
/// The x coordinate.
#[my_lib(skip)]
pub x: u8,
pub y: u8,
}
#[announce(repeater = crate::wire::Repeater)]
pub struct Length(pub Meters);The chain, in another crate. show! is the callback; it prints its whole input:
use announce_test_defs::wire as defs;
macro_rules! show {
($($t:tt)*) => { stringify!($($t)*) };
}
let wire: &str = defs::Point! { (expr) [defs::Point!, defs::Length!, show!] some rest };wire is this text. The tokens are the ones received, in order. Only the
layout is added by hand, and the random number of id (a different one for each
build) is replaced by ID. The test compares this block with what the chain
gives, so it cannot drift:
@announce_format "1"
(expr)
[show!]
defs::Length! => [
type_leak(repeater = $crate::wire::Repeater, id = ID,
context = 0, referrer = ["type-leak-format-14" (Meters)])
] {
pub struct Length(pub Meters);
}
defs::Point! => [
announce_test_derive::lib_check,
$crate::info(1),
attrs(my_lib)
] {
pub struct Point {
#[my_lib(skip)]
pub x: u8,
pub y: u8,
}
}
some restHow the parts map to the fields of Input:
| Tokens | Field | Notes |
|---|---|---|
@announce_format "1" | format | The prefix, written by both hops. |
(expr) | options | From the call, forwarded by both hops. |
[show!] | list | Only the callback is left: Point! and Length! each dropped themselves. |
defs::Length! => [..] { .. } | entries[0] | The newest entry, the last hop. Its key is as written in the call. |
defs::Point! => [..] { .. } | entries[1] | The oldest entry. |
some rest | rest | The caller’s tokens, untouched. |
What is in the entries:
Length:type_leak(..)is the table of therepeater(Entry::type_leak). The item still saysMeters, which only the defining crate can resolve; the table is what a callback uses to write the type from anywhere (TypeLeak::apply).Point:announce_test_derive::lib_checkis the check,$crate::info(1)ismeta = crate::info(1)(cratebecame$crate, so that the path resolves where the callback runs), andattrs(my_lib)lists the attribute names that the check owns.- The doc comments of
Pointare gone from the item, because the payload has no docs unlessdocs = true.#[my_lib(skip)]is still there: the definer removed it from the item it emitted, but the callback can read it.
§self and hygiene
self is hygienic. The self in the receiver of a method that the callback
receives (&self) belongs to the trampoline’s expansion; a self that the
callback writes itself, in its own quote!, is another one to rustc. A callback
that mixes the received tokens with its own output, for example the received
signature with a body it writes, must use the self token of the payload in that
body. Otherwise rustc reports E0424 (“cannot find value self in this
scope”). Take the token from the receiver of the parsed signature:
use announce::helper::Input;
use template_quote::quote;
// For `trait Get { fn get(&self) -> u8; }`: an impl that forwards `get` to a field.
fn forward(input: &Input) -> syn::Result<proc_macro2::TokenStream> {
let item = match input.entries[0].item()? {
syn::Item::Trait(t) => t,
_ => return Err(syn::Error::new(proc_macro2::Span::call_site(), "expected a trait")),
};
let sig = match &item.items[0] {
syn::TraitItem::Fn(f) => &f.sig,
_ => return Err(syn::Error::new(proc_macro2::Span::call_site(), "expected a method")),
};
let (trait_name, name) = (&item.ident, &sig.ident);
// The `self` of the payload: a `quote! { self.inner }` here would not match `#sig`.
let this = &sig.receiver().expect("a method with a receiver").self_token;
Ok(quote! {
impl #trait_name for Wrapper { #sig { #this.inner.#name() } }
})
}
let input: Input = syn::parse_quote! { [cb!] a::Get! => [] { trait Get { fn get(&self) -> u8; } } };
let out = forward(&input).unwrap().to_string();
assert!(out.contains("fn get (& self) -> u8 { self . inner . get () }"), "{}", out);Fields§
§format: Option<String>The version of the prefix @announce_format "VERSION" that the call started
with (ANNOUNCE_FORMAT when there is one), or None for a call without it
(written by hand). A call with another version does not parse.
forward, fetch and gather
always write the prefix with the version of this crate, whatever this field is.
options: Option<TokenStream>The global options group (..). forward passes it on
unchanged. fetch and gather add
__announce_fetched(..) to it, which records what they fetched (to detect
a continuation that is not a trampoline).
list: ContListThe continuation list; its head is the callback itself.
entries: Vec<Entry>Newest first.
rest: TokenStreamREST: tokens after the entries, untouched by the protocol.
Implementations§
Source§impl Input
impl Input
Sourcepub fn forward_transformed(&self, entry: Entry) -> Result<TokenStream>
pub fn forward_transformed(&self, entry: Entry) -> Result<TokenStream>
For a transformer: forwards to the next element with the newest entry
replaced by entry. Errors if there is no entry or no next element.
Source§impl Input
impl Input
Sourcepub fn entries_in_order(&self) -> impl Iterator<Item = &Entry>
pub fn entries_in_order(&self) -> impl Iterator<Item = &Entry>
Entries in the order their definitions were visited (oldest first).
Sourcepub fn fetch(&self, new: impl IntoIterator<Item = Cont>) -> TokenStream
pub fn fetch(&self, new: impl IntoIterator<Item = Cont>) -> TokenStream
Fetches more definitions, then calls this callback again. It invokes
new[0] with the list [new.., <this list>], so the new entries arrive in
front of the old ones.
The paths are recorded in the options group as __announce_fetched(..). If
this callback is called again with a recorded path but no entry for it, that
path is not an #[announce] trampoline. Then fetch emits a
compile_error! instead of fetching until the recursion limit. A callback
that fetches only what is still missing (as gather does)
never meets that error for a trampoline.
Sourcepub fn has_option(&self, name: &str) -> bool
pub fn has_option(&self, name: &str) -> bool
Whether the global options group contains the ident name.
Sourcepub fn option_items(&self) -> Vec<TokenStream>
pub fn option_items(&self) -> Vec<TokenStream>
The comma-separated elements of the options group.
Sourcepub fn entry_for(&self, cont: &Cont) -> Option<&Entry>
pub fn entry_for(&self, cont: &Cont) -> Option<&Entry>
The entry whose key names cont (compared by macro path, with generic
arguments ignored), newest first.
Sourcepub fn gather(&self, needed: impl IntoIterator<Item = Cont>) -> Gather<'_>
pub fn gather(&self, needed: impl IntoIterator<Item = Cont>) -> Gather<'_>
Asks for definitions: “give me these, then call me again”.
Returns Gather::Ready with one entry per requested continuation, in
order, when all are present. Otherwise it returns Gather::Fetch: tokens
that fetch the missing ones and call this callback again.
let entries = match input.gather(needed) {
Gather::Ready(e) => e,
Gather::Fetch(ts) => return Ok(ts),
};Sourcepub fn forward(&self) -> Option<TokenStream>
pub fn forward(&self) -> Option<TokenStream>
Continues the chain: drops the head and invokes the next element. Returns
None if the head was the last element.