use std::{
borrow::{Borrow, Cow},
cmp::Ordering,
fmt::Display,
ops::Deref,
};
use heck::{AsKebabCase, AsPascalCase, AsSnekCase};
use itertools::Itertools;
use ploidy_core::{
codegen::{
UniqueNames,
unique::{UniqueNamesScope, WordSegments},
},
ir::{
ExtendableView, InlineTypePathSegment, InlineTypeView, PrimitiveType, SchemaTypeView,
StructFieldName, StructFieldNameHint, UntaggedVariantNameHint,
},
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{IdentFragment, ToTokens, TokenStreamExt};
use ref_cast::{RefCastCustom, ref_cast_custom};
const KEYWORDS: &[&str] = &["crate", "self", "super", "Self"];
#[derive(Clone, Copy, Debug)]
pub enum CodegenTypeName<'a> {
Schema(&'a SchemaTypeView<'a>),
Inline(&'a InlineTypeView<'a>),
}
impl<'a> CodegenTypeName<'a> {
#[inline]
pub fn into_module_name(self) -> CodegenModuleName<'a> {
CodegenModuleName(self)
}
#[inline]
pub fn into_sort_key(self) -> CodegenTypeNameSortKey<'a> {
CodegenTypeNameSortKey(self)
}
}
impl ToTokens for CodegenTypeName<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Schema(view) => {
let ident = view.extensions().get::<CodegenIdent>().unwrap();
CodegenIdentUsage::Type(&ident).to_tokens(tokens);
}
Self::Inline(view) => {
let ident = CodegenIdent::from_segments(view.path().segments);
CodegenIdentUsage::Type(&ident).to_tokens(tokens);
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct CodegenModuleName<'a>(CodegenTypeName<'a>);
impl<'a> CodegenModuleName<'a> {
#[inline]
pub fn into_type_name(self) -> CodegenTypeName<'a> {
self.0
}
pub fn display(&self) -> impl Display {
struct DisplayModuleName<'a>(CodegenTypeName<'a>);
impl Display for DisplayModuleName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
CodegenTypeName::Schema(view) => {
let ident = view.extensions().get::<CodegenIdent>().unwrap();
write!(f, "{}", CodegenIdentUsage::Module(&ident).display())
}
CodegenTypeName::Inline(view) => {
let ident = CodegenIdent::from_segments(view.path().segments);
write!(f, "{}", CodegenIdentUsage::Module(&ident).display())
}
}
}
}
DisplayModuleName(self.0)
}
}
impl ToTokens for CodegenModuleName<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self.0 {
CodegenTypeName::Schema(view) => {
let ident = view.extensions().get::<CodegenIdent>().unwrap();
CodegenIdentUsage::Module(&ident).to_tokens(tokens);
}
CodegenTypeName::Inline(view) => {
let ident = CodegenIdent::from_segments(view.path().segments);
CodegenIdentUsage::Module(&ident).to_tokens(tokens);
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct CodegenTypeNameSortKey<'a>(CodegenTypeName<'a>);
impl<'a> CodegenTypeNameSortKey<'a> {
#[inline]
pub fn for_schema(view: &'a SchemaTypeView<'a>) -> Self {
Self(CodegenTypeName::Schema(view))
}
#[inline]
pub fn for_inline(view: &'a InlineTypeView<'a>) -> Self {
Self(CodegenTypeName::Inline(view))
}
#[inline]
pub fn into_name(self) -> CodegenTypeName<'a> {
self.0
}
}
impl Eq for CodegenTypeNameSortKey<'_> {}
impl Ord for CodegenTypeNameSortKey<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match (&self.0, &other.0) {
(CodegenTypeName::Schema(a), CodegenTypeName::Schema(b)) => a.name().cmp(b.name()),
(CodegenTypeName::Inline(a), CodegenTypeName::Inline(b)) => a.path().cmp(&b.path()),
(CodegenTypeName::Schema(_), CodegenTypeName::Inline(_)) => Ordering::Less,
(CodegenTypeName::Inline(_), CodegenTypeName::Schema(_)) => Ordering::Greater,
}
}
}
impl PartialEq for CodegenTypeNameSortKey<'_> {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl PartialOrd for CodegenTypeNameSortKey<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CodegenIdent(String);
impl CodegenIdent {
pub fn new(s: &str) -> Self {
let s = clean(s);
if KEYWORDS.contains(&s.as_str()) {
Self(format!("_{s}"))
} else {
Self(s)
}
}
pub fn from_segments(segments: &[InlineTypePathSegment<'_>]) -> Self {
Self(format!(
"{}",
segments
.iter()
.map(CodegenTypePathSegment)
.format_with("", |segment, f| f(&segment.display()))
))
}
}
impl AsRef<CodegenIdentRef> for CodegenIdent {
fn as_ref(&self) -> &CodegenIdentRef {
self
}
}
impl Borrow<CodegenIdentRef> for CodegenIdent {
fn borrow(&self) -> &CodegenIdentRef {
self
}
}
impl Deref for CodegenIdent {
type Target = CodegenIdentRef;
fn deref(&self) -> &Self::Target {
CodegenIdentRef::new(&self.0)
}
}
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, RefCastCustom)]
#[repr(transparent)]
pub struct CodegenIdentRef(str);
impl CodegenIdentRef {
#[ref_cast_custom]
fn new(s: &str) -> &Self;
pub fn from_field_name_hint(hint: StructFieldNameHint) -> Cow<'static, Self> {
match hint {
StructFieldNameHint::Index(index) => {
Cow::Owned(CodegenIdent(format!("variant_{index}")))
}
StructFieldNameHint::AdditionalProperties => {
Cow::Borrowed(Self::new("additional_properties"))
}
}
}
}
impl ToOwned for CodegenIdentRef {
type Owned = CodegenIdent;
fn to_owned(&self) -> Self::Owned {
CodegenIdent(self.0.to_owned())
}
}
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CargoFeature {
#[default]
Default,
Named(CodegenIdent),
}
impl CargoFeature {
#[inline]
pub fn from_name(name: &str) -> Self {
match name {
"default" => Self::Default,
name => Self::Named(CodegenIdent::new(name)),
}
}
#[inline]
pub fn as_ident(&self) -> &CodegenIdentRef {
match self {
Self::Named(name) => name,
Self::Default => CodegenIdentRef::new("default"),
}
}
#[inline]
pub fn display(&self) -> impl Display {
match self {
Self::Named(name) => AsKebabCase(name.0.as_str()),
Self::Default => AsKebabCase("default"),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum CodegenIdentUsage<'a> {
Module(&'a CodegenIdentRef),
Type(&'a CodegenIdentRef),
Field(&'a CodegenIdentRef),
Variant(&'a CodegenIdentRef),
Param(&'a CodegenIdentRef),
Method(&'a CodegenIdentRef),
}
impl CodegenIdentUsage<'_> {
pub fn display(self) -> impl Display {
struct DisplayUsage<'a>(CodegenIdentUsage<'a>);
impl Display for DisplayUsage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use CodegenIdentUsage::*;
match self.0 {
Module(name) | Field(name) | Param(name) | Method(name) => {
if name.0.starts_with(unicode_ident::is_xid_start) {
write!(f, "{}", AsSnekCase(&name.0))
} else {
write!(f, "_{}", AsSnekCase(&name.0))
}
}
Type(name) | Variant(name) => {
if name.0.starts_with(unicode_ident::is_xid_start) {
write!(f, "{}", AsPascalCase(&name.0))
} else {
write!(f, "_{}", AsPascalCase(&name.0))
}
}
}
}
}
DisplayUsage(self)
}
}
impl IdentFragment for CodegenIdentUsage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.display())
}
}
impl ToTokens for CodegenIdentUsage<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let s = self.display().to_string();
let ident = syn::parse_str(&s).unwrap_or_else(|_| Ident::new_raw(&s, Span::call_site()));
tokens.append(ident);
}
}
#[derive(Debug)]
pub struct CodegenIdentScope<'a>(UniqueNamesScope<'a>);
impl<'a> CodegenIdentScope<'a> {
pub fn new(arena: &'a UniqueNames) -> Self {
Self::with_reserved(arena, &[])
}
pub fn with_reserved(arena: &'a UniqueNames, reserved: &[&str]) -> Self {
Self(arena.scope_with_reserved(itertools::chain!(
reserved.iter().copied(),
KEYWORDS.iter().copied(),
std::iter::once("")
)))
}
pub fn uniquify(&mut self, name: &str) -> CodegenIdent {
CodegenIdent(self.0.uniquify(&clean(name)).into_owned())
}
}
#[derive(Clone, Copy, Debug)]
pub struct CodegenUntaggedVariantName(pub UntaggedVariantNameHint);
impl ToTokens for CodegenUntaggedVariantName {
fn to_tokens(&self, tokens: &mut TokenStream) {
use UntaggedVariantNameHint::*;
let s = match self.0 {
Primitive(PrimitiveType::String) => "String".into(),
Primitive(PrimitiveType::I8) => "I8".into(),
Primitive(PrimitiveType::U8) => "U8".into(),
Primitive(PrimitiveType::I16) => "I16".into(),
Primitive(PrimitiveType::U16) => "U16".into(),
Primitive(PrimitiveType::I32) => "I32".into(),
Primitive(PrimitiveType::U32) => "U32".into(),
Primitive(PrimitiveType::I64) => "I64".into(),
Primitive(PrimitiveType::U64) => "U64".into(),
Primitive(PrimitiveType::F32) => "F32".into(),
Primitive(PrimitiveType::F64) => "F64".into(),
Primitive(PrimitiveType::Bool) => "Bool".into(),
Primitive(PrimitiveType::DateTime) => "DateTime".into(),
Primitive(PrimitiveType::UnixTime) => "UnixTime".into(),
Primitive(PrimitiveType::Date) => "Date".into(),
Primitive(PrimitiveType::Url) => "Url".into(),
Primitive(PrimitiveType::Uuid) => "Uuid".into(),
Primitive(PrimitiveType::Bytes) => "Bytes".into(),
Primitive(PrimitiveType::Binary) => "Binary".into(),
Array => "Array".into(),
Map => "Map".into(),
Index(index) => Cow::Owned(format!("V{index}")),
};
tokens.append(Ident::new(&s, Span::call_site()));
}
}
#[derive(Clone, Copy, Debug)]
pub struct CodegenTypePathSegment<'a>(&'a InlineTypePathSegment<'a>);
impl<'a> CodegenTypePathSegment<'a> {
pub fn display(&self) -> impl Display {
struct DisplaySegment<'a>(&'a InlineTypePathSegment<'a>);
impl Display for DisplaySegment<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use InlineTypePathSegment::*;
match self.0 {
Operation(name) => write!(f, "{}", AsPascalCase(clean(name))),
Parameter(name) => write!(f, "{}", AsPascalCase(clean(name))),
Request => f.write_str("Request"),
Response => f.write_str("Response"),
Field(StructFieldName::Name(name)) => {
write!(f, "{}", AsPascalCase(clean(name)))
}
Field(StructFieldName::Hint(StructFieldNameHint::Index(index))) => {
write!(f, "Variant{index}")
}
Field(StructFieldName::Hint(StructFieldNameHint::AdditionalProperties)) => {
f.write_str("AdditionalProperties")
}
MapValue => f.write_str("Value"),
ArrayItem => f.write_str("Item"),
Variant(index) => write!(f, "V{index}"),
Parent(index) => write!(f, "P{index}"),
TaggedVariant(name) => write!(f, "{}", AsPascalCase(clean(name))),
}
}
}
DisplaySegment(self.0)
}
}
fn clean(s: &str) -> String {
WordSegments::new(s)
.flat_map(|s| s.split(|c| !unicode_ident::is_xid_continue(c)))
.join("_")
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use syn::parse_quote;
#[test]
fn test_feature_from_name() {
let feature = CargoFeature::from_name("customers");
assert_eq!(feature.display().to_string(), "customers");
}
#[test]
fn test_feature_default() {
let feature = CargoFeature::Default;
assert_eq!(feature.display().to_string(), "default");
let feature = CargoFeature::from_name("default");
assert_eq!(feature, CargoFeature::Default);
}
#[test]
fn test_features_from_multiple_words() {
let feature = CargoFeature::from_name("foo_bar");
assert_eq!(feature.display().to_string(), "foo-bar");
let feature = CargoFeature::from_name("foo.bar");
assert_eq!(feature.display().to_string(), "foo-bar");
let feature = CargoFeature::from_name("fooBar");
assert_eq!(feature.display().to_string(), "foo-bar");
let feature = CargoFeature::from_name("FooBar");
assert_eq!(feature.display().to_string(), "foo-bar");
}
#[test]
fn test_codegen_ident_type() {
let ident = CodegenIdent::new("pet_store");
let usage = CodegenIdentUsage::Type(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(PetStore);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_field() {
let ident = CodegenIdent::new("petStore");
let usage = CodegenIdentUsage::Field(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(pet_store);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_module() {
let ident = CodegenIdent::new("MyModule");
let usage = CodegenIdentUsage::Module(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(my_module);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_variant() {
let ident = CodegenIdent::new("http_error");
let usage = CodegenIdentUsage::Variant(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(HttpError);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_param() {
let ident = CodegenIdent::new("userId");
let usage = CodegenIdentUsage::Param(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(user_id);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_method() {
let ident = CodegenIdent::new("getUserById");
let usage = CodegenIdentUsage::Method(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(get_user_by_id);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_handles_rust_keywords() {
let ident = CodegenIdent::new("type");
let usage = CodegenIdentUsage::Field(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(r#type);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_handles_invalid_start_chars() {
let ident = CodegenIdent::new("123foo");
let usage = CodegenIdentUsage::Field(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(_123_foo);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_handles_special_chars() {
let ident = CodegenIdent::new("foo-bar-baz");
let usage = CodegenIdentUsage::Field(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(foo_bar_baz);
assert_eq!(actual, expected);
}
#[test]
fn test_codegen_ident_handles_number_prefix() {
let ident = CodegenIdent::new("1099KStatus");
let usage = CodegenIdentUsage::Field(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(_1099_k_status);
assert_eq!(actual, expected);
let usage = CodegenIdentUsage::Type(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(_1099KStatus);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_string() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::String));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(String);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_i32() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::I32));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(I32);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_i64() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::I64));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(I64);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_f32() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::F32));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(F32);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_f64() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::F64));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(F64);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_bool() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Bool));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Bool);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_datetime() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::DateTime));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(DateTime);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_date() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Date));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Date);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_url() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Url));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Url);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_uuid() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Uuid));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Uuid);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_bytes() {
let variant_name =
CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Bytes));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Bytes);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_index() {
let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Index(0));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(V0);
assert_eq!(actual, expected);
let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Index(42));
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(V42);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_array() {
let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Array);
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Array);
assert_eq!(actual, expected);
}
#[test]
fn test_untagged_variant_name_map() {
let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Map);
let actual: syn::Ident = parse_quote!(#variant_name);
let expected: syn::Ident = parse_quote!(Map);
assert_eq!(actual, expected);
}
#[test]
fn test_struct_field_name_index() {
let field_name = CodegenIdentRef::from_field_name_hint(StructFieldNameHint::Index(0));
let usage = CodegenIdentUsage::Field(&field_name);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(variant_0);
assert_eq!(actual, expected);
let field_name = CodegenIdentRef::from_field_name_hint(StructFieldNameHint::Index(5));
let usage = CodegenIdentUsage::Field(&field_name);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(variant_5);
assert_eq!(actual, expected);
}
#[test]
fn test_struct_field_name_additional_properties() {
let field_name =
CodegenIdentRef::from_field_name_hint(StructFieldNameHint::AdditionalProperties);
let usage = CodegenIdentUsage::Field(&field_name);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(additional_properties);
assert_eq!(actual, expected);
}
#[test]
fn test_clean() {
assert_eq!(clean("foo-bar"), "foo_bar");
assert_eq!(clean("foo.bar"), "foo_bar");
assert_eq!(clean("foo bar"), "foo_bar");
assert_eq!(clean("foo@bar"), "foo_bar");
assert_eq!(clean("foo#bar"), "foo_bar");
assert_eq!(clean("foo!bar"), "foo_bar");
assert_eq!(clean("foo_bar"), "foo_bar");
assert_eq!(clean("FooBar"), "Foo_Bar");
assert_eq!(clean("foo123"), "foo123");
assert_eq!(clean("_foo"), "foo");
assert_eq!(clean("_foo"), "foo");
assert_eq!(clean("__foo"), "foo");
assert_eq!(clean("123foo"), "123_foo");
assert_eq!(clean("9bar"), "9_bar");
assert_eq!(clean("café"), "café");
assert_eq!(clean("foo™bar"), "foo_bar");
assert_eq!(clean("foo---bar"), "foo_bar");
assert_eq!(clean("foo...bar"), "foo_bar");
}
#[test]
fn test_codegen_ident_scope_handles_empty() {
let unique = UniqueNames::new();
let mut scope = CodegenIdentScope::new(&unique);
let ident = scope.uniquify("");
let usage = CodegenIdentUsage::Field(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(_2);
assert_eq!(actual, expected);
let usage = CodegenIdentUsage::Type(&ident);
let actual: syn::Ident = parse_quote!(#usage);
let expected: syn::Ident = parse_quote!(_2);
assert_eq!(actual, expected);
}
}