cxx_enumext_macro/
lib.rs

1use std::collections::HashSet;
2use std::fmt::Display;
3
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::{quote, quote_spanned, ToTokens};
7use syn::ext::IdentExt;
8use syn::{
9    parse::{Parse, ParseStream, Parser},
10    spanned::Spanned,
11};
12use syn::{
13    Attribute, Fields, GenericArgument, GenericParam, Generics, Ident, Item as RustItem, ItemEnum,
14    ItemType, Lit, LitStr, Path, PathArguments, Token, Type, Variant, Visibility,
15};
16
17use syn::{Error as SynError, Result as SynResult};
18
19struct Errors {
20    errors: Vec<SynError>,
21}
22
23impl Errors {
24    pub fn new() -> Self {
25        Errors { errors: Vec::new() }
26    }
27
28    #[allow(dead_code)]
29    pub fn error(&mut self, sp: impl ToTokens, msg: impl Display) {
30        self.errors.push(SynError::new_spanned(sp, msg));
31    }
32
33    pub fn push(&mut self, error: SynError) {
34        self.errors.push(error);
35    }
36
37    pub fn propagate(&mut self) -> SynResult<()> {
38        let mut iter = self.errors.drain(..);
39        let Some(mut all_errors) = iter.next() else {
40            return Ok(());
41        };
42        for err in iter {
43            all_errors.combine(err);
44        }
45        Err(all_errors)
46    }
47}
48
49impl Default for Errors {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55#[proc_macro_attribute]
56pub fn extern_type(attribute: TokenStream, input: TokenStream) -> TokenStream {
57    match AstPieces::from_token_streams(attribute, input) {
58        Err(error) => error.into_compile_error().into(),
59        Ok(pieces) => expand(pieces).into(),
60    }
61}
62
63fn expand(pieces: AstPieces) -> proc_macro2::TokenStream {
64    let mut output = proc_macro2::TokenStream::new();
65
66    let ident = &pieces.ident;
67    let qualified_name = Lit::Str(LitStr::new(
68        &format!(
69            "{}{}",
70            pieces
71                .namespace
72                .clone()
73                .unwrap_or_default()
74                .0
75                .iter()
76                .fold(String::new(), |acc, piece| acc + piece + "::"),
77            pieces
78                .cxx_name
79                .clone()
80                .unwrap_or_else(|| ForeignName(ident.to_string()))
81                .0
82        ),
83        ident.span(),
84    ));
85
86    output.extend(match &pieces.item {
87        Item::Enum(enm) => expand_enum(&pieces, enm),
88        Item::Optional(optional) => expand_optional(&pieces, optional),
89        Item::Expected(expected) => expand_expected(&pieces, expected),
90    });
91
92    let cfg = &pieces.cfg;
93    let generics = &pieces.generics;
94
95    output.extend(quote! {
96
97        #cfg
98        #[automatically_derived]
99        unsafe impl #generics ::cxx::ExternType for #ident #generics {
100            #[allow(unused_attributes)] // incorrect lint
101            #[doc(hidden)]
102            type Id = ::cxx::type_id!(#qualified_name);
103            type Kind = ::cxx::kind::Trivial;
104        }
105
106    });
107
108    output.extend(expand_asserts(&pieces));
109
110    output
111}
112
113fn expand_enum(pieces: &AstPieces, enm: &Enum) -> proc_macro2::TokenStream {
114    let ident = &pieces.ident;
115    let vis = &pieces.vis;
116    let attrs = pieces.attrs.iter();
117    let generics = &pieces.generics;
118    let cfg = &pieces.cfg;
119    let variants = enm.variants.iter().map(|variant| {
120        let attrs = variant.attrs.iter();
121        let ident = &variant.ident;
122        match &variant.fields {
123            Fields::Named(named) => {
124                let list = named.named.iter();
125                quote! {#(#attrs)* #ident { #(#list),* } }
126            }
127            Fields::Unnamed(unnamed) => {
128                let list = unnamed.unnamed.iter();
129                quote! {#(#attrs)* #ident ( #(#list),* ) }
130            }
131            Fields::Unit => {
132                quote! {#(#attrs)* #ident }
133            }
134        }
135    });
136
137    quote! {
138        #cfg
139        #(#attrs)*
140        #[repr(C)]
141        #vis enum #ident #generics {
142            #(#variants,)*
143        }
144    }
145}
146
147fn expand_optional(pieces: &AstPieces, optional: &Optional) -> proc_macro2::TokenStream {
148    let ident = &pieces.ident;
149    let vis = &pieces.vis;
150    let attrs = pieces.attrs.iter();
151    let generics = &pieces.generics;
152    let inner = &optional.inner;
153    let cfg = &pieces.cfg;
154
155    quote! {
156        #cfg
157        #(#attrs)*
158        #[repr(C)]
159        #vis enum #ident #generics {
160            None,
161            Some(#inner)
162        }
163
164        #cfg
165        #[automatically_derived]
166        impl #generics ::std::convert::From<#ident #generics> for Option<#inner> {
167            fn from(value: #ident) -> Self {
168                match value {
169                    #ident::None => None,
170                    #ident::Some(value) => Some(value),
171                }
172            }
173        }
174
175        #cfg
176        #[automatically_derived]
177        impl #generics ::std::convert::From<Option<#inner>> for #ident #generics{
178            fn from(value: Option<#inner>) -> Self {
179                match value {
180                    None => #ident::None,
181                    Some(value) => #ident::Some(value),
182                }
183            }
184        }
185    }
186}
187
188fn expand_expected(pieces: &AstPieces, expected: &Expected) -> proc_macro2::TokenStream {
189    let ident = &pieces.ident;
190    let vis = &pieces.vis;
191    let attrs = pieces.attrs.iter();
192    let generics = &pieces.generics;
193    let cfg = &pieces.cfg;
194    let expected_t = &expected.expected;
195    let unexpected_t = &expected.unexpected;
196
197    quote! {
198        #cfg
199        #(#attrs)*
200        #[repr(C)]
201        #vis enum #ident #generics {
202            Ok(#expected_t),
203            Err(#unexpected_t),
204        }
205
206        #cfg
207        #[automatically_derived]
208        impl #generics ::std::convert::From<#ident #generics> for Result<#expected_t, #unexpected_t> {
209            fn from(value: #ident) -> Self {
210                match value {
211                    #ident::Ok(value) => Ok(value),
212                    #ident::Err(value) => Err(value),
213                }
214            }
215        }
216
217        #cfg
218        #[automatically_derived]
219        impl #generics ::std::convert::From<Result<#expected_t, #unexpected_t>> for #ident #generics {
220            fn from(value: Result<#expected_t, #unexpected_t>) -> Self {
221                match value {
222                    Ok(value) => #ident::Ok(value),
223                    Err(value) => #ident::Err(value),
224                }
225            }
226        }
227    }
228}
229
230fn expand_asserts(pieces: &AstPieces) -> proc_macro2::TokenStream {
231    let mut seen_trivial = HashSet::new();
232    let mut seen_opaque = HashSet::new();
233    let mut seen_extern = HashSet::new();
234    let mut seen_box = HashSet::new();
235    let mut seen_vec = HashSet::new();
236
237    let mut verify_extern = proc_macro2::TokenStream::new();
238
239    let mut assert_extern =
240        |span: Span, path: &Path, verify_extern: &mut proc_macro2::TokenStream| {
241            if !seen_extern.contains(path) {
242                seen_extern.insert(path.clone());
243                let reason = format!(" {} Must be ::cxx::ExternType", path.to_token_stream());
244                let assert = quote_spanned! {span=> assert!(::cxx_enumext::private::IsCxxExternType::<#path>::IS_CXX_EXTERN_TYPE,#reason )};
245                verify_extern.extend(quote! {
246                    const _: () = #assert;
247                });
248            }
249        };
250
251    for ty in pieces.extern_types.iter() {
252        match ty {
253            ExternType::Trivial(path) => {
254                let span = path.span();
255                assert_extern(span, path, &mut verify_extern);
256                if !seen_trivial.contains(path) {
257                    seen_trivial.insert(path.clone());
258                    let reason = format!(
259                        " {} Must be ::cxx::ExternType<Kind = Trivial>",
260                        path.to_token_stream()
261                    );
262                    let assert = quote_spanned! {span=> assert!(::cxx_enumext::private::IsCxxExternTrivial::<#path>::IS_CXX_EXTERN_TRIVIAL, #reason)};
263                    verify_extern.extend(quote! {
264                        const _: () = #assert;
265                    });
266                }
267            }
268            ExternType::Opaque(path) => {
269                let span = path.span();
270                assert_extern(span, path, &mut verify_extern);
271                if !seen_opaque.contains(path) {
272                    seen_opaque.insert(path.clone());
273                    let reason = format!(
274                        " {} Must be ::cxx::ExternType<Kind = Opaque>",
275                        path.to_token_stream()
276                    );
277                    let assert = quote_spanned! {span=> assert!(::cxx_enumext::private::IsCxxExternOpaque::<#path>::IS_CXX_EXTERN_OPAQUE, #reason)};
278                    verify_extern.extend(quote! {
279                        const _: () = #assert;
280                    });
281                }
282            }
283            ExternType::Unspecified(path) => {
284                assert_extern(path.span(), path, &mut verify_extern);
285            }
286        }
287    }
288
289    let mut verify_box = proc_macro2::TokenStream::new();
290
291    for path in pieces.box_types.iter() {
292        if !seen_box.contains(path) {
293            seen_box.insert(path.clone());
294            let span = path.span();
295            let reason = format!("{} is not exposed inside a Box in a cxx bridge. Use a Box as a function parameter/return value or shared struct member", path.to_token_stream());
296            let assert = quote_spanned!(span=> assert!(cxx_enumext::private::IsCxxImplBox::<#path>::IS_CXX_IMPL_BOX, #reason));
297            verify_box.extend(quote! {
298                const _: () = #assert;
299            });
300        }
301    }
302
303    for path in pieces.vec_types.iter() {
304        if !seen_vec.contains(path) {
305            seen_vec.insert(path.clone());
306            let span = path.span();
307            let reason = format!("{} is not exposed inside a Vec in a cxx bridge. Use a Vec as a function parameter/return value or shared struct member", path.to_token_stream());
308            let assert = quote_spanned!(span=> assert!(cxx_enumext::private::IsCxxImplBox::<#path>::IS_CXX_IMPL_VEC, #reason));
309            verify_box.extend(quote! {
310                const _: () = #assert;
311            });
312        }
313    }
314
315    let cfg = &pieces.cfg;
316
317    quote! {
318        #cfg
319        #[doc(hidden)]
320        const _: () = {
321            use ::cxx_enumext::private::NotCxxExternType as _;
322            use ::cxx_enumext::private::NotCxxExternTrivial as _;
323            use ::cxx_enumext::private::NotCxxExternOpaque as _;
324            use ::cxx_enumext::private::NotCxxImplBox as _;
325            use ::cxx_enumext::private::NotCxxImplVec as _;
326
327            #verify_extern
328            #verify_box
329            #verify_extern
330        };
331    }
332}
333
334struct Enum {
335    variants: Vec<Variant>,
336}
337
338struct Optional {
339    inner: Type,
340}
341
342struct Expected {
343    expected: Type,
344    unexpected: Type,
345}
346
347enum Item {
348    Enum(Enum),
349    Optional(Optional),
350    Expected(Expected),
351}
352
353enum ExternType {
354    Trivial(Path),
355    Opaque(Path),
356    #[allow(dead_code)]
357    Unspecified(Path),
358}
359
360struct AstPieces {
361    /// the item to bridge
362    item: Item,
363    ident: Ident,
364    namespace: Option<Namespace>,
365    cxx_name: Option<ForeignName>,
366    vis: Visibility,
367    generics: Generics,
368    attrs: Vec<Attribute>,
369    cfg: Option<Attribute>,
370    /// type name to confirm impl cxx::private::ImplBox
371    box_types: Vec<Path>,
372    /// type name to confirm impl cxx::private::ImplVec
373    vec_types: Vec<Path>,
374    /// type name and kind of cxx::ExternType to confirm
375    extern_types: Vec<ExternType>,
376}
377
378mod kw {
379    syn::custom_keyword!(namespace);
380    syn::custom_keyword!(cxx_name);
381}
382
383#[derive(Default, Clone)]
384struct Namespace(pub Vec<String>);
385
386#[derive(Default, Clone)]
387struct ForeignName(pub String);
388
389impl ForeignName {
390    pub fn parse(text: &str, span: Span) -> SynResult<Self> {
391        match Ident::parse_any.parse_str(text) {
392            Ok(ident) => {
393                let text = ident.to_string();
394                Ok(ForeignName(text))
395            }
396            Err(err) => Err(SynError::new(span, err)),
397        }
398    }
399}
400
401impl Parse for Namespace {
402    fn parse(input: ParseStream) -> SynResult<Self> {
403        if input.is_empty() {
404            return Ok(Namespace(vec![]));
405        }
406        let path = input.call(Path::parse_mod_style)?;
407        Ok(Namespace(
408            path.segments
409                .iter()
410                .map(|segment| segment.ident.to_string())
411                .collect(),
412        ))
413    }
414}
415
416fn parse_bridge_params(input: ParseStream) -> SynResult<(Option<Namespace>, Option<ForeignName>)> {
417    if input.is_empty() {
418        Ok((None, None))
419    } else {
420        let mut ns = None;
421        let mut cxx_name = None;
422        loop {
423            if input.peek(kw::namespace) {
424                let ns_tok = input.parse::<kw::namespace>()?;
425                if ns.is_some() {
426                    return Err(SynError::new_spanned(ns_tok, "duplicate namespace param"));
427                }
428                input.parse::<Token![=]>()?;
429                ns = Some(input.parse::<Namespace>()?);
430            } else if input.peek(kw::cxx_name) {
431                let name_tok = input.parse::<kw::cxx_name>()?;
432                if cxx_name.is_some() {
433                    return Err(SynError::new_spanned(name_tok, "duplicate cxx_name param"));
434                }
435                input.parse::<Token![=]>()?;
436                cxx_name = Some(ForeignName::parse(
437                    &input.parse::<LitStr>()?.value(),
438                    name_tok.span,
439                )?);
440            }
441
442            if (input.parse::<Option<Token![,]>>()?).is_none() {
443                break;
444            }
445        }
446        Ok((ns, cxx_name))
447    }
448}
449
450impl AstPieces {
451    // Parses the macro arguments and returns the pieces, returning a `syn::Error` on error.
452    fn from_token_streams(attribute: TokenStream, item: TokenStream) -> SynResult<AstPieces> {
453        let (namespace, cxx_name) = parse_bridge_params.parse(attribute)?;
454
455        match syn::parse::<RustItem>(item)? {
456            RustItem::Type(ty) => parse_type_decl(ty, namespace, cxx_name),
457            RustItem::Enum(enm) => parse_enum(enm, namespace, cxx_name),
458            other => Err(SynError::new_spanned(
459                other,
460                "unsupported item for ExternType generation",
461            )),
462        }
463    }
464}
465
466fn parse_enum(
467    enm: ItemEnum,
468    namespace: Option<Namespace>,
469    cxx_name: Option<ForeignName>,
470) -> SynResult<AstPieces> {
471    let cx = &mut Errors::new();
472
473    let mut box_types = Vec::new();
474    let mut vec_types = Vec::new();
475    let mut extern_types = Vec::new();
476
477    let mut attrs = enm.attrs;
478    let mut cfg = None;
479    attrs.retain_mut(|attr| {
480        let attr_path = attr.path();
481        if attr_path.is_ident("cfg") {
482            cfg = Some(attr.clone());
483            return false;
484        }
485        if attr_path.is_ident("repr") {
486            cx.push(SynError::new_spanned(attr, "unsupported repr attribute"));
487        }
488        true
489    });
490
491    for variant in &enm.variants {
492        match &variant.fields {
493            Fields::Named(named) => {
494                for field in &named.named {
495                    find_types(
496                        &field.ty,
497                        &mut box_types,
498                        &mut vec_types,
499                        &mut extern_types,
500                        cx,
501                    );
502                }
503            }
504            Fields::Unit => {}
505            Fields::Unnamed(unnamed) => {
506                for field in &unnamed.unnamed {
507                    find_types(
508                        &field.ty,
509                        &mut box_types,
510                        &mut vec_types,
511                        &mut extern_types,
512                        cx,
513                    );
514                }
515            }
516        }
517    }
518    for generic in &enm.generics.params {
519        if !matches!(generic, GenericParam::Lifetime(_)) {
520            cx.push(SynError::new_spanned(
521                generic,
522                "only lifetime generic params supported",
523            ));
524        }
525    }
526    cx.propagate()?;
527    Ok(AstPieces {
528        item: Item::Enum(Enum {
529            variants: enm.variants.into_iter().collect(),
530        }),
531        ident: enm.ident.clone(),
532        cxx_name,
533        namespace,
534        attrs,
535        vis: enm.vis,
536        generics: enm.generics,
537        cfg,
538        box_types,
539        vec_types,
540        extern_types,
541    })
542}
543
544fn parse_type_decl(
545    alias: ItemType,
546    namespace: Option<Namespace>,
547    cxx_name: Option<ForeignName>,
548) -> SynResult<AstPieces> {
549    let cx = &mut Errors::new();
550    let ident = alias.ident;
551    if !alias.generics.params.is_empty() {
552        cx.push(SynError::new_spanned(
553            alias.generics.params.clone(),
554            "Generics are not supported",
555        ));
556    }
557
558    let mut box_types = Vec::new();
559    let mut vec_types = Vec::new();
560    let mut extern_types = Vec::new();
561
562    let mut attrs = alias.attrs;
563    let mut cfg = None;
564    attrs.retain_mut(|attr| {
565        let attr_path = attr.path();
566        if attr_path.is_ident("cfg") {
567            cfg = Some(attr.clone());
568            return false;
569        }
570        if attr_path.is_ident("repr") {
571            cx.push(SynError::new_spanned(attr, "unsupported repr attribute"));
572        }
573        true
574    });
575
576    match alias.ty.as_ref() {
577        Type::Path(ty) => {
578            let path = &ty.path;
579            if ty.qself.is_none() {
580                let segment = {
581                    if path.segments.len() == 1 {
582                        &path.segments[0]
583                    } else if path.segments.len() == 2 && path.segments[0].ident == "cxx_enumext" {
584                        &path.segments[1]
585                    } else {
586                        return Err(SynError::new_spanned(
587                            path,
588                            "unsupported type, did you mean 'Optional' or 'cxx_enumext::Optional'?",
589                        ));
590                    }
591                };
592                let ty_ident = segment.ident.clone();
593                if ty_ident == "Option" {
594                    return Err(SynError::new_spanned(
595                        path,
596                        "unsupported type, did you mean 'Optional'?",
597                    ));
598                } else if ty_ident == "Result" {
599                    return Err(SynError::new_spanned(
600                        path,
601                        "unsupported type, did you mean 'Expected'?",
602                    ));
603                } else if ty_ident == "Optional" {
604                    let inner = match &segment.arguments {
605                        PathArguments::None => {
606                            return Err(SynError::new_spanned(
607                                path,
608                                "Optional needs a contained type",
609                            ));
610                        }
611                        PathArguments::Parenthesized(_) => {
612                            return Err(SynError::new_spanned(
613                                path,
614                                "Optional needs a contained type",
615                            ));
616                        }
617                        PathArguments::AngleBracketed(generic) => {
618                            if generic.args.len() == 1 {
619                                let GenericArgument::Type(inner) = &generic.args[0] else {
620                                    return Err(SynError::new_spanned(
621                                        path,
622                                        "Optional takes only one generic type argument",
623                                    ));
624                                };
625
626                                find_types(
627                                    inner,
628                                    &mut box_types,
629                                    &mut vec_types,
630                                    &mut extern_types,
631                                    cx,
632                                );
633                                inner
634                            } else {
635                                return Err(SynError::new_spanned(
636                                    path,
637                                    "Optional takes only one generic type argument",
638                                ));
639                            }
640                        }
641                    };
642                    cx.propagate()?;
643                    return Ok(AstPieces {
644                        item: Item::Optional(Optional {
645                            inner: inner.clone(),
646                        }),
647                        ident,
648                        namespace,
649                        cxx_name,
650                        attrs,
651                        vis: alias.vis,
652                        generics: alias.generics,
653                        cfg,
654                        box_types,
655                        vec_types,
656                        extern_types,
657                    });
658                } else if ty_ident == "Expected" {
659                    let (expected, unexpected) = match &segment.arguments {
660                        PathArguments::None => {
661                            return Err(SynError::new_spanned(
662                                path,
663                                "Expected needs two contained types",
664                            ));
665                        }
666                        PathArguments::Parenthesized(_) => {
667                            return Err(SynError::new_spanned(
668                                path,
669                                "Expected needs two contained types",
670                            ));
671                        }
672                        PathArguments::AngleBracketed(generic) => {
673                            if generic.args.len() == 2 {
674                                let GenericArgument::Type(expected) = &generic.args[0] else {
675                                    return Err(SynError::new_spanned(
676                                        &generic.args[0],
677                                        "must be a type argument",
678                                    ));
679                                };
680                                let GenericArgument::Type(unexpected) = &generic.args[1] else {
681                                    return Err(SynError::new_spanned(
682                                        &generic.args[0],
683                                        "must be a type argument",
684                                    ));
685                                };
686
687                                find_types(
688                                    expected,
689                                    &mut box_types,
690                                    &mut vec_types,
691                                    &mut extern_types,
692                                    cx,
693                                );
694                                find_types(
695                                    unexpected,
696                                    &mut box_types,
697                                    &mut vec_types,
698                                    &mut extern_types,
699                                    cx,
700                                );
701                                (expected, unexpected)
702                            } else {
703                                return Err(SynError::new_spanned(
704                                    path,
705                                    "Expected takes two generic type argument",
706                                ));
707                            }
708                        }
709                    };
710                    cx.propagate()?;
711                    return Ok(AstPieces {
712                        item: Item::Expected(Expected {
713                            expected: expected.clone(),
714                            unexpected: unexpected.clone(),
715                        }),
716                        ident,
717                        namespace,
718                        cxx_name,
719                        attrs,
720                        vis: alias.vis,
721                        generics: alias.generics,
722                        cfg,
723                        box_types,
724                        vec_types,
725                        extern_types,
726                    });
727                };
728            }
729            Err(SynError::new_spanned(path, "unsupported type"))
730        }
731        other => Err(SynError::new_spanned(other, "unsupported type")),
732    }
733}
734
735fn find_types(
736    ty: &Type,
737    box_types: &mut Vec<Path>,
738    vec_types: &mut Vec<Path>,
739    extern_types: &mut Vec<ExternType>,
740    cx: &mut Errors,
741) {
742    match ty {
743        Type::Reference(_) => {}
744        Type::Ptr(_) => {}
745        Type::Array(_) => {}
746        Type::BareFn(_) => {}
747        Type::Tuple(ty) if ty.elems.is_empty() => {}
748        Type::Path(ty) => {
749            let path = &ty.path;
750
751            // used to detect if type was handeled already
752            let count = extern_types.len() + box_types.len() + vec_types.len();
753
754            if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 {
755                let segment = &path.segments[0];
756                let ident = segment.ident.clone();
757                match &segment.arguments {
758                    PathArguments::None => {}
759                    PathArguments::AngleBracketed(generic) => {
760                        if ident == "Box" && generic.args.len() == 1 {
761                            if let GenericArgument::Type(Type::Path(inner)) = &generic.args[0] {
762                                box_types.push(inner.path.clone());
763                            }
764                        } else if ident == "Vec" && generic.args.len() == 1 {
765                            if let GenericArgument::Type(Type::Path(inner)) = &generic.args[0] {
766                                vec_types.push(inner.path.clone());
767                            }
768                        } else if ["UniquePtr", "SharedPtr", "WeakPtr", "CxxVector"]
769                            .iter()
770                            .any(|name| ident == name)
771                            && generic.args.len() == 1
772                        {
773                            if let GenericArgument::Type(Type::Path(inner)) = &generic.args[0] {
774                                extern_types.push(ExternType::Opaque(inner.path.clone()));
775                            }
776                        }
777                    }
778                    PathArguments::Parenthesized(_) => {}
779                }
780            } else if ty.qself.is_none()
781                && path.leading_colon.is_none()
782                && path.segments.len() == 2
783                && path.segments[0].ident == "cxx"
784            {
785                let segment = &path.segments[1];
786                let ident = segment.ident.clone();
787
788                match &segment.arguments {
789                    PathArguments::None => {}
790                    PathArguments::AngleBracketed(generic) => {
791                        if ["UniquePtr", "SharedPtr", "WeakPtr", "CxxVector"]
792                            .iter()
793                            .any(|name| ident == name)
794                            && generic.args.len() == 1
795                        {
796                            if let GenericArgument::Type(Type::Path(inner)) = &generic.args[0] {
797                                extern_types.push(ExternType::Opaque(inner.path.clone()));
798                            }
799                        }
800                    }
801                    PathArguments::Parenthesized(_) => {}
802                }
803            }
804
805            if (extern_types.len() + box_types.len() + vec_types.len()) == count {
806                // didn't find a special type
807                extern_types.push(ExternType::Trivial(path.clone()));
808            }
809        }
810        other => {
811            cx.push(SynError::new_spanned(other, "unsupported type"));
812        }
813    }
814}