1//! Small cross-cutting helpers used by multiple macro modules.
23/// Extract `#[cfg(...)]` attributes from a list of attributes.
4///
5/// These should be propagated to generated items so they are conditionally
6/// compiled along with the original function.
7pub(crate) fn extract_cfg_attrs(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
8attrs9 .iter()
10 .filter(|attr| attr.path().is_ident("cfg"))
11 .cloned()
12 .collect()
13}
1415/// Rewrites a leading `Self` in `path` to `type_ident`: a path an attribute
16/// names (`#[r_data(get = "Self::f")]`, `fallback = "Self::f"`) is called
17/// from a free function the macro emits outside any impl, where `Self` means
18/// nothing.
19pub(crate) fn resolve_self(path: &mut syn::Path, type_ident: &syn::Ident) {
20if let Some(first) = path.segments.first_mut()
21 && first.ident == "Self"
22{
23first.ident = syn::Ident::new(&type_ident.to_string(), first.ident.span());
24 }
25}
2627/// Format a human-readable source location note from a syntax span.
28///
29/// Column is reported as 1-based for consistency with editor displays.
30pub(crate) fn source_location_doc(span: proc_macro2::Span) -> String {
31let start = span.start();
32::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Generated from source location line {0}, column {1}.",
start.line, start.column + 1))
})format!(
33"Generated from source location line {}, column {}.",
34 start.line,
35 start.column + 1
36)37}
3839/// Build a `TokenStream` containing a raw string literal from an R wrapper string.
40///
41/// Every generated wrapper is finalized here, so this is also where calls a
42/// formal would shadow are qualified: `formals` holds every R formal of the
43/// wrappers in `s`, and each call to a base function that shares a name with
44/// one of them is written `base::name(...)`
45/// ([`qualify_shadowed_calls`](crate::r_shadowing::qualify_shadowed_calls)).
46///
47/// The delimiter has one more `#` than the longest run of `#` after a `"` in
48/// the wrapper, so a doc comment quoting `"# a footer"` or a default of `"#"`
49/// can't end the literal early (#2111).
50pub(crate) fn r_wrapper_raw_literal(
51 s: &str,
52 formals: &std::collections::BTreeSet<String>,
53) -> proc_macro2::TokenStream {
54use std::str::FromStr;
55let s = crate::r_shadowing::qualify_shadowed_calls(s, formals);
56let hashes = "#".repeat(raw_string_hashes(&s));
57let raw = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("r{0}\"\n{1}\n\"{0}", hashes, s))
})format!("r{hashes}\"\n{s}\n\"{hashes}");
58 proc_macro2::TokenStream::from_str(&raw).expect("valid raw string literal")
59}
6061/// How many `#` a raw string literal holding `s` needs: one more than the
62/// longest run of `#` that follows a `"` in `s`.
63fn raw_string_hashes(s: &str) -> usize {
64let longest = s65 .match_indices('"')
66 .map(|(i, _)| s[i + 1..].bytes().take_while(|&b| b == b'#').count())
67 .max()
68 .unwrap_or(0);
69longest + 1
70}
7172#[cfg(test)]
73mod tests {
74use super::*;
7576/// The literal parses back to the wrapper text, newline-framed, however
77 /// many `#` follow a quote in it (#2111).
78#[test]
79fn raw_literal_holds_quote_hash_runs() {
80let formals = std::collections::BTreeSet::new();
81for text in [
82"plain",
83"#' the last line reads \"# Not shown: sd\".",
84"f <- function(sep = \"#\") sep",
85"\"##\" and \"###x\"",
86"ends with a quote\"",
87 ] {
88let literal: syn::LitStr = syn::parse2(r_wrapper_raw_literal(text, &formals))
89 .unwrap_or_else(|e| panic!("{text:?}: {e}"));
90assert_eq!(literal.value(), format!("\n{text}\n"));
91 }
92 }
9394#[test]
95fn raw_string_hashes_counts_runs_after_quotes() {
96assert_eq!(raw_string_hashes("no quotes ##"), 1);
97assert_eq!(raw_string_hashes("\"x\""), 1);
98assert_eq!(raw_string_hashes("\"#"), 2);
99assert_eq!(raw_string_hashes("\"## then \"#"), 3);
100 }
101}