Skip to main content

miniextendr_macros/
match_arg_keys.rs

1//! Write-time placeholder & symbol-name formatting for the match_arg pipeline.
2//!
3//! All four shapes (`choices_placeholder`, `param_doc_placeholder`,
4//! `choices_helper_c_name`, `choices_helper_def_ident`) share the same
5//! `{c_ident_without_prefix}_{r_param}` stem so the cdylib's write-time pass
6//! can correlate them. Keep them together so the shape can't drift.
7//!
8//! The `c_ident_without_prefix` input has `C_` already stripped (or is a stem
9//! already, e.g. `MyType__method`). Callers that have a full `c_ident` should
10//! pass `c_ident.trim_start_matches("C_")`.
11//!
12//! All four helpers call `c_stem(...)` internally so callers may also pass a
13//! full `c_ident` (e.g. `"C_my_fn"`) — the `C_` prefix is normalized away.
14
15fn c_stem(c_ident: &str) -> &str {
16    c_ident.trim_start_matches("C_")
17}
18
19/// R-side placeholder that the cdylib resolves to a `c("a", "b", ...)` literal
20/// at write time. Substituted by `MX_MATCH_ARG_CHOICES` entries.
21pub(crate) fn choices_placeholder(c_ident: &str, r_param: &str) -> String {
22    format!(".__MX_MATCH_ARG_CHOICES_{}_{}__", c_stem(c_ident), r_param)
23}
24
25/// R-side placeholder for the `@param` doc line, substituted by
26/// `MX_MATCH_ARG_PARAM_DOCS` entries at write time. See #210.
27pub(crate) fn param_doc_placeholder(c_ident: &str, r_param: &str) -> String {
28    format!(
29        ".__MX_MATCH_ARG_PARAM_DOC_{}_{}__",
30        c_stem(c_ident),
31        r_param
32    )
33}
34
35/// C symbol name for the helper fn that returns the enum's choices SEXP,
36/// called from the R wrapper's match.arg prelude.
37pub(crate) fn choices_helper_c_name(c_ident: &str, r_param: &str) -> String {
38    format!("C_{}__match_arg_choices__{}", c_stem(c_ident), r_param)
39}
40
41/// Rust ident holding the `R_CallMethodDef` for the match_arg choices helper.
42pub(crate) fn choices_helper_def_ident(c_ident: &str, r_param: &str) -> syn::Ident {
43    quote::format_ident!(
44        "call_method_def_{}",
45        choices_helper_c_name(c_ident, r_param)
46    )
47}
48
49/// Extract the unquoted form of a user-supplied `default = "..."` literal
50/// for a `match_arg` parameter.
51///
52/// The user writes the value as it appears in R source, so a string default
53/// is `default = "\"zstd\""` — the `String` we receive is `"zstd"` (with the
54/// quote chars). Strip the outer quotes if present; otherwise pass the raw
55/// value through unchanged. The write-time pass validates the result against
56/// the enum's `CHOICES` and panics on miss, so a malformed literal (e.g.
57/// `default = "1L"`) still surfaces as a clear runtime error at cdylib load.
58pub(crate) fn extract_match_arg_default(raw: &str) -> String {
59    raw.strip_prefix('"')
60        .and_then(|s| s.strip_suffix('"'))
61        .unwrap_or(raw)
62        .to_string()
63}
64
65/// Derive a safe Rust ident from a write-time placeholder string.
66///
67/// Strips surrounding underscores and turns every `.` into `_`, so a placeholder
68/// like `.__MX_MATCH_ARG_CHOICES_foo_bar__` becomes an ident suffix that quotes
69/// cleanly in emitted code.
70pub(crate) fn placeholder_ident_suffix(placeholder: &str) -> String {
71    placeholder.trim_matches('_').replace('.', "_")
72}
73
74/// Emit the `MX_MATCH_ARG_CHOICES` static + its linkme registration.
75///
76/// Factored so lib.rs (standalone fns) and miniextendr_impl.rs (impl methods)
77/// can't drift apart — both previously open-coded the same quote! block.
78///
79/// `preferred_default` is the unquoted form of the user's `default = "..."`
80/// (e.g. `"zstd"`). Pass `""` when the user supplied no default — the write
81/// pass then keeps the natural enum order.
82pub(crate) fn choices_entry_tokens(
83    cfg_attrs: &[syn::Attribute],
84    entry_ident: &syn::Ident,
85    placeholder: &str,
86    choices_ty: &syn::Type,
87    preferred_default: &str,
88) -> proc_macro2::TokenStream {
89    quote::quote! {
90        #(#cfg_attrs)*
91        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_MATCH_ARG_CHOICES), linkme(crate = ::miniextendr_api::linkme))]
92        #[allow(non_upper_case_globals)]
93        #[allow(non_snake_case)]
94        static #entry_ident: ::miniextendr_api::registry::MatchArgChoicesEntry =
95            ::miniextendr_api::registry::MatchArgChoicesEntry {
96                placeholder: #placeholder,
97                choices_str: || {
98                    <#choices_ty as ::miniextendr_api::match_arg::MatchArg>::CHOICES
99                        .iter()
100                        .map(|c| format!(
101                            "\"{}\"",
102                            ::miniextendr_api::match_arg::escape_r_string(c)
103                        ))
104                        .collect::<Vec<_>>()
105                        .join(", ")
106                },
107                preferred_default: #preferred_default,
108            };
109    }
110}
111
112/// Emit the `MX_MATCH_ARG_PARAM_DOCS` static + its linkme registration.
113pub(crate) fn param_doc_entry_tokens(
114    cfg_attrs: &[syn::Attribute],
115    entry_ident: &syn::Ident,
116    placeholder: &str,
117    several_ok: bool,
118    choices_ty: &syn::Type,
119) -> proc_macro2::TokenStream {
120    quote::quote! {
121        #(#cfg_attrs)*
122        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_MATCH_ARG_PARAM_DOCS), linkme(crate = ::miniextendr_api::linkme))]
123        #[allow(non_upper_case_globals)]
124        #[allow(non_snake_case)]
125        static #entry_ident: ::miniextendr_api::registry::MatchArgParamDocEntry =
126            ::miniextendr_api::registry::MatchArgParamDocEntry {
127                placeholder: #placeholder,
128                several_ok: #several_ok,
129                choices_str: || {
130                    <#choices_ty as ::miniextendr_api::match_arg::MatchArg>::CHOICES
131                        .iter()
132                        .map(|c| format!("\"{}\"", c))
133                        .collect::<Vec<_>>()
134                        .join(", ")
135                },
136            };
137    }
138}