Skip to main content

ref_view/
lib.rs

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}