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)] #[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 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 box_types: Vec<Path>,
372 vec_types: Vec<Path>,
374 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 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 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 extern_types.push(ExternType::Trivial(path.clone()));
808 }
809 }
810 other => {
811 cx.push(SynError::new_spanned(other, "unsupported type"));
812 }
813 }
814}