1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::ToTokens;
8use quote::format_ident;
9use quote::quote;
10use syn::Data;
11use syn::DataEnum;
12use syn::DataStruct;
13use syn::DeriveInput;
14use syn::Fields;
15use syn::GenericArgument;
16use syn::GenericParam;
17use syn::Ident;
18use syn::ItemFn;
19use syn::ItemImpl;
20use syn::PathArguments;
21use syn::Token;
22use syn::Type;
23use syn::Visibility;
24use syn::parse::Parse;
25use syn::parse::ParseStream;
26use syn::parse_macro_input;
27use syn::punctuated::Punctuated;
28
29#[proc_macro_derive(RefView, attributes(ref_view))]
30pub fn derive_ref_view(input: TokenStream) -> TokenStream {
31 let input = parse_macro_input!(input as DeriveInput);
32 expand_ref_view(input)
33 .unwrap_or_else(|err| err.to_compile_error())
34 .into()
35}
36
37#[proc_macro_attribute]
38pub fn impl_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
39 let args = parse_macro_input!(attr as ImplTraitArgs);
40 let item = parse_macro_input!(item as ItemImpl);
41
42 expand_impl_trait(args, item)
43 .unwrap_or_else(|err| err.to_compile_error())
44 .into()
45}
46
47#[proc_macro_attribute]
48pub fn impl_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
49 let trait_path = parse_macro_input!(attr as syn::Path);
50 let item = parse_macro_input!(item as ItemFn);
51
52 expand_impl_fn(trait_path, item)
53 .unwrap_or_else(|err| err.to_compile_error())
54 .into()
55}
56
57fn expand_ref_view(input: DeriveInput) -> syn::Result<TokenStream2> {
58 if !input.generics.params.is_empty() {
59 return Err(syn::Error::new_spanned(
60 input.generics,
61 "RefView does not support generic types yet",
62 ));
63 }
64
65 let type_ident = input.ident;
66 let vis = input.vis;
67 let config = Config::parse(&input.attrs, &type_ident)?;
68
69 match input.data {
70 Data::Struct(data) => expand_struct(&vis, &type_ident, data, config),
71 Data::Enum(data) => expand_enum(&vis, &type_ident, data, config),
72 Data::Union(data) => Err(syn::Error::new_spanned(
73 data.union_token,
74 "RefView does not support unions",
75 )),
76 }
77}
78
79#[derive(Debug, Default)]
80struct Config {
81 trait_name: Option<Ident>,
82 derives: Vec<syn::Path>,
83 views: Vec<ViewSpec>,
84}
85
86#[derive(Debug, Clone)]
87struct ViewSpec {
88 name: Ident,
89 omitted: BTreeSet<OmitTarget>,
90}
91
92#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
93enum OmitTarget {
94 Field(String),
95 VariantField { variant: String, field: String },
96}
97
98impl Config {
99 fn parse(attrs: &[syn::Attribute], type_ident: &Ident) -> syn::Result<Self> {
100 let mut config = Config::default();
101
102 for attr in attrs {
103 if !attr.path().is_ident("ref_view") {
104 continue;
105 }
106
107 let mut view_name = None;
108 let mut omitted = BTreeSet::new();
109
110 attr.parse_nested_meta(|meta| {
111 if meta.path.is_ident("trait_name") {
112 let value = meta.value()?;
113 config.trait_name = Some(value.parse()?);
114 return Ok(());
115 }
116
117 if meta.path.is_ident("name") {
118 let value = meta.value()?;
119 view_name = Some(value.parse()?);
120 return Ok(());
121 }
122
123 if meta.path.is_ident("derive") {
124 let content;
125 syn::parenthesized!(content in meta.input);
126 while !content.is_empty() {
127 config.derives.push(content.parse()?);
128 if content.is_empty() {
129 break;
130 }
131 content.parse::<Token![,]>()?;
132 }
133 return Ok(());
134 }
135
136 if meta.path.is_ident("omit") {
137 let content;
138 syn::parenthesized!(content in meta.input);
139 while !content.is_empty() {
140 omitted.insert(parse_omit_target(&content)?);
141 if content.is_empty() {
142 break;
143 }
144 content.parse::<Token![,]>()?;
145 }
146 return Ok(());
147 }
148
149 Err(meta.error("unsupported ref_view option"))
150 })?;
151
152 if let Some(name) = view_name {
153 config.views.push(ViewSpec { name, omitted });
154 }
155 }
156
157 let default_ref = format_ident!("{}Ref", type_ident);
158 if !config.views.iter().any(|view| view.name == default_ref) {
159 config.views.insert(
160 0,
161 ViewSpec {
162 name: default_ref,
163 omitted: BTreeSet::new(),
164 },
165 );
166 }
167
168 Ok(config)
169 }
170
171 fn trait_ident(&self, type_ident: &Ident) -> Ident {
172 self.trait_name
173 .clone()
174 .unwrap_or_else(|| format_ident!("{}View", type_ident))
175 }
176
177 fn view_derives(&self) -> Vec<syn::Path> {
178 let mut derives = vec![syn::parse_quote!(Clone), syn::parse_quote!(Copy)];
179 let mut seen = derives.iter().map(type_key_path).collect::<BTreeSet<_>>();
180
181 for derive in &self.derives {
182 if seen.insert(type_key_path(derive)) {
183 derives.push(derive.clone());
184 }
185 }
186
187 derives
188 }
189}
190
191fn parse_omit_target(input: syn::parse::ParseStream) -> syn::Result<OmitTarget> {
192 let first = input.parse::<Ident>()?;
193 if input.peek(Token![.]) {
194 input.parse::<Token![.]>()?;
195 let second = input.parse::<Ident>()?;
196 Ok(OmitTarget::VariantField {
197 variant: first.to_string(),
198 field: second.to_string(),
199 })
200 } else {
201 Ok(OmitTarget::Field(first.to_string()))
202 }
203}
204
205#[derive(Debug, Clone)]
206struct FieldInfo {
207 ident: Ident,
208 ty: Type,
209 option_inner: Option<Type>,
210 returns_option: bool,
211}
212
213impl FieldInfo {
214 fn new(ident: Ident, ty: Type) -> Self {
215 let option_inner = option_inner_type(&ty);
216 Self {
217 ident,
218 ty,
219 option_inner,
220 returns_option: false,
221 }
222 }
223
224 fn accessor_return_ty(&self) -> TokenStream2 {
225 let ty = self.return_inner_ty();
226 if self.returns_option || self.option_inner.is_some() {
227 quote! { ::core::option::Option<&#ty> }
228 } else {
229 quote! { & #ty }
230 }
231 }
232
233 fn return_inner_ty(&self) -> &Type {
234 self.option_inner.as_ref().unwrap_or(&self.ty)
235 }
236
237 fn view_field_ty(&self) -> TokenStream2 {
238 let ty = &self.ty;
239 quote! { &'a #ty }
240 }
241
242 fn full_value_expr(&self, receiver: TokenStream2) -> TokenStream2 {
243 let ident = &self.ident;
244 if self.option_inner.is_some() {
245 quote! { #receiver.#ident.as_ref() }
246 } else if self.returns_option {
247 quote! { ::core::option::Option::Some(&#receiver.#ident) }
248 } else {
249 quote! { &#receiver.#ident }
250 }
251 }
252
253 fn view_value_expr(&self, receiver: TokenStream2) -> TokenStream2 {
254 let ident = &self.ident;
255 if self.option_inner.is_some() {
256 quote! { #receiver.#ident.as_ref() }
257 } else if self.returns_option {
258 quote! { ::core::option::Option::Some(#receiver.#ident) }
259 } else {
260 quote! { #receiver.#ident }
261 }
262 }
263
264 fn enum_field_expr(&self, field: TokenStream2) -> TokenStream2 {
265 if self.option_inner.is_some() {
266 quote! { #field.as_ref() }
267 } else {
268 quote! { ::core::option::Option::Some(#field) }
269 }
270 }
271}
272
273fn expand_struct(
274 vis: &Visibility,
275 type_ident: &Ident,
276 data: DataStruct,
277 config: Config,
278) -> syn::Result<TokenStream2> {
279 let Fields::Named(fields) = data.fields else {
280 return Err(syn::Error::new_spanned(
281 data.struct_token,
282 "RefView only supports structs with named fields",
283 ));
284 };
285
286 let mut field_infos = fields
287 .named
288 .into_iter()
289 .map(|field| {
290 let ident = field.ident.expect("named field");
291 FieldInfo::new(ident, field.ty)
292 })
293 .collect::<Vec<_>>();
294
295 let omitted_fields = config
296 .views
297 .iter()
298 .flat_map(|view| view.omitted.iter())
299 .map(|target| match target {
300 OmitTarget::Field(field) => Ok(field.clone()),
301 OmitTarget::VariantField { .. } => Err(syn::Error::new(
302 Span::call_site(),
303 "struct omit targets must use `field`, not `Variant.field`",
304 )),
305 })
306 .collect::<syn::Result<BTreeSet<_>>>()?;
307
308 for field in &mut field_infos {
309 field.returns_option =
310 field.option_inner.is_some() || omitted_fields.contains(&field.ident.to_string());
311 }
312
313 validate_struct_omits(&field_infos, &omitted_fields)?;
314
315 let trait_ident = config.trait_ident(type_ident);
316 let view_derives = config.view_derives();
317 let trait_def = generate_trait(vis, &trait_ident, &field_infos);
318 let source_trait_impl =
319 generate_struct_source_trait_impl(&trait_ident, type_ident, &field_infos);
320 let views = config
321 .views
322 .iter()
323 .map(|view| {
324 generate_struct_view(
325 vis,
326 type_ident,
327 &trait_ident,
328 &field_infos,
329 view,
330 &view_derives,
331 )
332 })
333 .collect::<syn::Result<Vec<_>>>()?;
334
335 Ok(quote! {
336 #trait_def
337 #source_trait_impl
338 #(#views)*
339 })
340}
341
342fn validate_struct_omits(
343 fields: &[FieldInfo],
344 omitted_fields: &BTreeSet<String>,
345) -> syn::Result<()> {
346 let known = fields
347 .iter()
348 .map(|field| field.ident.to_string())
349 .collect::<BTreeSet<_>>();
350 for omitted in omitted_fields {
351 if !known.contains(omitted) {
352 return Err(syn::Error::new(
353 Span::call_site(),
354 format!("unknown omitted field `{}`", omitted),
355 ));
356 }
357 }
358 Ok(())
359}
360
361fn generate_trait(vis: &Visibility, trait_ident: &Ident, fields: &[FieldInfo]) -> TokenStream2 {
362 let methods = fields.iter().map(|field| {
363 let ident = &field.ident;
364 let return_ty = field.accessor_return_ty();
365 quote! {
366 fn #ident(&self) -> #return_ty;
367 }
368 });
369
370 quote! {
371 #vis trait #trait_ident {
372 #(#methods)*
373 }
374 }
375}
376
377fn generate_struct_source_trait_impl(
378 trait_ident: &Ident,
379 type_ident: &Ident,
380 fields: &[FieldInfo],
381) -> TokenStream2 {
382 let methods = fields.iter().map(|field| {
383 let ident = &field.ident;
384 let return_ty = field.accessor_return_ty();
385 let value = field.full_value_expr(quote! { self });
386 quote! {
387 fn #ident(&self) -> #return_ty {
388 #value
389 }
390 }
391 });
392
393 quote! {
394 impl #trait_ident for #type_ident {
395 #(#methods)*
396 }
397 }
398}
399
400fn generate_struct_view(
401 vis: &Visibility,
402 type_ident: &Ident,
403 trait_ident: &Ident,
404 fields: &[FieldInfo],
405 view: &ViewSpec,
406 view_derives: &[syn::Path],
407) -> syn::Result<TokenStream2> {
408 let view_ident = &view.name;
409 let omitted = view
410 .omitted
411 .iter()
412 .map(|target| match target {
413 OmitTarget::Field(field) => Ok(field.clone()),
414 OmitTarget::VariantField { .. } => Err(syn::Error::new(
415 Span::call_site(),
416 "struct omit targets must use `field`, not `Variant.field`",
417 )),
418 })
419 .collect::<syn::Result<BTreeSet<_>>>()?;
420
421 let included_fields = fields
422 .iter()
423 .filter(|field| !omitted.contains(&field.ident.to_string()))
424 .collect::<Vec<_>>();
425
426 let struct_fields = included_fields.iter().map(|field| {
427 let ident = &field.ident;
428 let ty = field.view_field_ty();
429 quote! { #ident: #ty }
430 });
431
432 let from_bindings = included_fields.iter().map(|field| {
433 let ident = &field.ident;
434 quote! { #ident: &value.#ident }
435 });
436
437 let methods = fields.iter().map(|field| {
438 let ident = &field.ident;
439 let return_ty = field.accessor_return_ty();
440 let body = if omitted.contains(&field.ident.to_string()) {
441 quote! { ::core::option::Option::None }
442 } else {
443 field.view_value_expr(quote! { self })
444 };
445 quote! {
446 fn #ident(&self) -> #return_ty {
447 #body
448 }
449 }
450 });
451
452 Ok(quote! {
453 #[derive(#(#view_derives),*)]
454 #vis struct #view_ident<'a> {
455 #(#struct_fields,)*
456 }
457
458 impl<'a> ::core::convert::From<&'a #type_ident> for #view_ident<'a> {
459 fn from(value: &'a #type_ident) -> Self {
460 Self {
461 #(#from_bindings,)*
462 }
463 }
464 }
465
466 impl<'a> #trait_ident for #view_ident<'a> {
467 #(#methods)*
468 }
469 })
470}
471
472#[derive(Debug, Clone)]
473struct VariantInfo {
474 ident: Ident,
475 fields: Vec<FieldInfo>,
476}
477
478fn expand_enum(
479 vis: &Visibility,
480 type_ident: &Ident,
481 data: DataEnum,
482 config: Config,
483) -> syn::Result<TokenStream2> {
484 let variants = data
485 .variants
486 .into_iter()
487 .map(|variant| {
488 let Fields::Named(fields) = variant.fields else {
489 return Err(syn::Error::new_spanned(
490 variant.ident,
491 "RefView only supports enum variants with named fields",
492 ));
493 };
494
495 let fields = fields
496 .named
497 .into_iter()
498 .map(|field| {
499 let ident = field.ident.expect("named field");
500 let mut info = FieldInfo::new(ident, field.ty);
501 info.returns_option = true;
502 info
503 })
504 .collect::<Vec<_>>();
505
506 Ok(VariantInfo {
507 ident: variant.ident,
508 fields,
509 })
510 })
511 .collect::<syn::Result<Vec<_>>>()?;
512
513 let fields = collect_enum_fields(&variants)?;
514 validate_enum_omits(&variants, &config.views)?;
515
516 let trait_ident = config.trait_ident(type_ident);
517 let view_derives = config.view_derives();
518 let trait_def = generate_trait(vis, &trait_ident, &fields);
519 let source_trait_impl =
520 generate_enum_source_trait_impl(&trait_ident, type_ident, &variants, &fields);
521 let views = config
522 .views
523 .iter()
524 .map(|view| {
525 generate_enum_view(
526 vis,
527 type_ident,
528 &trait_ident,
529 &variants,
530 &fields,
531 view,
532 &view_derives,
533 )
534 })
535 .collect::<syn::Result<Vec<_>>>()?;
536
537 Ok(quote! {
538 #trait_def
539 #source_trait_impl
540 #(#views)*
541 })
542}
543
544fn collect_enum_fields(variants: &[VariantInfo]) -> syn::Result<Vec<FieldInfo>> {
545 let mut fields = BTreeMap::<String, FieldInfo>::new();
546 for variant in variants {
547 for field in &variant.fields {
548 let name = field.ident.to_string();
549 if let Some(existing) = fields.get(&name) {
550 if type_key(existing.return_inner_ty()) != type_key(field.return_inner_ty()) {
551 return Err(syn::Error::new_spanned(
552 &field.ident,
553 format!(
554 "field `{}` appears with different types across variants",
555 name
556 ),
557 ));
558 }
559 } else {
560 fields.insert(name, field.clone());
561 }
562 }
563 }
564 Ok(fields.into_values().collect())
565}
566
567fn validate_enum_omits(variants: &[VariantInfo], views: &[ViewSpec]) -> syn::Result<()> {
568 let known = variants
569 .iter()
570 .flat_map(|variant| {
571 let variant_name = variant.ident.to_string();
572 variant
573 .fields
574 .iter()
575 .map(move |field| (variant_name.clone(), field.ident.to_string()))
576 })
577 .collect::<BTreeSet<_>>();
578
579 for view in views {
580 for target in &view.omitted {
581 match target {
582 OmitTarget::VariantField { variant, field } => {
583 if !known.contains(&(variant.clone(), field.clone())) {
584 return Err(syn::Error::new(
585 Span::call_site(),
586 format!("unknown omitted field `{}.{}`", variant, field),
587 ));
588 }
589 }
590 OmitTarget::Field(field) => {
591 if !known.iter().any(|(_, known_field)| known_field == field) {
592 return Err(syn::Error::new(
593 Span::call_site(),
594 format!("unknown omitted field `{}`", field),
595 ));
596 }
597 }
598 }
599 }
600 }
601
602 Ok(())
603}
604
605fn generate_enum_source_trait_impl(
606 trait_ident: &Ident,
607 type_ident: &Ident,
608 variants: &[VariantInfo],
609 fields: &[FieldInfo],
610) -> TokenStream2 {
611 let methods = fields.iter().map(|field| {
612 let field_ident = &field.ident;
613 let return_ty = field.accessor_return_ty();
614 let arms = variants.iter().map(|variant| {
615 let variant_ident = &variant.ident;
616 if variant
617 .fields
618 .iter()
619 .any(|field| field.ident == *field_ident)
620 {
621 let value = field.enum_field_expr(quote! { #field_ident });
622 quote! {
623 #type_ident::#variant_ident { #field_ident, .. } => #value
624 }
625 } else {
626 quote! {
627 #type_ident::#variant_ident { .. } => ::core::option::Option::None
628 }
629 }
630 });
631
632 quote! {
633 fn #field_ident(&self) -> #return_ty {
634 match self {
635 #(#arms,)*
636 }
637 }
638 }
639 });
640
641 quote! {
642 impl #trait_ident for #type_ident {
643 #(#methods)*
644 }
645 }
646}
647
648fn generate_enum_view(
649 vis: &Visibility,
650 type_ident: &Ident,
651 trait_ident: &Ident,
652 variants: &[VariantInfo],
653 fields: &[FieldInfo],
654 view: &ViewSpec,
655 view_derives: &[syn::Path],
656) -> syn::Result<TokenStream2> {
657 let view_ident = &view.name;
658
659 let variant_defs = variants.iter().map(|variant| {
660 let variant_ident = &variant.ident;
661 let fields = variant.fields.iter().filter_map(|field| {
662 if is_enum_field_omitted(view, &variant.ident, &field.ident) {
663 None
664 } else {
665 let field_ident = &field.ident;
666 let ty = field.view_field_ty();
667 Some(quote! { #field_ident: #ty })
668 }
669 });
670 quote! { #variant_ident { #(#fields,)* } }
671 });
672
673 let from_arms = variants.iter().map(|variant| {
674 let variant_ident = &variant.ident;
675 let included = variant
676 .fields
677 .iter()
678 .filter(|field| !is_enum_field_omitted(view, &variant.ident, &field.ident))
679 .collect::<Vec<_>>();
680 let pattern_fields = included.iter().map(|field| &field.ident);
681 let value_fields = included.iter().map(|field| {
682 let field_ident = &field.ident;
683 quote! { #field_ident }
684 });
685
686 quote! {
687 #type_ident::#variant_ident { #(#pattern_fields,)* .. } => {
688 Self::#variant_ident { #(#value_fields,)* }
689 }
690 }
691 });
692
693 let methods = fields.iter().map(|field| {
694 let field_ident = &field.ident;
695 let return_ty = field.accessor_return_ty();
696 let arms = variants.iter().map(|variant| {
697 let variant_ident = &variant.ident;
698 let contains_field = variant
699 .fields
700 .iter()
701 .any(|field| field.ident == *field_ident);
702 if contains_field && !is_enum_field_omitted(view, &variant.ident, field_ident) {
703 let value = field.enum_field_expr(quote! { #field_ident });
704 quote! {
705 #view_ident::#variant_ident { #field_ident, .. } => #value
706 }
707 } else {
708 quote! {
709 #view_ident::#variant_ident { .. } => ::core::option::Option::None
710 }
711 }
712 });
713
714 quote! {
715 fn #field_ident(&self) -> #return_ty {
716 match self {
717 #(#arms,)*
718 }
719 }
720 }
721 });
722
723 Ok(quote! {
724 #[derive(#(#view_derives),*)]
725 #vis enum #view_ident<'a> {
726 #(#variant_defs,)*
727 }
728
729 impl<'a> ::core::convert::From<&'a #type_ident> for #view_ident<'a> {
730 fn from(value: &'a #type_ident) -> Self {
731 match value {
732 #(#from_arms,)*
733 }
734 }
735 }
736
737 impl<'a> #trait_ident for #view_ident<'a> {
738 #(#methods)*
739 }
740 })
741}
742
743struct ImplTraitArgs {
744 view_trait: syn::Path,
745 target_types: Punctuated<Type, Token![,]>,
746}
747
748impl Parse for ImplTraitArgs {
749 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
750 let view_trait = input.parse()?;
751 input.parse::<Token![=>]>()?;
752 Ok(Self {
753 view_trait,
754 target_types: Punctuated::parse_terminated(input)?,
755 })
756 }
757}
758
759fn expand_impl_trait(args: ImplTraitArgs, item: ItemImpl) -> syn::Result<TokenStream2> {
760 if args.target_types.is_empty() {
761 return Err(syn::Error::new(
762 Span::call_site(),
763 "impl_trait requires at least one target type after `=>`",
764 ));
765 }
766
767 let _view_trait = &args.view_trait;
768 let impls = args.target_types.iter().map(|target_ty| {
769 let mut item = item.clone();
770 item.self_ty = Box::new(target_ty.clone());
771 item
772 });
773
774 Ok(quote! {
775 #item
776 #(#impls)*
777 })
778}
779
780fn expand_impl_fn(trait_path: syn::Path, mut item: ItemFn) -> syn::Result<TokenStream2> {
781 if item.sig.generics.params.iter().any(|param| match param {
782 GenericParam::Type(param) => param.ident == "__RefView",
783 _ => false,
784 }) {
785 return Err(syn::Error::new_spanned(
786 item.sig.generics,
787 "impl_fn reserves the generic parameter name `__RefView`",
788 ));
789 }
790
791 let Some(first_arg) = item.sig.inputs.first_mut() else {
792 return Err(syn::Error::new_spanned(
793 item.sig.ident,
794 "impl_fn requires a first argument",
795 ));
796 };
797
798 let syn::FnArg::Typed(first_arg) = first_arg else {
799 return Err(syn::Error::new_spanned(
800 first_arg,
801 "impl_fn requires the first argument to be a typed argument",
802 ));
803 };
804
805 let Type::Reference(reference) = first_arg.ty.as_mut() else {
806 return Err(syn::Error::new_spanned(
807 &first_arg.ty,
808 "impl_fn requires the first argument to be a shared reference",
809 ));
810 };
811
812 if reference.mutability.is_some() {
813 return Err(syn::Error::new_spanned(
814 &first_arg.ty,
815 "impl_fn does not support mutable first arguments",
816 ));
817 }
818
819 reference.elem = Box::new(syn::parse_quote! { __RefView });
820 item.sig
821 .generics
822 .params
823 .push(syn::parse_quote! { __RefView: #trait_path + ?Sized });
824
825 Ok(quote! {
826 #item
827 })
828}
829
830fn is_enum_field_omitted(view: &ViewSpec, variant: &Ident, field: &Ident) -> bool {
831 let variant = variant.to_string();
832 let field = field.to_string();
833 view.omitted.contains(&OmitTarget::Field(field.clone()))
834 || view
835 .omitted
836 .contains(&OmitTarget::VariantField { variant, field })
837}
838
839fn option_inner_type(ty: &Type) -> Option<Type> {
840 let Type::Path(type_path) = ty else {
841 return None;
842 };
843 let segment = type_path.path.segments.last()?;
844 if segment.ident != "Option" {
845 return None;
846 }
847 let PathArguments::AngleBracketed(args) = &segment.arguments else {
848 return None;
849 };
850 let Some(GenericArgument::Type(inner)) = args.args.first() else {
851 return None;
852 };
853 Some(inner.clone())
854}
855
856fn type_key(ty: &Type) -> String {
857 ty.to_token_stream().to_string()
858}
859
860fn type_key_path(path: &syn::Path) -> String {
861 path.to_token_stream().to_string()
862}