flux_attrs_impl/
extern_spec.rs

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    // Prepare struct
95    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    // Dummy struct used to extract def_id
108    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; // no more mutation
169
170    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                    // If there's a colon this is an arbitrary self types and we leave it as is.
392                    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 mut generics: 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        generics.where_clause = input.parse()?;
476
477        let content;
478        let brace_token = braced!(content in input);
479        parse_inner(&content, &mut attrs)?;
480        let mut items = Vec::new();
481        while !content.is_empty() {
482            items.push(content.parse()?);
483        }
484
485        let mut dummy_prefix = "__FluxExternImplStruct".to_string();
486        if let Some(trait_path) = trait_.as_ref().map(|(_, path, _)| path) {
487            dummy_prefix.push_str(&create_dummy_string_from_path(trait_path)?);
488        }
489        let dummy_ident = create_dummy_ident(&mut dummy_prefix, &self_ty)?;
490
491        Ok(ExternItemImpl {
492            attrs,
493            impl_token,
494            generics,
495            trait_,
496            self_ty: Box::new(self_ty),
497            brace_token,
498            items,
499            dummy_ident,
500        })
501    }
502}
503
504impl Parse for ExternItemTrait {
505    fn parse(input: ParseStream) -> syn::Result<Self> {
506        let mut attrs = input.call(Attribute::parse_outer)?;
507        let trait_token = input.parse()?;
508        let ident: Ident = input.parse()?;
509        let mut generics: syn::Generics = input.parse()?;
510        generics.where_clause = input.parse()?;
511
512        let supertrait;
513        let has_super_trait = input.peek(Token![:]);
514        if has_super_trait {
515            input.parse::<Token![:]>()?;
516            supertrait = Some(input.parse::<syn::Path>()?);
517        } else {
518            supertrait = None;
519        }
520        let content;
521        let brace_token = braced!(content in input);
522        parse_inner(&content, &mut attrs)?;
523        let mut items = Vec::new();
524        while !content.is_empty() {
525            items.push(content.parse()?);
526        }
527
528        Ok(ExternItemTrait { attrs, trait_token, ident, generics, supertrait, brace_token, items })
529    }
530}
531
532fn create_dummy_ident(dummy_prefix: &mut String, ty: &syn::Type) -> syn::Result<Ident> {
533    use syn::Type::*;
534    match ty {
535        Reference(ty_ref) => {
536            if ty_ref.mutability.is_some() {
537                dummy_prefix.push_str("Mut");
538            };
539            dummy_prefix.push_str("Ref");
540            create_dummy_ident(dummy_prefix, ty_ref.elem.as_ref())
541        }
542        Slice(ty_slice) => {
543            dummy_prefix.push_str("Slice");
544            create_dummy_ident(dummy_prefix, ty_slice.elem.as_ref())
545        }
546        Path(ty_path) => create_dummy_ident_from_path(dummy_prefix, &ty_path.path),
547        Ptr(ty_ptr) => {
548            if ty_ptr.mutability.is_some() {
549                dummy_prefix.push_str("MutPtr");
550            } else {
551                dummy_prefix.push_str("ConstPtr");
552            };
553            create_dummy_ident(dummy_prefix, ty_ptr.elem.as_ref())
554        }
555        Array(ty_array) => {
556            dummy_prefix.push_str("Array");
557            create_dummy_ident(dummy_prefix, ty_array.elem.as_ref())
558        }
559        _ => {
560            Err(syn::Error::new(
561                ty.span(),
562                format!("invalid extern_spec: unsupported type {:?}", ty),
563            ))
564        }
565    }
566}
567
568fn create_dummy_string_from_path(path: &syn::Path) -> syn::Result<String> {
569    if let Some(path_segment) = path.segments.last() {
570        // Mangle the identifier using the dummy_prefix
571        let str = format!("{}", path_segment.ident);
572        Ok(str)
573    } else {
574        Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
575    }
576}
577
578fn create_dummy_ident_from_path(dummy_prefix: &str, path: &syn::Path) -> syn::Result<Ident> {
579    // For paths, we mangle the last identifier
580    if let Some(path_segment) = path.segments.last() {
581        // Mangle the identifier using the dummy_prefix
582        let ident = Ident::new(
583            &format!("{}{}", dummy_prefix, path_segment.ident),
584            path_segment.ident.span(),
585        );
586        Ok(ident)
587    } else {
588        Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
589    }
590}
591
592struct GenericArgs<'a>(&'a syn::Generics);
593
594impl ToTokens for GenericArgs<'_> {
595    fn to_tokens(&self, tokens: &mut TokenStream) {
596        tokens_or_default(self.0.lt_token.as_ref(), tokens);
597        for param in self.0.params.pairs() {
598            match param.value() {
599                GenericParam::Lifetime(param) => {
600                    param.lifetime.to_tokens(tokens);
601                }
602                GenericParam::Type(param) => {
603                    param.ident.to_tokens(tokens);
604                }
605                GenericParam::Const(param) => {
606                    param.ident.to_tokens(tokens);
607                }
608            }
609            param.punct().to_tokens(tokens);
610        }
611        tokens_or_default(self.0.gt_token.as_ref(), tokens);
612    }
613}
614
615// Cribbed from Prusti's extern_spec_rewriter
616fn generic_params_to_args(
617    generic_params: &Punctuated<GenericParam, Token!(,)>,
618) -> Punctuated<GenericArgument, Token!(,)> {
619    generic_params
620        .iter()
621        .map(|param| -> GenericArgument {
622            let span = param.span();
623            match param {
624                GenericParam::Type(syn::TypeParam { ident, .. }) => {
625                    parse_quote_spanned!(span => #ident )
626                }
627                GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
628                    parse_quote_spanned!(span => #lifetime )
629                }
630                GenericParam::Const(syn::ConstParam { ident, .. }) => {
631                    parse_quote_spanned!(span => #ident )
632                }
633            }
634        })
635        .collect()
636}
637
638/// Given a list of generic parameters creates a list of fields that use all non-const parameters
639fn generic_params_to_fields(
640    params: &Punctuated<GenericParam, Token![,]>,
641) -> Punctuated<syn::Field, Token![,]> {
642    params
643        .iter()
644        .filter_map(|param| -> Option<syn::Field> {
645            let span = param.span();
646            match param {
647                GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
648                    Some(parse_quote_spanned!(span=> &#lifetime ()))
649                }
650                GenericParam::Type(syn::TypeParam { ident, .. }) => {
651                    Some(parse_quote_spanned!(span=> #ident))
652                }
653                GenericParam::Const(..) => None,
654            }
655        })
656        .collect()
657}
658
659// Cribbed from Prusti's extern_spec_rewriter
660fn fn_params_to_args(params: &Punctuated<FnArg, Token!(,)>) -> Punctuated<Expr, Token!(,)> {
661    params
662        .iter()
663        .map(|param| -> Expr {
664            match param {
665                FnArg::Typed(pat_type) => {
666                    match pat_type.pat.as_ref() {
667                        syn::Pat::Ident(pat) => {
668                            let ident = &pat.ident;
669                            parse_quote!(#ident)
670                        }
671                        _ => {
672                            unimplemented!(
673                                "extern specs don't support patterns other than simple identifiers"
674                            )
675                        }
676                    }
677                }
678                FnArg::Receiver(_) => {
679                    let span = param.span();
680                    parse_quote_spanned!(span=> self)
681                }
682            }
683        })
684        .collect()
685}
686
687struct UseWildcard(syn::Path);
688
689impl ToTokens for UseWildcard {
690    fn to_tokens(&self, tokens: &mut TokenStream) {
691        let path = &self.0;
692        tokens.extend(quote!(use #path::*;))
693    }
694}