mf2_macros/lib.rs
1//! `mf2-macros` — the `tr!` proc-macro.
2//!
3//! An application never names this crate. `mf2-build` generates, in the i18n
4//! crate, an exported `tr!` wrapper that forwards to `__tr_impl` with the
5//! manifest's absolute path and hash baked in as literals:
6//!
7//! ```text
8//! __tr_impl!("<abs path>/manifest.mf2m" 0x<hash>u64 ; $crate ; "id", name = value, …)
9//! __tr_impl!(bytes b"<manifest>" 0x<hash>u64 ; $crate ; "id", …)
10//! ```
11//!
12//! so that any crate depending on the i18n crate can call `tr!`, with no
13//! unstable feature and nothing to configure. The manifest is read once
14//! per compiler process, keyed by the path and verified against the baked
15//! hash — a manifest that hashes to anything else is reported as stale, never
16//! used, which is what keeps a long-lived rust-analyzer proc-macro server
17//! honest.
18//!
19//! `MF2_MACRO_STATS=<file>` makes each rustc process write what the macro
20//! cost it — expansions, nanoseconds, manifest reads — which is how the
21//! cache and the macro's time are measured (`stats`).
22//!
23//! What the macro checks and what it emits is the `expand` module's doc; what reaches
24//! the wasm is a `MsgId` and the argument values, never an id string, an
25//! argument name or a markup name.
26//!
27//! Both proc-macros are hidden from the documentation: only the generated
28//! wrapper calls them, and 2.x promises the `tr!` forms, not these
29//! (`docs/versioning.md`).
30//!
31//! # The user guide
32//!
33//! The [Rust MF2 book](https://evancarroll.github.io/rust-mf2/) is the user
34//! guide: how the crates fit together, web and native applications, the
35//! command line, and what 2.x promises.
36//! An application calls `tr!` through the i18n crate that
37//! `mf2-build` generates, and names [`mf2`](https://docs.rs/mf2).
38
39#![warn(missing_docs)]
40// docs.rs (`cargo xtask docs-rs`): each feature-gated item says which features it needs.
41#![cfg_attr(docsrs, feature(doc_cfg))]
42#![forbid(unsafe_code)]
43
44mod error;
45mod expand;
46mod manifest;
47mod parse;
48mod stats;
49
50use proc_macro::TokenStream;
51use quote::quote;
52
53/// The call site, checked against the manifest and lowered to a positional
54/// description of the message. Called only by the generated `tr!` wrapper.
55#[proc_macro]
56#[doc(hidden)]
57pub fn __tr_impl(input: TokenStream) -> TokenStream {
58 let started = stats::start();
59 let out = match parse::input(input).and_then(expand::expand) {
60 Ok(tokens) => tokens,
61 // Several errors of one call site become several `compile_error!`s;
62 // wrapped in a block, so the expansion is still one expression and
63 // rustc reports every one of them (a bare sequence misparses in
64 // expression position and hides all but the first).
65 Err(e) => {
66 let errors = e.into_compile_error();
67 quote! { { #errors } }
68 }
69 };
70 stats::expansion(started);
71 out.into()
72}
73
74/// The `MsgId` of a message, checked against the manifest — for a caller
75/// that formats with arguments it does not know at compile time
76/// (`mf2::TrDyn`). Called only by the generated `msg_id!` wrapper.
77#[proc_macro]
78#[doc(hidden)]
79pub fn __msg_id_impl(input: TokenStream) -> TokenStream {
80 let started = stats::start();
81 let out = match parse::input(input).and_then(expand::expand_id) {
82 Ok(tokens) => tokens,
83 Err(e) => {
84 let errors = e.into_compile_error();
85 quote! { { #errors } }
86 }
87 };
88 stats::expansion(started);
89 out.into()
90}