Skip to main content

md_tmpl_macros/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4mod codegen;
5mod compile;
6mod struct_gen;
7mod type_gen;
8
9use std::cell::RefCell;
10
11use codegen::{codegen_compiled_inline_template, codegen_segment, codegen_value, codegen_var_decl};
12use compile::{CompiledTemplateAst, load_and_compile, stem_from_path};
13use proc_macro::TokenStream;
14use quote::{format_ident, quote};
15use struct_gen::{StructGenSource, generate_struct_tokens};
16use syn::{
17    Ident, LitStr, Token,
18    parse::{Parse, ParseStream},
19    parse_macro_input,
20};
21use type_gen::generate_type_alias_tokens;
22
23/// Convert absolute dependency paths into owned strings for `include_str!`
24/// emission, so Cargo tracks imported / `{% include %}`d files as build inputs.
25fn dep_paths_to_strings(paths: &[std::path::PathBuf]) -> Vec<String> {
26    paths
27        .iter()
28        .map(|p| p.to_string_lossy().into_owned())
29        .collect()
30}
31
32thread_local! {
33    /// The crate path to use in generated code.
34    ///
35    /// Defaults to `::md_tmpl` but can be overridden via the
36    /// `crate = path` argument in `include_template!` / `template!`.
37    /// Using a thread-local avoids threading the value through every
38    /// codegen helper.
39    static CRATE_PATH: RefCell<proc_macro2::TokenStream> = RefCell::new(quote! { ::md_tmpl });
40}
41
42/// Read the current crate path from the thread-local.
43pub(crate) fn crate_path() -> proc_macro2::TokenStream {
44    CRATE_PATH.with(|cp| cp.borrow().clone())
45}
46
47/// Set the crate path for the duration of the closure, then restore it.
48fn with_crate_path<F: FnOnce() -> R, R>(path: proc_macro2::TokenStream, f: F) -> R {
49    CRATE_PATH.with(|cp| {
50        let old = cp.replace(path);
51        let result = f();
52        cp.replace(old);
53        result
54    })
55}
56
57/// Parsed input for `include_template!("path")`,
58/// `include_template!("path" => custom_mod_name)`,
59/// `include_template!("path" as StructName)`,
60/// `include_template!("path" as StructName => custom_mod_name)`,
61/// `include_template!("path", crate = ::my_crate::reexport)`, or
62/// `include_template!("path", env = { KEY: "value", KEY2: 42 })`.
63struct IncludeTemplateInput {
64    path: LitStr,
65    struct_name: Option<Ident>,
66    custom_name: Option<Ident>,
67    crate_path: Option<syn::Path>,
68    env: Vec<(String, syn::Expr)>,
69    import_paths: Vec<(String, syn::Path)>,
70}
71
72impl Parse for IncludeTemplateInput {
73    fn parse(input: ParseStream) -> syn::Result<Self> {
74        let path: LitStr = input.parse()?;
75
76        // Optional: `as StructName`
77        let struct_name = if input.peek(Token![as]) {
78            let _as: Token![as] = input.parse()?;
79            Some(input.parse()?)
80        } else {
81            None
82        };
83
84        // Optional: `=> custom_mod_name`
85        let custom_name = if input.peek(Token![=>]) {
86            let _arrow: Token![=>] = input.parse()?;
87            Some(input.parse()?)
88        } else {
89            None
90        };
91
92        // Optional trailing arguments: `, crate = ...`, `, env = { ... }`, or
93        // `, imports = { stem = ::rust::path, ... }`.
94        let mut crate_path = None;
95        let mut env = Vec::new();
96        let mut import_paths = Vec::new();
97        while input.peek(Token![,]) {
98            let _comma: Token![,] = input.parse()?;
99            if input.is_empty() {
100                break;
101            }
102            if input.peek(Token![crate]) {
103                let _kw: Token![crate] = input.parse()?;
104                let _eq: Token![=] = input.parse()?;
105                crate_path = Some(input.parse()?);
106            } else {
107                let kw: Ident = input.parse()?;
108                if kw == "env" {
109                    let _eq: Token![=] = input.parse()?;
110                    env = parse_env_block(input)?;
111                } else if kw == "imports" {
112                    let _eq: Token![=] = input.parse()?;
113                    import_paths = parse_imports_block(input)?;
114                } else {
115                    return Err(syn::Error::new(
116                        kw.span(),
117                        format!("unknown option '{kw}', expected 'crate', 'env', or 'imports'"),
118                    ));
119                }
120            }
121        }
122
123        Ok(Self {
124            path,
125            struct_name,
126            custom_name,
127            crate_path,
128            env,
129            import_paths,
130        })
131    }
132}
133
134/// Parsed input for `template!(r#"source"# => mod_name)`.
135///
136/// The `=> name` is **required** because inline templates have no file path
137/// from which to derive a module name.
138struct InlineTemplateInput {
139    source: LitStr,
140    struct_name: Option<Ident>,
141    name: Ident,
142    crate_path: Option<syn::Path>,
143    env: Vec<(String, syn::Expr)>,
144    import_paths: Vec<(String, syn::Path)>,
145}
146
147impl Parse for InlineTemplateInput {
148    fn parse(input: ParseStream) -> syn::Result<Self> {
149        let source: LitStr = input.parse()?;
150        let struct_name = if input.peek(Token![as]) {
151            let _as: Token![as] = input.parse()?;
152            Some(input.parse()?)
153        } else {
154            None
155        };
156        let _: Token![=>] = input.parse()?;
157        let name: Ident = input.parse()?;
158        let mut crate_path = None;
159        let mut env = Vec::new();
160        let mut import_paths = Vec::new();
161        while input.peek(Token![,]) {
162            let _comma: Token![,] = input.parse()?;
163            if input.is_empty() {
164                break;
165            }
166            if input.peek(Token![crate]) {
167                let _kw: Token![crate] = input.parse()?;
168                let _eq: Token![=] = input.parse()?;
169                crate_path = Some(input.parse()?);
170            } else {
171                let kw: Ident = input.parse()?;
172                if kw == "env" {
173                    let _eq: Token![=] = input.parse()?;
174                    env = parse_env_block(input)?;
175                } else if kw == "imports" {
176                    let _eq: Token![=] = input.parse()?;
177                    import_paths = parse_imports_block(input)?;
178                } else {
179                    return Err(syn::Error::new(
180                        kw.span(),
181                        format!("unknown option '{kw}', expected 'crate', 'env', or 'imports'"),
182                    ));
183                }
184            }
185        }
186        Ok(Self {
187            source,
188            struct_name,
189            name,
190            crate_path,
191            env,
192            import_paths,
193        })
194    }
195}
196
197/// Strict, reserved, and weak keywords in Rust that require `r#` when used
198/// as identifiers.  Sourced from the Rust Reference.
199const RUST_KEYWORDS: &[&str] = &[
200    // Strict keywords
201    "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for",
202    "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
203    "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where",
204    "while", "async", "await", "dyn", // Reserved keywords
205    "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized",
206    "virtual", "yield", "try", // Weak keyword used in specific contexts
207    "union",
208];
209
210/// Keywords that cannot be used as raw identifiers (`r#self` is a compile
211/// error).  For these we prefix with `__` and emit `#[serde(rename = "…")]`.
212const UNESCAPABLE_KEYWORDS: &[&str] = &["self", "Self", "super", "crate"];
213
214/// Create an identifier, using raw syntax (`r#name`) when `name` is a Rust
215/// keyword.  For the four keywords that cannot be raw identifiers (`self`,
216/// `Self`, `super`, `crate`), we prefix with `__` instead (e.g. `__self`).
217///
218/// Works for module names, field names, and any user-provided identifier that
219/// might collide with a keyword.
220pub(crate) fn make_ident(name: &str) -> Ident {
221    if UNESCAPABLE_KEYWORDS.contains(&name) {
222        format_ident!("__{}", name)
223    } else if RUST_KEYWORDS.contains(&name) {
224        format_ident!("r#{}", name)
225    } else {
226        Ident::new(name, proc_macro2::Span::call_site())
227    }
228}
229
230/// Returns a `#[serde(rename = "original")]` attribute token stream when the
231/// name was mangled by [`make_ident`] (i.e., one of the un-escapable keywords).
232/// Returns an empty token stream otherwise — safe to interpolate unconditionally.
233pub(crate) fn serde_rename_attr(name: &str) -> proc_macro2::TokenStream {
234    if UNESCAPABLE_KEYWORDS.contains(&name) {
235        quote! { #[serde(rename = #name)] }
236    } else {
237        quote! {}
238    }
239}
240
241/// Parse an env block: `{ KEY: expr, KEY2: expr, ... }`.
242///
243/// Each key is an identifier and each value is a literal expression
244/// (string, int, float, or bool).
245fn parse_env_block(input: ParseStream) -> syn::Result<Vec<(String, syn::Expr)>> {
246    let content;
247    syn::braced!(content in input);
248    let mut entries = Vec::new();
249    while !content.is_empty() {
250        let key: Ident = content.parse()?;
251        let _colon: Token![:] = content.parse()?;
252        let expr: syn::Expr = content.parse()?;
253        entries.push((key.to_string(), expr));
254        if content.peek(Token![,]) {
255            let _comma: Token![,] = content.parse()?;
256        }
257    }
258    Ok(entries)
259}
260
261/// Parse an imports block: `{ stem = ::rust::path, stem2 = crate::mod }`.
262///
263/// Maps each import stem (as declared in the template's `imports:` block) to
264/// the Rust module path where that imported template's generated types live.
265/// Used to reference imported enum types directly instead of emitting a
266/// duplicate per-template copy.
267fn parse_imports_block(input: ParseStream) -> syn::Result<Vec<(String, syn::Path)>> {
268    let content;
269    syn::braced!(content in input);
270    let mut entries = Vec::new();
271    while !content.is_empty() {
272        let stem: Ident = content.parse()?;
273        let _eq: Token![=] = content.parse()?;
274        let path: syn::Path = content.parse()?;
275        entries.push((stem.to_string(), path));
276        if content.peek(Token![,]) {
277            let _comma: Token![,] = content.parse()?;
278        }
279    }
280    Ok(entries)
281}
282
283/// Resolve each imported-enum param to the full Rust path of its imported type.
284///
285/// Intersects the frontmatter's [`imported_type_params`] (param → `(stem,
286/// type_name)`) with the caller-provided `imports = { stem = path }` mapping,
287/// yielding `param_name → tokens(path::TypeName)`. Params whose import stem was
288/// not mapped are omitted, so codegen falls back to emitting a fresh type.
289///
290/// [`imported_type_params`]: md_tmpl_core::Frontmatter::imported_type_params
291fn build_imported_type_paths(
292    fm: &md_tmpl_core::Frontmatter,
293    import_paths: &[(String, syn::Path)],
294) -> std::collections::HashMap<String, proc_macro2::TokenStream> {
295    let mut out = std::collections::HashMap::new();
296    for (param, (stem, type_name)) in &fm.imported_type_params {
297        if let Some((_, base)) = import_paths.iter().find(|(s, _)| s == stem) {
298            let ty = format_ident!("{}", type_name);
299            out.insert(param.clone(), quote! { #base::#ty });
300        }
301    }
302    out
303}
304
305/// Evaluate an env expression at proc-macro expansion time.
306///
307/// Supports:
308/// - String literals: `"value"` → `Value::Str("value")`
309/// - Integer literals: `42` → `Value::Int(42)`
310/// - Float literals: `3.14` → `Value::Float(3.14)`
311/// - Bool literals: `true`/`false` → `Value::Bool(true/false)`
312fn eval_env_expr(expr: &syn::Expr) -> Result<md_tmpl_core::Value, String> {
313    match expr {
314        syn::Expr::Lit(lit) => match &lit.lit {
315            syn::Lit::Str(s) => Ok(md_tmpl_core::Value::Str(s.value())),
316            syn::Lit::Int(i) => {
317                let n: i64 = i
318                    .base10_parse()
319                    .map_err(|e| format!("invalid integer: {e}"))?;
320                Ok(md_tmpl_core::Value::Int(n))
321            }
322            syn::Lit::Float(f) => {
323                let n: f64 = f
324                    .base10_parse()
325                    .map_err(|e| format!("invalid float: {e}"))?;
326                Ok(md_tmpl_core::Value::Float(n))
327            }
328            syn::Lit::Bool(b) => Ok(md_tmpl_core::Value::Bool(b.value)),
329            _ => Err("env value must be a string, int, float, or bool literal".to_string()),
330        },
331        // Handle `true` and `false` as path expressions (syn parses
332        // bare `true`/`false` as Expr::Path, not Expr::Lit, in some contexts).
333        syn::Expr::Path(p) => {
334            if p.path.is_ident("true") {
335                Ok(md_tmpl_core::Value::Bool(true))
336            } else if p.path.is_ident("false") {
337                Ok(md_tmpl_core::Value::Bool(false))
338            } else {
339                Err(format!(
340                    "env value must be a literal, got path: {}",
341                    quote! { #expr }
342                ))
343            }
344        }
345        _ => Err(format!(
346            "env value must be a literal, got: {}",
347            quote! { #expr }
348        )),
349    }
350}
351
352/// Helper: convert a `load_and_compile` error into a compile error token stream.
353fn err_tokens(span: proc_macro2::Span, rel_path: &str, e: &str) -> TokenStream {
354    let msg = format!("template '{rel_path}': {e}");
355    syn::Error::new(span, msg).to_compile_error().into()
356}
357
358/// Pre-parse and validate a `.tmpl.md` template at compile time and emit a
359/// complete module.
360///
361/// # Syntax
362///
363/// ```text
364/// include_template!("path/to/template.tmpl.md");
365/// include_template!("path/to/template.tmpl.md" => custom_mod);
366/// ```
367///
368/// When no custom name is given the module name is derived from the file stem
369/// (e.g. `greeting` from `greeting.tmpl.md`).
370///
371/// # Generated module contents
372///
373/// * `pub fn template() -> &'static Template` — the pre-compiled template
374///   singleton.
375/// * `pub struct Params { … }` — typed parameter struct with:
376///   - `render()` — zero-arg render using the embedded template.
377///   - `render_reloaded(tmpl)` — render with a hot-reloaded template
378///     from disk.
379///   - `validate_template(tmpl)` — check template compatibility.
380///   - `to_context()` — convert to a `Context`.
381/// * Sub-structs for compound types.
382/// * Constants from the `consts:` block.
383/// * Type aliases from the `types:` block.
384///
385/// # Examples
386///
387/// ```rust
388/// extern crate md_tmpl_core as md_tmpl;
389/// md_tmpl_macros::include_template!("prompts/simple_greeting.tmpl.md");
390///
391/// let output = simple_greeting::Params {
392///     name: "World".into(),
393/// }
394/// .render()
395/// .unwrap();
396/// assert_eq!(output, "\nHello World!\n");
397/// ```
398///
399/// # Panics
400///
401/// Panics if an `env` expression cannot be evaluated at macro expansion time.
402#[proc_macro]
403pub fn include_template(input: TokenStream) -> TokenStream {
404    let parsed = parse_macro_input!(input as IncludeTemplateInput);
405    let rel_path = parsed.path.value();
406
407    // Evaluate env expressions at macro expansion time.
408    let env_values: Vec<(String, md_tmpl_core::Value)> = parsed
409        .env
410        .iter()
411        .map(|(k, expr)| {
412            let val = eval_env_expr(expr).unwrap_or_else(|e| panic!("env '{k}': {e}"));
413            (k.clone(), val)
414        })
415        .collect();
416    let env_refs: Vec<(&str, md_tmpl_core::Value)> = env_values
417        .iter()
418        .map(|(k, v)| (k.as_str(), v.clone()))
419        .collect();
420
421    let (full_path, ast) = match load_and_compile(&rel_path, &env_refs) {
422        Ok(v) => v,
423        Err(e) => return err_tokens(parsed.path.span(), &rel_path, &e),
424    };
425    let CompiledTemplateAst {
426        frontmatter: fm,
427        segments,
428        inline_templates,
429        source_hash,
430        dependency_paths,
431    } = ast;
432    let path_str = full_path.to_string_lossy().to_string();
433    let dep_paths_str = dep_paths_to_strings(&dependency_paths);
434
435    // Module name: custom or derived from file stem.
436    let mod_ident = match parsed.custom_name {
437        Some(ident) => ident,
438        None => make_ident(&stem_from_path(&rel_path)),
439    };
440
441    // Crate path: custom or default `::md_tmpl`.
442    let crate_path = parsed
443        .crate_path
444        .map_or_else(|| quote! { ::md_tmpl }, |p| quote! { #p });
445
446    with_crate_path(crate_path.clone(), || {
447        // Template AST codegen.
448        let segments_tokens = segments.iter().map(codegen_segment);
449        let decls_tokens = fm.declarations.iter().map(codegen_var_decl);
450        let inline_templates_tokens = inline_templates.iter().map(|(k, v)| {
451            let v_tokens = codegen_compiled_inline_template(v);
452            quote! { (#k, #v_tokens) }
453        });
454        let consts_tokens = fm.consts.iter().chain(fm.env.iter()).filter_map(|d| {
455            d.default_value.as_ref().map(|v| {
456                let name = &d.name;
457                let val_tokens = codegen_value(v);
458                quote! { (#name, #val_tokens) }
459            })
460        });
461        let imported_consts_tokens = fm.imported_consts.iter().map(|(k, v)| {
462            let val_tokens = codegen_value(v);
463            quote! { (#k, #val_tokens) }
464        });
465
466        // Params struct codegen.
467        let struct_name = parsed
468            .struct_name
469            .unwrap_or_else(|| format_ident!("Params"));
470        let source = StructGenSource::Module {
471            doc_path: &rel_path,
472        };
473        let imported_type_paths = build_imported_type_paths(&fm, &parsed.import_paths);
474        let struct_tokens =
475            generate_struct_tokens(&fm, &struct_name, &source, &imported_type_paths);
476
477        // Type alias codegen.
478        let type_alias_tokens = generate_type_alias_tokens(&fm.type_aliases);
479
480        let name_token = if let Some(n) = &fm.name {
481            quote! { Some(#n) }
482        } else {
483            quote! { None }
484        };
485        let desc_token = if let Some(d) = &fm.description {
486            quote! { Some(#d) }
487        } else {
488            quote! { None }
489        };
490
491        let expanded = quote! {
492            pub mod #mod_ident {
493                const _: &str = include_str!(#path_str);
494                #(const _: &str = include_str!(#dep_paths_str);)*
495
496                fn __init_template() -> #crate_path::Template {
497                    #crate_path::Template::from_precompiled(&#crate_path::PrecompiledTemplateData {
498                        segments: &[#(#segments_tokens),*],
499                        declared_variables: &[#(#decls_tokens),*],
500                        inline_templates: &[#(#inline_templates_tokens),*],
501                        source_hash: #source_hash,
502                        consts: &[#(#consts_tokens),*],
503                        imported_consts: &[#(#imported_consts_tokens),*],
504                        name: #name_token,
505                        description: #desc_token,
506                    })
507                }
508                static __TEMPLATE: #crate_path::__private::LazyLock<#crate_path::Template> =
509                    #crate_path::__private::LazyLock::new(__init_template);
510
511                /// Get a reference to the compile-time validated, pre-compiled template.
512                pub fn template() -> &'static #crate_path::Template {
513                    &*__TEMPLATE
514                }
515
516                #struct_tokens
517                #(#type_alias_tokens)*
518            }
519        };
520        expanded.into()
521    })
522}
523
524/// Parse and validate an inline template string at compile time and emit a
525/// complete module.
526///
527/// Unlike [`include_template!`] which reads from a file, this macro takes a
528/// string literal containing the full template source (including frontmatter).
529/// The `=> module_name` is **required** because there is no file path from
530/// which to derive a name.
531///
532/// The generated module has the same shape as [`include_template!`] — see its
533/// docs for details.
534///
535/// # Examples
536///
537/// ```rust
538/// extern crate md_tmpl_core as md_tmpl;
539/// md_tmpl_macros::template!(
540///     r#"
541/// ---
542/// params:
543///   - name = str
544/// ---
545/// Hello {{ name }}!
546/// "# => greeting
547/// );
548///
549/// let output = greeting::Params { name: "World".into() }
550///     .render()
551///     .unwrap();
552/// assert_eq!(output, "Hello World!\n");
553/// ```
554///
555/// # Panics
556///
557/// Panics if an `env` expression cannot be evaluated at macro expansion time.
558#[proc_macro]
559pub fn template(input: TokenStream) -> TokenStream {
560    let parsed = parse_macro_input!(input as InlineTemplateInput);
561    let source = parsed.source.value();
562    let mod_ident = parsed.name;
563
564    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
565    let base_dir = std::path::Path::new(&manifest_dir);
566
567    // Evaluate env expressions at macro expansion time.
568    let env_values: Vec<(String, md_tmpl_core::Value)> = parsed
569        .env
570        .iter()
571        .map(|(k, expr)| {
572            let val = eval_env_expr(expr).unwrap_or_else(|e| panic!("env '{k}': {e}"));
573            (k.clone(), val)
574        })
575        .collect();
576    let env_refs: Vec<(&str, md_tmpl_core::Value)> = env_values
577        .iter()
578        .map(|(k, v)| (k.as_str(), v.clone()))
579        .collect();
580
581    let ast = match compile::compile_template_to_ast(&source, base_dir, &env_refs) {
582        Ok(v) => v,
583        Err(e) => {
584            let msg = format!("inline template: {e}");
585            return syn::Error::new(parsed.source.span(), msg)
586                .to_compile_error()
587                .into();
588        }
589    };
590    let CompiledTemplateAst {
591        frontmatter: fm,
592        segments,
593        inline_templates,
594        source_hash,
595        dependency_paths,
596    } = ast;
597    let dep_paths_str = dep_paths_to_strings(&dependency_paths);
598
599    // Crate path: custom or default `::md_tmpl`.
600    let crate_path = parsed
601        .crate_path
602        .map_or_else(|| quote! { ::md_tmpl }, |p| quote! { #p });
603
604    with_crate_path(crate_path.clone(), || {
605        // Template AST codegen.
606        let segments_tokens = segments.iter().map(codegen_segment);
607        let decls_tokens = fm.declarations.iter().map(codegen_var_decl);
608        let inline_templates_tokens = inline_templates.iter().map(|(k, v)| {
609            let v_tokens = codegen_compiled_inline_template(v);
610            quote! { (#k, #v_tokens) }
611        });
612        let consts_tokens = fm.consts.iter().chain(fm.env.iter()).filter_map(|d| {
613            d.default_value.as_ref().map(|v| {
614                let name = &d.name;
615                let val_tokens = codegen_value(v);
616                quote! { (#name, #val_tokens) }
617            })
618        });
619        let imported_consts_tokens = fm.imported_consts.iter().map(|(k, v)| {
620            let val_tokens = codegen_value(v);
621            quote! { (#k, #val_tokens) }
622        });
623
624        // Params struct codegen — uses Module so render() calls super::template().
625        let struct_name = parsed
626            .struct_name
627            .clone()
628            .unwrap_or_else(|| format_ident!("Params"));
629        let source = StructGenSource::Module {
630            doc_path: "<inline>",
631        };
632        let imported_type_paths = build_imported_type_paths(&fm, &parsed.import_paths);
633        let struct_tokens =
634            generate_struct_tokens(&fm, &struct_name, &source, &imported_type_paths);
635
636        // Type alias codegen.
637        let type_alias_tokens = generate_type_alias_tokens(&fm.type_aliases);
638
639        let name_token = fm
640            .name
641            .as_ref()
642            .map_or_else(|| quote! { None }, |n| quote! { Some(#n) });
643        let desc_token = fm
644            .description
645            .as_ref()
646            .map_or_else(|| quote! { None }, |d| quote! { Some(#d) });
647
648        let expanded = quote! {
649            pub mod #mod_ident {
650                #(const _: &str = include_str!(#dep_paths_str);)*
651                fn __init_template() -> #crate_path::Template {
652                    #crate_path::Template::from_precompiled(&#crate_path::PrecompiledTemplateData {
653                        segments: &[#(#segments_tokens),*],
654                        declared_variables: &[#(#decls_tokens),*],
655                        inline_templates: &[#(#inline_templates_tokens),*],
656                        source_hash: #source_hash,
657                        consts: &[#(#consts_tokens),*],
658                        imported_consts: &[#(#imported_consts_tokens),*],
659                        name: #name_token,
660                        description: #desc_token,
661                    })
662                }
663                static __TEMPLATE: #crate_path::__private::LazyLock<#crate_path::Template> =
664                    #crate_path::__private::LazyLock::new(__init_template);
665
666                /// Get a reference to the compile-time validated, pre-compiled template.
667                pub fn template() -> &'static #crate_path::Template {
668                    &*__TEMPLATE
669                }
670
671                #struct_tokens
672                #(#type_alias_tokens)*
673            }
674        };
675        expanded.into()
676    })
677}