1use std::mem;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{ToTokens, TokenStreamExt, format_ident, quote, quote_spanned};
5use syn::{
6 Attribute, Expr, FnArg, GenericArgument, GenericParam, Generics, Ident, Signature, Token, Type,
7 TypePath, braced,
8 parse::{Parse, ParseStream},
9 parse_quote, parse_quote_spanned,
10 punctuated::Punctuated,
11 spanned::Spanned,
12 token::Brace,
13};
14
15use crate::{flux_tool_attrs, inner, outer, parse_inner, tokens_or_default};
16
17pub(crate) fn transform_extern_spec(
18 attr: TokenStream,
19 tokens: TokenStream,
20) -> syn::Result<TokenStream> {
21 let mod_path: Option<syn::Path> =
22 if !attr.is_empty() { Some(syn::parse2(attr)?) } else { None };
23 let mod_use = mod_path.map(UseWildcard);
24 let span = tokens.span();
25 match syn::parse2::<ExternItem>(tokens)? {
26 ExternItem::Struct(item_struct) => extern_struct_to_tokens(mod_use, item_struct),
27 ExternItem::Enum(item_enum) => extern_enum_to_tokens(mod_use, item_enum),
28 ExternItem::Trait(item_trait) => extern_trait_to_tokens(span, mod_use, item_trait),
29 ExternItem::Fn(extern_fn) => extern_fn_to_tokens(span, mod_use, extern_fn),
30 ExternItem::Impl(extern_item_impl) => {
31 extern_impl_to_tokens(span, mod_use, extern_item_impl)
32 }
33 }
34}
35
36fn extern_fn_to_tokens(
37 span: Span,
38 mod_use: Option<UseWildcard>,
39 mut extern_fn: ExternFn,
40) -> syn::Result<TokenStream> {
41 extern_fn.prepare(&FnCtxt::Free, true);
42 Ok(quote_spanned! {span=>
43 #[allow(unused, dead_code, non_camel_case_types)]
44 #[flux_tool::extern_spec]
45 const _: () = {
46 #mod_use
47
48 #extern_fn
49 };
50 })
51}
52
53fn extern_enum_to_tokens(
54 mod_use: Option<UseWildcard>,
55 mut item_enum: syn::ItemEnum,
56) -> syn::Result<TokenStream> {
57 let span = item_enum.span();
58 let ident = item_enum.ident;
59
60 item_enum.ident = format_ident!("__FluxExternSpecEnum__{}", ident);
61
62 flux_tool_attrs(&mut item_enum.attrs);
63 for variant in &mut item_enum.variants {
64 flux_tool_attrs(&mut variant.attrs);
65 }
66
67 let dummy_struct = format_ident!("__FluxExternSpecDummy__{}", ident);
68 let generics = &item_enum.generics;
69 let args = generic_params_to_args(&generics.params);
70
71 Ok(quote_spanned! {span=>
72 #[allow(unused, dead_code, non_camel_case_types)]
73 #[flux_tool::extern_spec]
74 const _: () = {
75 #mod_use
76
77 struct #dummy_struct #generics ( #ident < #args > );
78
79 #item_enum
80 };
81 })
82}
83
84fn extern_struct_to_tokens(
85 mod_use: Option<UseWildcard>,
86 mut item_struct: syn::ItemStruct,
87) -> syn::Result<TokenStream> {
88 let item_struct_span = item_struct.span();
89 let ident = item_struct.ident;
90
91 let generics = &item_struct.generics;
92 let args = generic_params_to_args(&generics.params);
93
94 item_struct.ident = format_ident!("__FluxExternSpecStruct__{}", ident);
96 flux_tool_attrs(&mut item_struct.attrs);
97 for field in &mut item_struct.fields {
98 flux_tool_attrs(&mut field.attrs);
99 }
100 if let syn::Fields::Unit = &item_struct.fields {
101 if !has_opaque_attr(&item_struct.attrs) {
102 item_struct.attrs.push(parse_quote!(#[flux_tool::opaque]));
103 }
104 item_struct.fields = syn::Fields::Unnamed(parse_quote! { (#ident < #args >) });
105 }
106
107 let dummy_struct = format_ident!("__FluxExternSpecDummy__{}", ident);
109
110 Ok(quote_spanned! {item_struct_span =>
111 #[allow(unused, dead_code, non_camel_case_types)]
112 #[flux_tool::extern_spec]
113 const _: () = {
114 #mod_use
115
116 struct #dummy_struct #generics (#ident < #args >);
117
118 #item_struct
119 };
120 })
121}
122
123fn has_opaque_attr(attrs: &[syn::Attribute]) -> bool {
124 attrs
125 .iter()
126 .any(|attr| path_matches(attr.path(), &["flux_tool", "opaque"]))
127}
128
129fn path_matches(path: &syn::Path, x: &[&str]) -> bool {
130 let mut i = 0;
131 for segment in &path.segments {
132 if i == x.len() {
133 return false;
134 }
135 if segment.ident != x[i] {
136 return false;
137 }
138 i += 1;
139 }
140 true
141}
142
143fn extern_trait_to_tokens(
144 span: Span,
145 mod_use: Option<UseWildcard>,
146 mut item_trait: ExternItemTrait,
147) -> syn::Result<TokenStream> {
148 item_trait.prepare();
149 let item_trait = item_trait;
150
151 Ok(quote_spanned! {span =>
152 #[allow(unused, dead_code, non_camel_case_types)]
153 #[flux_tool::extern_spec]
154 const _: () = {
155 #mod_use
156
157 #item_trait
158 };
159 })
160}
161
162fn extern_impl_to_tokens(
163 span: Span,
164 mod_use: Option<UseWildcard>,
165 mut extern_item_impl: ExternItemImpl,
166) -> syn::Result<TokenStream> {
167 extern_item_impl.prepare();
168 let extern_item_impl = extern_item_impl; let self_ty = &extern_item_impl.self_ty;
171 let (impl_generics, ty_generics, where_clause) = &extern_item_impl.generics.split_for_impl();
172
173 let dummy_ident = &extern_item_impl.dummy_ident;
174 let mut fields = generic_params_to_fields(&extern_item_impl.generics.params);
175 fields.push(parse_quote!(#self_ty));
176
177 let dummy_impl = if let Some((_, trait_, _)) = &extern_item_impl.trait_ {
178 Some(quote!(
179 impl #impl_generics #dummy_ident #ty_generics #where_clause {
180 fn __flux_extern_extract_impl_id() where #self_ty: #trait_ {}
181 }
182 ))
183 } else {
184 None
185 };
186
187 Ok(quote_spanned! {span=>
188 #[allow(unused, dead_code, non_camel_case_types)]
189 #[flux_tool::extern_spec]
190 const _: () = {
191 #mod_use
192
193 struct #dummy_ident #impl_generics ( #fields ) #where_clause;
194
195 #dummy_impl
196
197 #extern_item_impl
198 };
199 })
200}
201
202enum ExternItem {
203 Struct(syn::ItemStruct),
204 Enum(syn::ItemEnum),
205 Trait(ExternItemTrait),
206 Fn(ExternFn),
207 Impl(ExternItemImpl),
208}
209
210impl ExternItem {
211 fn replace_attrs(&mut self, new: Vec<Attribute>) -> Vec<Attribute> {
212 match self {
213 ExternItem::Struct(syn::ItemStruct { attrs, .. })
214 | ExternItem::Enum(syn::ItemEnum { attrs, .. })
215 | ExternItem::Trait(ExternItemTrait { attrs, .. })
216 | ExternItem::Fn(ExternFn { attrs, .. })
217 | ExternItem::Impl(ExternItemImpl { attrs, .. }) => mem::replace(attrs, new),
218 }
219 }
220}
221
222impl Parse for ExternItem {
223 fn parse(input: ParseStream) -> syn::Result<Self> {
224 let mut attrs = input.call(Attribute::parse_outer)?;
225 let lookahead = input.lookahead1();
226 let mut item = if lookahead.peek(Token![fn]) {
227 ExternItem::Fn(input.parse()?)
228 } else if lookahead.peek(Token![impl]) {
229 ExternItem::Impl(input.parse()?)
230 } else if lookahead.peek(Token![struct]) {
231 ExternItem::Struct(input.parse()?)
232 } else if lookahead.peek(Token![enum]) {
233 let enm = input.parse();
234 ExternItem::Enum(enm?)
235 } else if lookahead.peek(Token![trait]) {
236 ExternItem::Trait(input.parse()?)
237 } else {
238 return Err(lookahead.error());
239 };
240
241 attrs.extend(item.replace_attrs(Vec::new()));
242 item.replace_attrs(attrs);
243 Ok(item)
244 }
245}
246
247struct ExternItemImpl {
248 attrs: Vec<Attribute>,
249 impl_token: Token![impl],
250 generics: Generics,
251 trait_: Option<(Option<Token![!]>, syn::Path, Token![for])>,
252 self_ty: Box<Type>,
253 brace_token: Brace,
254 items: Vec<ExternFn>,
255 dummy_ident: Ident,
256}
257
258impl ExternItemImpl {
259 fn prepare(&mut self) {
260 flux_tool_attrs(&mut self.attrs);
261 let cx = if let Some(trait_) = self.trait_.as_ref().map(|(_, path, _)| path) {
262 FnCtxt::TraitImpl { trait_, self_ty: &self.self_ty }
263 } else {
264 FnCtxt::InherentImpl { self_ty: &self.self_ty }
265 };
266
267 for item in &mut self.items {
268 item.prepare(&cx, false);
269 }
270 }
271}
272
273impl ToTokens for ExternItemImpl {
274 fn to_tokens(&self, tokens: &mut TokenStream) {
275 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
276
277 tokens.append_all(outer(&self.attrs));
278
279 self.impl_token.to_tokens(tokens);
280 impl_generics.to_tokens(tokens);
281
282 self.dummy_ident.to_tokens(tokens);
283 ty_generics.to_tokens(tokens);
284
285 where_clause.to_tokens(tokens);
286 self.brace_token.surround(tokens, |tokens| {
287 tokens.append_all(inner(&self.attrs));
288 for item in &self.items {
289 item.to_tokens(tokens);
290 }
291 });
292 }
293}
294
295struct ExternItemTrait {
296 attrs: Vec<Attribute>,
297 trait_token: Token![trait],
298 ident: Ident,
299 generics: Generics,
300 supertrait: Option<syn::Path>,
301 brace_token: Brace,
302 items: Vec<ExternFn>,
303}
304
305impl ExternItemTrait {
306 fn prepare(&mut self) {
307 let dummy_ident = format_ident!("__FluxExternTrait{}", self.ident);
308 let ident = std::mem::replace(&mut self.ident, dummy_ident);
309
310 let ident_span = self.ident.span();
311 let args = GenericArgs(&self.generics);
312 let trait_ = parse_quote_spanned!(ident_span=> #ident #args);
313
314 flux_tool_attrs(&mut self.attrs);
315
316 let cx = FnCtxt::Trait { trait_: &trait_ };
317 for item in &mut self.items {
318 item.prepare(&cx, false);
319 }
320
321 self.supertrait = Some(trait_);
322 }
323}
324
325impl ToTokens for ExternItemTrait {
326 fn to_tokens(&self, tokens: &mut TokenStream) {
327 tokens.append_all(outer(&self.attrs));
328 self.trait_token.to_tokens(tokens);
329 self.ident.to_tokens(tokens);
330 self.generics.to_tokens(tokens);
331 if let Some(supertrait) = &self.supertrait {
332 tokens.extend(quote!(: #supertrait));
333 }
334 self.generics.where_clause.to_tokens(tokens);
335 self.brace_token.surround(tokens, |tokens| {
336 tokens.append_all(inner(&self.attrs));
337 for item in &self.items {
338 item.to_tokens(tokens);
339 }
340 })
341 }
342}
343
344enum FnCtxt<'a> {
345 TraitImpl { self_ty: &'a syn::Type, trait_: &'a syn::Path },
346 InherentImpl { self_ty: &'a syn::Type },
347 Trait { trait_: &'a syn::Path },
348 Free,
349}
350
351struct ExternFn {
352 attrs: Vec<Attribute>,
353 sig: Signature,
354 block: Option<TokenStream>,
355}
356
357impl ExternFn {
358 fn prepare(&mut self, cx: &FnCtxt, mangle: bool) {
359 flux_tool_attrs(&mut self.attrs);
360 if let FnCtxt::TraitImpl { self_ty, .. } | FnCtxt::InherentImpl { self_ty } = cx {
361 struct ReplaceSelf<'a> {
362 self_ty: &'a syn::Type,
363 }
364
365 impl syn::visit_mut::VisitMut for ReplaceSelf<'_> {
366 fn visit_type_mut(&mut self, ty: &mut syn::Type) {
367 if let syn::Type::Path(type_path) = ty {
368 if type_path.path.is_ident("Self") {
369 *ty = self.self_ty.clone();
370 }
371 }
372 }
373 }
374
375 syn::visit_mut::visit_signature_mut(&mut ReplaceSelf { self_ty }, &mut self.sig);
376
377 self.change_receiver(self_ty);
378 }
379 self.fill_body(cx);
380 if mangle {
381 self.sig.ident = format_ident!("__flux_extern_spec_{}", self.sig.ident);
382 }
383 }
384
385 fn change_receiver(&mut self, self_ty: &syn::Type) {
386 if let Some(first) = self.sig.inputs.first_mut() {
387 if let FnArg::Receiver(receiver) = first {
388 let ident = format_ident!("__self", span = receiver.self_token.span);
389
390 *first = if receiver.colon_token.is_some() {
391 let receiver_ty = &receiver.ty;
393 parse_quote!(#ident : #receiver_ty)
394 } else if let Some((ampersand, lft)) = &receiver.reference {
395 let mutbl = receiver.mutability;
396 parse_quote!(#ident : #ampersand #lft #mutbl #self_ty)
397 } else {
398 parse_quote!(#ident : #self_ty)
399 };
400 }
401 }
402 }
403
404 fn fill_body(&mut self, cx: &FnCtxt) {
405 let ident = &self.sig.ident;
406 let fn_path = match cx {
407 FnCtxt::TraitImpl { self_ty, trait_ } => quote!(< #self_ty as #trait_ > :: #ident),
408 FnCtxt::InherentImpl { self_ty } => quote!(< #self_ty > :: #ident),
409 FnCtxt::Trait { trait_ } => quote!(< Self as #trait_ > :: #ident),
410 FnCtxt::Free => quote!(#ident),
411 };
412 let generic_args = generic_params_to_args(&self.sig.generics.params);
413 let fn_args = fn_params_to_args(&self.sig.inputs);
414 self.block = Some(quote!({ #fn_path :: <#generic_args> ( #fn_args ) }));
415 }
416}
417
418impl ToTokens for ExternFn {
419 fn to_tokens(&self, tokens: &mut TokenStream) {
420 debug_assert!(self.block.is_some());
421 tokens.append_all(&self.attrs);
422 self.sig.to_tokens(tokens);
423 self.block.to_tokens(tokens);
424 }
425}
426
427impl Parse for ExternFn {
428 fn parse(input: ParseStream) -> syn::Result<Self> {
429 let attrs = input.call(Attribute::parse_outer)?;
430 let sig = input.parse()?;
431 input.parse::<Token![;]>()?;
432 Ok(ExternFn { attrs, sig, block: None })
433 }
434}
435
436impl Parse for ExternItemImpl {
437 fn parse(input: ParseStream) -> syn::Result<Self> {
438 let mut attrs = input.call(Attribute::parse_outer)?;
439 let impl_token = input.parse()?;
440 let generics = input.parse()?;
441
442 let mut first_ty: Type = input.parse()?;
443 let self_ty: Type;
444 let trait_;
445
446 let is_impl_for = input.peek(Token![for]);
447 if is_impl_for {
448 let for_token: Token![for] = input.parse()?;
449 let mut first_ty_ref = &first_ty;
450 while let Type::Group(ty) = first_ty_ref {
451 first_ty_ref = &ty.elem;
452 }
453 if let Type::Path(TypePath { qself: None, .. }) = first_ty_ref {
454 while let Type::Group(ty) = first_ty {
455 first_ty = *ty.elem;
456 }
457 if let Type::Path(TypePath { qself: None, path }) = first_ty {
458 trait_ = Some((None, path, for_token));
459 } else {
460 unreachable!();
461 }
462 } else {
463 trait_ = None;
464 }
465 self_ty = input.parse()?;
466 } else {
467 trait_ = None;
468 self_ty = first_ty;
469 }
470
471 let content;
472 let brace_token = braced!(content in input);
473 parse_inner(&content, &mut attrs)?;
474 let mut items = Vec::new();
475 while !content.is_empty() {
476 items.push(content.parse()?);
477 }
478
479 let mut dummy_prefix = "__FluxExternImplStruct".to_string();
480 if let Some(trait_path) = trait_.as_ref().map(|(_, path, _)| path) {
481 dummy_prefix.push_str(&create_dummy_string_from_path(trait_path)?);
482 }
483 let dummy_ident = create_dummy_ident(&mut dummy_prefix, &self_ty)?;
484
485 Ok(ExternItemImpl {
486 attrs,
487 impl_token,
488 generics,
489 trait_,
490 self_ty: Box::new(self_ty),
491 brace_token,
492 items,
493 dummy_ident,
494 })
495 }
496}
497
498impl Parse for ExternItemTrait {
499 fn parse(input: ParseStream) -> syn::Result<Self> {
500 let mut attrs = input.call(Attribute::parse_outer)?;
501 let trait_token = input.parse()?;
502 let ident: Ident = input.parse()?;
503 let mut generics: syn::Generics = input.parse()?;
504 generics.where_clause = input.parse()?;
505
506 let content;
507 let brace_token = braced!(content in input);
508 parse_inner(&content, &mut attrs)?;
509 let mut items = Vec::new();
510 while !content.is_empty() {
511 items.push(content.parse()?);
512 }
513
514 Ok(ExternItemTrait {
515 attrs,
516 trait_token,
517 ident,
518 generics,
519 supertrait: None,
520 brace_token,
521 items,
522 })
523 }
524}
525
526fn create_dummy_ident(dummy_prefix: &mut String, ty: &syn::Type) -> syn::Result<Ident> {
527 use syn::Type::*;
528 match ty {
529 Reference(ty_ref) => {
530 if ty_ref.mutability.is_some() {
531 dummy_prefix.push_str("Mut");
532 };
533 dummy_prefix.push_str("Ref");
534 create_dummy_ident(dummy_prefix, ty_ref.elem.as_ref())
535 }
536 Slice(ty_slice) => {
537 dummy_prefix.push_str("Slice");
538 create_dummy_ident(dummy_prefix, ty_slice.elem.as_ref())
539 }
540 Path(ty_path) => create_dummy_ident_from_path(dummy_prefix, &ty_path.path),
541 _ => {
542 Err(syn::Error::new(
543 ty.span(),
544 format!("invalid extern_spec: unsupported type {:?}", ty),
545 ))
546 }
547 }
548}
549
550fn create_dummy_string_from_path(path: &syn::Path) -> syn::Result<String> {
551 if let Some(path_segment) = path.segments.last() {
552 let str = format!("{}", path_segment.ident);
554 Ok(str)
555 } else {
556 Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
557 }
558}
559
560fn create_dummy_ident_from_path(dummy_prefix: &str, path: &syn::Path) -> syn::Result<Ident> {
561 if let Some(path_segment) = path.segments.last() {
563 let ident = Ident::new(
565 &format!("{}{}", dummy_prefix, path_segment.ident),
566 path_segment.ident.span(),
567 );
568 Ok(ident)
569 } else {
570 Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
571 }
572}
573
574struct GenericArgs<'a>(&'a syn::Generics);
575
576impl ToTokens for GenericArgs<'_> {
577 fn to_tokens(&self, tokens: &mut TokenStream) {
578 tokens_or_default(self.0.lt_token.as_ref(), tokens);
579 for param in self.0.params.pairs() {
580 match param.value() {
581 GenericParam::Lifetime(param) => {
582 param.lifetime.to_tokens(tokens);
583 }
584 GenericParam::Type(param) => {
585 param.ident.to_tokens(tokens);
586 }
587 GenericParam::Const(param) => {
588 param.ident.to_tokens(tokens);
589 }
590 }
591 param.punct().to_tokens(tokens);
592 }
593 tokens_or_default(self.0.gt_token.as_ref(), tokens);
594 }
595}
596
597fn generic_params_to_args(
599 generic_params: &Punctuated<GenericParam, Token!(,)>,
600) -> Punctuated<GenericArgument, Token!(,)> {
601 generic_params
602 .iter()
603 .map(|param| -> GenericArgument {
604 let span = param.span();
605 match param {
606 GenericParam::Type(syn::TypeParam { ident, .. }) => {
607 parse_quote_spanned!(span => #ident )
608 }
609 GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
610 parse_quote_spanned!(span => #lifetime )
611 }
612 GenericParam::Const(syn::ConstParam { ident, .. }) => {
613 parse_quote_spanned!(span => #ident )
614 }
615 }
616 })
617 .collect()
618}
619
620fn generic_params_to_fields(
622 params: &Punctuated<GenericParam, Token![,]>,
623) -> Punctuated<syn::Field, Token![,]> {
624 params
625 .iter()
626 .filter_map(|param| -> Option<syn::Field> {
627 let span = param.span();
628 match param {
629 GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
630 Some(parse_quote_spanned!(span=> &#lifetime ()))
631 }
632 GenericParam::Type(syn::TypeParam { ident, .. }) => {
633 Some(parse_quote_spanned!(span=> #ident))
634 }
635 GenericParam::Const(..) => None,
636 }
637 })
638 .collect()
639}
640
641fn fn_params_to_args(params: &Punctuated<FnArg, Token!(,)>) -> Punctuated<Expr, Token!(,)> {
643 params
644 .iter()
645 .map(|param| -> Expr {
646 match param {
647 FnArg::Typed(pat_type) => {
648 match pat_type.pat.as_ref() {
649 syn::Pat::Ident(pat) => {
650 let ident = &pat.ident;
651 parse_quote!(#ident)
652 }
653 _ => {
654 unimplemented!(
655 "extern specs don't support patterns other than simple identifiers"
656 )
657 }
658 }
659 }
660 FnArg::Receiver(_) => {
661 let span = param.span();
662 parse_quote_spanned!(span=> self)
663 }
664 }
665 })
666 .collect()
667}
668
669struct UseWildcard(syn::Path);
670
671impl ToTokens for UseWildcard {
672 fn to_tokens(&self, tokens: &mut TokenStream) {
673 let path = &self.0;
674 tokens.extend(quote!(use #path::*;))
675 }
676}