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]) {
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        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        // Mangle the identifier using the dummy_prefix
553        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    // For paths, we mangle the last identifier
562    if let Some(path_segment) = path.segments.last() {
563        // Mangle the identifier using the dummy_prefix
564        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
597// Cribbed from Prusti's extern_spec_rewriter
598fn 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
620/// Given a list of generic parameters creates a list of fields that use all non-const parameters
621fn 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
641// Cribbed from Prusti's extern_spec_rewriter
642fn 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}