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]) || lookahead.peek(Token![unsafe]) {
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 if self.sig.unsafety.is_some() {
415 self.block = Some(quote!({ unsafe { #fn_path :: <#generic_args> ( #fn_args ) } }));
416 } else {
417 self.block = Some(quote!({ #fn_path :: <#generic_args> ( #fn_args ) }));
418 }
419 }
420}
421
422impl ToTokens for ExternFn {
423 fn to_tokens(&self, tokens: &mut TokenStream) {
424 debug_assert!(self.block.is_some());
425 tokens.append_all(&self.attrs);
426 self.sig.to_tokens(tokens);
427 self.block.to_tokens(tokens);
428 }
429}
430
431impl Parse for ExternFn {
432 fn parse(input: ParseStream) -> syn::Result<Self> {
433 let attrs = input.call(Attribute::parse_outer)?;
434 let sig = input.parse()?;
435 input.parse::<Token![;]>()?;
436 Ok(ExternFn { attrs, sig, block: None })
437 }
438}
439
440impl Parse for ExternItemImpl {
441 fn parse(input: ParseStream) -> syn::Result<Self> {
442 let mut attrs = input.call(Attribute::parse_outer)?;
443 let impl_token = input.parse()?;
444 let generics = input.parse()?;
445
446 let mut first_ty: Type = input.parse()?;
447 let self_ty: Type;
448 let trait_;
449
450 let is_impl_for = input.peek(Token![for]);
451 if is_impl_for {
452 let for_token: Token![for] = input.parse()?;
453 let mut first_ty_ref = &first_ty;
454 while let Type::Group(ty) = first_ty_ref {
455 first_ty_ref = &ty.elem;
456 }
457 if let Type::Path(TypePath { qself: None, .. }) = first_ty_ref {
458 while let Type::Group(ty) = first_ty {
459 first_ty = *ty.elem;
460 }
461 if let Type::Path(TypePath { qself: None, path }) = first_ty {
462 trait_ = Some((None, path, for_token));
463 } else {
464 unreachable!();
465 }
466 } else {
467 trait_ = None;
468 }
469 self_ty = input.parse()?;
470 } else {
471 trait_ = None;
472 self_ty = first_ty;
473 }
474
475 let content;
476 let brace_token = braced!(content in input);
477 parse_inner(&content, &mut attrs)?;
478 let mut items = Vec::new();
479 while !content.is_empty() {
480 items.push(content.parse()?);
481 }
482
483 let mut dummy_prefix = "__FluxExternImplStruct".to_string();
484 if let Some(trait_path) = trait_.as_ref().map(|(_, path, _)| path) {
485 dummy_prefix.push_str(&create_dummy_string_from_path(trait_path)?);
486 }
487 let dummy_ident = create_dummy_ident(&mut dummy_prefix, &self_ty)?;
488
489 Ok(ExternItemImpl {
490 attrs,
491 impl_token,
492 generics,
493 trait_,
494 self_ty: Box::new(self_ty),
495 brace_token,
496 items,
497 dummy_ident,
498 })
499 }
500}
501
502impl Parse for ExternItemTrait {
503 fn parse(input: ParseStream) -> syn::Result<Self> {
504 let mut attrs = input.call(Attribute::parse_outer)?;
505 let trait_token = input.parse()?;
506 let ident: Ident = input.parse()?;
507 let mut generics: syn::Generics = input.parse()?;
508 generics.where_clause = input.parse()?;
509
510 let supertrait;
511 let has_super_trait = input.peek(Token![:]);
512 if has_super_trait {
513 input.parse::<Token![:]>()?;
514 supertrait = Some(input.parse::<syn::Path>()?);
515 } else {
516 supertrait = None;
517 }
518 let content;
519 let brace_token = braced!(content in input);
520 parse_inner(&content, &mut attrs)?;
521 let mut items = Vec::new();
522 while !content.is_empty() {
523 items.push(content.parse()?);
524 }
525
526 Ok(ExternItemTrait { attrs, trait_token, ident, generics, supertrait, brace_token, items })
527 }
528}
529
530fn create_dummy_ident(dummy_prefix: &mut String, ty: &syn::Type) -> syn::Result<Ident> {
531 use syn::Type::*;
532 match ty {
533 Reference(ty_ref) => {
534 if ty_ref.mutability.is_some() {
535 dummy_prefix.push_str("Mut");
536 };
537 dummy_prefix.push_str("Ref");
538 create_dummy_ident(dummy_prefix, ty_ref.elem.as_ref())
539 }
540 Slice(ty_slice) => {
541 dummy_prefix.push_str("Slice");
542 create_dummy_ident(dummy_prefix, ty_slice.elem.as_ref())
543 }
544 Path(ty_path) => create_dummy_ident_from_path(dummy_prefix, &ty_path.path),
545 Ptr(ty_ptr) => {
546 if ty_ptr.mutability.is_some() {
547 dummy_prefix.push_str("MutPtr");
548 } else {
549 dummy_prefix.push_str("ConstPtr");
550 };
551 create_dummy_ident(dummy_prefix, ty_ptr.elem.as_ref())
552 }
553 _ => {
554 Err(syn::Error::new(
555 ty.span(),
556 format!("invalid extern_spec: unsupported type {:?}", ty),
557 ))
558 }
559 }
560}
561
562fn create_dummy_string_from_path(path: &syn::Path) -> syn::Result<String> {
563 if let Some(path_segment) = path.segments.last() {
564 let str = format!("{}", path_segment.ident);
566 Ok(str)
567 } else {
568 Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
569 }
570}
571
572fn create_dummy_ident_from_path(dummy_prefix: &str, path: &syn::Path) -> syn::Result<Ident> {
573 if let Some(path_segment) = path.segments.last() {
575 let ident = Ident::new(
577 &format!("{}{}", dummy_prefix, path_segment.ident),
578 path_segment.ident.span(),
579 );
580 Ok(ident)
581 } else {
582 Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
583 }
584}
585
586struct GenericArgs<'a>(&'a syn::Generics);
587
588impl ToTokens for GenericArgs<'_> {
589 fn to_tokens(&self, tokens: &mut TokenStream) {
590 tokens_or_default(self.0.lt_token.as_ref(), tokens);
591 for param in self.0.params.pairs() {
592 match param.value() {
593 GenericParam::Lifetime(param) => {
594 param.lifetime.to_tokens(tokens);
595 }
596 GenericParam::Type(param) => {
597 param.ident.to_tokens(tokens);
598 }
599 GenericParam::Const(param) => {
600 param.ident.to_tokens(tokens);
601 }
602 }
603 param.punct().to_tokens(tokens);
604 }
605 tokens_or_default(self.0.gt_token.as_ref(), tokens);
606 }
607}
608
609fn generic_params_to_args(
611 generic_params: &Punctuated<GenericParam, Token!(,)>,
612) -> Punctuated<GenericArgument, Token!(,)> {
613 generic_params
614 .iter()
615 .map(|param| -> GenericArgument {
616 let span = param.span();
617 match param {
618 GenericParam::Type(syn::TypeParam { ident, .. }) => {
619 parse_quote_spanned!(span => #ident )
620 }
621 GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
622 parse_quote_spanned!(span => #lifetime )
623 }
624 GenericParam::Const(syn::ConstParam { ident, .. }) => {
625 parse_quote_spanned!(span => #ident )
626 }
627 }
628 })
629 .collect()
630}
631
632fn generic_params_to_fields(
634 params: &Punctuated<GenericParam, Token![,]>,
635) -> Punctuated<syn::Field, Token![,]> {
636 params
637 .iter()
638 .filter_map(|param| -> Option<syn::Field> {
639 let span = param.span();
640 match param {
641 GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
642 Some(parse_quote_spanned!(span=> &#lifetime ()))
643 }
644 GenericParam::Type(syn::TypeParam { ident, .. }) => {
645 Some(parse_quote_spanned!(span=> #ident))
646 }
647 GenericParam::Const(..) => None,
648 }
649 })
650 .collect()
651}
652
653fn fn_params_to_args(params: &Punctuated<FnArg, Token!(,)>) -> Punctuated<Expr, Token!(,)> {
655 params
656 .iter()
657 .map(|param| -> Expr {
658 match param {
659 FnArg::Typed(pat_type) => {
660 match pat_type.pat.as_ref() {
661 syn::Pat::Ident(pat) => {
662 let ident = &pat.ident;
663 parse_quote!(#ident)
664 }
665 _ => {
666 unimplemented!(
667 "extern specs don't support patterns other than simple identifiers"
668 )
669 }
670 }
671 }
672 FnArg::Receiver(_) => {
673 let span = param.span();
674 parse_quote_spanned!(span=> self)
675 }
676 }
677 })
678 .collect()
679}
680
681struct UseWildcard(syn::Path);
682
683impl ToTokens for UseWildcard {
684 fn to_tokens(&self, tokens: &mut TokenStream) {
685 let path = &self.0;
686 tokens.extend(quote!(use #path::*;))
687 }
688}