use proc_macro2::{Ident, Span};
use syn::token;
pub fn make_abi(abi: &str) -> syn::Abi {
syn::Abi {
extern_token: token::Extern::default(),
name: Some(syn::LitStr::new(abi, Span::call_site())),
}
}
pub fn ident(name: &str) -> Ident {
debug_assert!(
!name.contains('(') && !name.contains('{'),
"ident received pattern syntax '{}'. This is likely a bug - patterns should not be passed to ident().",
name
);
if let Some(raw_name) = name.strip_prefix("r#") {
Ident::new_raw(raw_name, Span::call_site())
} else {
let sanitized = sanitize_ident(name);
Ident::new(&sanitized, Span::call_site())
}
}
fn sanitize_ident(name: &str) -> String {
if name.is_empty() {
return "_empty".to_string();
}
if is_valid_ident(name) {
return name.to_string();
}
let mut result = String::with_capacity(name.len() + 1);
let mut chars = name.chars().peekable();
if let Some(&first) = chars.peek() {
if first.is_ascii_digit() {
result.push('_');
}
}
for c in chars {
if c.is_alphanumeric() || c == '_' {
result.push(c);
} else {
result.push('_');
}
}
if result.is_empty() || result.chars().all(|c| c == '_') {
return "_param".to_string();
}
result
}
fn is_valid_ident(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_alphanumeric() || c == '_')
}
use super::ToSynError;
pub fn try_parse_path(path: &str) -> Result<syn::Path, ToSynError> {
syn::parse_str(path).map_err(|e| ToSynError::ParsePath {
input: path.to_string(),
message: e.to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ident_simple() {
let id = ident("foo");
assert_eq!(id.to_string(), "foo");
}
#[test]
fn test_ident_raw() {
let id = ident("r#async");
assert_eq!(id.to_string(), "r#async");
}
#[test]
fn test_try_parse_path_simple() {
let path = try_parse_path("std::io::Result").unwrap();
assert_eq!(path.segments.len(), 3);
}
#[test]
fn test_try_parse_path_with_generics() {
let path = try_parse_path("Vec<String>").unwrap();
assert_eq!(path.segments.len(), 1);
}
#[test]
fn test_try_parse_path_complex_generics() {
let path = try_parse_path("HashMap<String, Vec<i32>>").unwrap();
assert_eq!(path.segments.len(), 1);
}
#[test]
fn test_try_parse_path_rejects_pattern_syntax() {
assert!(try_parse_path("(crate_name, path)").is_err());
assert!(try_parse_path("Foo { x }").is_err());
}
#[test]
fn test_try_parse_path_tuple_in_generics() {
let path = try_parse_path("Vec<(String, String)>").unwrap();
assert_eq!(path.segments.len(), 1);
assert_eq!(path.segments[0].ident.to_string(), "Vec");
}
#[test]
#[cfg_attr(
debug_assertions,
should_panic(expected = "ident received pattern syntax")
)]
fn test_sanitize_tuple_pattern() {
let id = ident("(crate_name , path)");
assert!(!id.to_string().contains('('));
assert!(!id.to_string().contains(')'));
}
#[test]
fn test_sanitize_empty() {
let id = ident("");
assert_eq!(id.to_string(), "_empty");
}
#[test]
fn test_sanitize_starting_with_digit() {
let id = ident("123abc");
assert!(id.to_string().starts_with('_'));
}
}