miniextendr_macros/typed_external_macro.rs
1//! `impl_typed_external!` — explicit monomorphization of `TypedExternal` for generic types.
2//!
3//! Since `#[derive(ExternalPtr)]` rejects generic types (the generated .Call entrypoints
4//! and R wrapper code cannot be generic), this macro provides an alternative for users
5//! who want to use `ExternalPtr<T>` with a specific monomorphization of a generic type.
6//!
7//! # Usage
8//!
9//! ```ignore
10//! impl_typed_external!(MyWrapper<i32>);
11//! impl_typed_external!(MyWrapper<String>);
12//! impl_typed_external!(TreeNode<String, Vec<u8>>);
13//! ```
14//!
15//! # Generated Code
16//!
17//! For `impl_typed_external!(MyWrapper<i32>)`:
18//!
19//! ```ignore
20//! impl TypedExternal for MyWrapper<i32> {
21//! const TYPE_NAME: &'static str = "MyWrapper<i32>";
22//! const TYPE_NAME_CSTR: &'static [u8] = b"MyWrapper<i32>\0";
23//! const TYPE_ID_CSTR: &'static [u8] =
24//! concat!(env!("CARGO_PKG_NAME"), "@", env!("CARGO_PKG_VERSION"),
25//! "::", module_path!(), "::MyWrapper<i32>\0").as_bytes();
26//! }
27//!
28//! impl IntoExternalPtr for MyWrapper<i32> {}
29//! ```
30
31use proc_macro2::TokenStream;
32
33/// Implementation of `impl_typed_external!`.
34///
35/// Accepts a concrete type path with generic arguments filled in
36/// (e.g., `MyWrapper<i32>`, `TreeNode<String, Vec<u8>>`).
37///
38/// Returns a `TokenStream` containing:
39/// - `impl TypedExternal for <type> { ... }`
40/// - `impl IntoExternalPtr for <type> {}`
41pub fn impl_typed_external(input: TokenStream) -> syn::Result<TokenStream> {
42 // Parse the input as a Type (supports paths with generic args)
43 let ty: syn::Type = syn::parse2(input)?;
44
45 // Extract a display name for the type. We use the token representation
46 // which naturally includes angle brackets and generic args.
47 let type_display = quote::quote!(#ty).to_string();
48 // Clean up spacing: `MyWrapper < i32 >` → `MyWrapper<i32>`
49 let type_display = type_display
50 .replace(" < ", "<")
51 .replace(" > ", ">")
52 .replace("< ", "<")
53 .replace(" >", ">");
54
55 let name_lit = syn::LitStr::new(&type_display, proc_macro2::Span::call_site());
56 let name_cstr_bytes = format!("{}\0", type_display);
57 let name_cstr =
58 syn::LitByteStr::new(name_cstr_bytes.as_bytes(), proc_macro2::Span::call_site());
59
60 Ok(quote::quote! {
61 impl ::miniextendr_api::externalptr::TypedExternal for #ty {
62 const TYPE_NAME: &'static str = #name_lit;
63 const TYPE_NAME_CSTR: &'static [u8] = #name_cstr;
64 const TYPE_ID_CSTR: &'static [u8] =
65 concat!(
66 env!("CARGO_PKG_NAME"), "@", env!("CARGO_PKG_VERSION"),
67 "::", module_path!(), "::", #name_lit, "\0"
68 ).as_bytes();
69 }
70
71 impl ::miniextendr_api::externalptr::IntoExternalPtr for #ty {}
72 })
73}