Skip to main content

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
36pub(crate) fn transform_extern_spec_doc(
37    attr: TokenStream,
38    tokens: TokenStream,
39) -> syn::Result<TokenStream> {
40    // Rustdoc cannot attach documentation to an item in another crate. Following the approach
41    // introduced by Creusot, emit searchable documentation-only items instead. Unlike Creusot,
42    // Flux cannot reuse the original signatures because refinement syntax and bodyless inherent
43    // impls do not always type-check as ordinary Rust, so marker items carry the complete
44    // declaration as a code block.
45    let attr_source = attr.to_string();
46    let mod_path: Option<syn::Path> =
47        if !attr.is_empty() { Some(syn::parse2(attr)?) } else { None };
48    let canonical_source = tokens.to_string();
49    let source = doc_source_text(tokens.span(), &canonical_source);
50    let hash = stable_doc_hash(&format!("{attr_source}|{canonical_source}"));
51    let item = syn::parse2::<ExternItem>(tokens)?;
52
53    let tokens = match item {
54        ExternItem::Struct(item) => doc_marker(&mod_path, &item.ident, hash, &source),
55        ExternItem::Enum(item) => doc_marker(&mod_path, &item.ident, hash, &source),
56        ExternItem::Trait(item) => doc_marker(&mod_path, &item.ident, hash, &source),
57        ExternItem::Fn(item) => {
58            let ident = &item.sig.ident;
59            let target = doc_target(&mod_path, ident);
60            let docs = doc_source(&target, &source);
61            let marker = format_ident!("{ident}Spec_{hash:016x}");
62            quote!(
63                #[allow(non_camel_case_types)]
64                #[doc = #docs]
65                pub struct #marker;
66            )
67        }
68        ExternItem::Impl(item) => {
69            let self_name = doc_type_name(&item.self_ty);
70            let base = if let Some((_, path, _)) = &item.trait_ {
71                let trait_name = path.segments.last().unwrap().ident.to_string();
72                format!("{trait_name}For{self_name}")
73            } else {
74                self_name
75            };
76            let ident = format_ident!("{base}Spec_{hash:016x}");
77            let self_ty = &item.self_ty;
78            let declaration = if let Some((_, path, _)) = &item.trait_ {
79                format!("`impl {} for {}`", quote!(#path), quote!(#self_ty))
80            } else {
81                format!("`{}`", quote!(#self_ty))
82            };
83            let target = if let Some(path) = &mod_path {
84                let path = path.to_token_stream().to_string().replace(' ', "");
85                format!("External specifications in `{path}` for {declaration}.")
86            } else {
87                format!("External specifications for {declaration}.")
88            };
89            let docs = doc_source(&target, &source);
90            let methods = item.items.iter().map(|method| {
91                let ident = &method.sig.ident;
92                let attrs = &method.attrs;
93                let sig = &method.sig;
94                let canonical_method_source = quote!(#(#attrs)* #sig;).to_string();
95                let method_span = method
96                    .attrs
97                    .first()
98                    .and_then(|attr| attr.span().join(method.sig.span()))
99                    .unwrap_or_else(|| method.sig.span());
100                let method_source = doc_source_text(method_span, &canonical_method_source)
101                    .trim_end_matches(';')
102                    .to_string()
103                    + ";";
104                let method_docs = doc_source("Original declaration.", &method_source);
105                quote!(
106                    #[allow(non_snake_case)]
107                    #[doc = #method_docs]
108                    pub mod #ident {}
109                )
110            });
111            quote!(
112                #[allow(non_snake_case)]
113                #[doc = #docs]
114                pub mod #ident { #(#methods)* }
115            )
116        }
117    };
118
119    Ok(quote!(#[cfg(doc)] #tokens))
120}
121
122fn doc_marker(mod_path: &Option<syn::Path>, ident: &Ident, hash: u64, source: &str) -> TokenStream {
123    let marker = format_ident!("{ident}Spec_{hash:016x}");
124    let docs = doc_source(&doc_target(mod_path, ident), source);
125    quote!(
126        #[allow(non_camel_case_types)]
127        #[doc = #docs]
128        pub struct #marker;
129    )
130}
131
132fn doc_target(mod_path: &Option<syn::Path>, ident: &Ident) -> String {
133    if let Some(path) = mod_path {
134        let path = path.to_token_stream().to_string().replace(' ', "");
135        format!("External specification for [`{path}::{ident}`].")
136    } else {
137        format!("External specification for `{ident}`.")
138    }
139}
140
141fn doc_source(summary: &str, source: &str) -> String {
142    format!(
143        "{summary}\n\nThis is not a real Rust item; it exists only to document the external specification.\n\n```rust,ignore\n{source}\n```"
144    )
145}
146
147fn doc_source_text(span: Span, canonical_source: &str) -> String {
148    select_doc_source(span.source_text(), canonical_source)
149}
150
151fn select_doc_source(source: Option<String>, canonical_source: &str) -> String {
152    source
153        // Macro-generated spans can point back to template source instead of the concrete
154        // expansion. Preserve source formatting only when it represents the same tokens.
155        .filter(|source| {
156            source
157                .parse::<TokenStream>()
158                .is_ok_and(|tokens| tokens.to_string() == canonical_source)
159        })
160        .unwrap_or_else(|| canonical_source.to_owned())
161}
162
163fn stable_doc_hash(source: &str) -> u64 {
164    // The target path participates in `source`, so otherwise identical specs for different crates
165    // cannot collide. FNV-1a keeps names deterministic across builds and platforms.
166    source
167        .bytes()
168        .fold(14_695_981_039_346_656_037, |hash, byte| {
169            (hash ^ u64::from(byte)).wrapping_mul(1_099_511_628_211)
170        })
171}
172
173fn doc_type_name(ty: &Type) -> String {
174    match ty {
175        Type::Path(path) => {
176            path.path
177                .segments
178                .last()
179                .map_or_else(|| "Type".into(), |segment| segment.ident.to_string())
180        }
181        Type::Reference(reference) => doc_type_name(&reference.elem),
182        Type::Slice(slice) => format!("{}Slice", doc_type_name(&slice.elem)),
183        Type::Array(array) => format!("{}Array", doc_type_name(&array.elem)),
184        Type::Ptr(pointer) => format!("{}Ptr", doc_type_name(&pointer.elem)),
185        _ => "Type".into(),
186    }
187}
188
189fn extern_fn_to_tokens(
190    span: Span,
191    mod_use: Option<UseWildcard>,
192    mut extern_fn: ExternFn,
193) -> syn::Result<TokenStream> {
194    extern_fn.prepare(&FnCtxt::Free, true);
195    Ok(quote_spanned! {span=>
196        #[allow(unused, dead_code, non_camel_case_types)]
197        #[flux_tool::extern_spec]
198        const _: () = {
199            #mod_use
200
201            #extern_fn
202        };
203    })
204}
205
206fn extern_enum_to_tokens(
207    mod_use: Option<UseWildcard>,
208    mut item_enum: syn::ItemEnum,
209) -> syn::Result<TokenStream> {
210    let span = item_enum.span();
211    let ident = item_enum.ident;
212
213    item_enum.ident = format_ident!("__FluxExternSpecEnum__{}", ident);
214
215    flux_tool_attrs(&mut item_enum.attrs);
216    for variant in &mut item_enum.variants {
217        flux_tool_attrs(&mut variant.attrs);
218    }
219
220    let dummy_struct = format_ident!("__FluxExternSpecDummy__{}", ident);
221    let generics = &item_enum.generics;
222    let args = generic_params_to_args(&generics.params);
223
224    Ok(quote_spanned! {span=>
225        #[allow(unused, dead_code, non_camel_case_types)]
226        #[flux_tool::extern_spec]
227        const _: () = {
228            #mod_use
229
230            struct #dummy_struct #generics ( #ident < #args > );
231
232            #item_enum
233        };
234    })
235}
236
237fn extern_struct_to_tokens(
238    mod_use: Option<UseWildcard>,
239    mut item_struct: syn::ItemStruct,
240) -> syn::Result<TokenStream> {
241    let item_struct_span = item_struct.span();
242    let ident = item_struct.ident;
243
244    let generics = &item_struct.generics;
245    let args = generic_params_to_args(&generics.params);
246
247    // Prepare struct
248    item_struct.ident = format_ident!("__FluxExternSpecStruct__{}", ident);
249    flux_tool_attrs(&mut item_struct.attrs);
250    for field in &mut item_struct.fields {
251        flux_tool_attrs(&mut field.attrs);
252    }
253    if let syn::Fields::Unit = &item_struct.fields {
254        if !has_opaque_attr(&item_struct.attrs) {
255            item_struct.attrs.push(parse_quote!(#[flux_tool::opaque]));
256        }
257        item_struct.fields = syn::Fields::Unnamed(parse_quote! { (#ident < #args >) });
258    }
259
260    // Dummy struct used to extract def_id
261    let dummy_struct = format_ident!("__FluxExternSpecDummy__{}", ident);
262
263    Ok(quote_spanned! {item_struct_span =>
264        #[allow(unused, dead_code, non_camel_case_types)]
265        #[flux_tool::extern_spec]
266        const _: () = {
267            #mod_use
268
269            struct #dummy_struct #generics (#ident < #args >);
270
271            #item_struct
272        };
273    })
274}
275
276fn has_opaque_attr(attrs: &[syn::Attribute]) -> bool {
277    attrs
278        .iter()
279        .any(|attr| path_matches(attr.path(), &["flux_tool", "opaque"]))
280}
281
282fn path_matches(path: &syn::Path, x: &[&str]) -> bool {
283    let mut i = 0;
284    for segment in &path.segments {
285        if i == x.len() {
286            return false;
287        }
288        if segment.ident != x[i] {
289            return false;
290        }
291        i += 1;
292    }
293    true
294}
295
296fn extern_trait_to_tokens(
297    span: Span,
298    mod_use: Option<UseWildcard>,
299    mut item_trait: ExternItemTrait,
300) -> syn::Result<TokenStream> {
301    item_trait.prepare();
302    let item_trait = item_trait;
303
304    Ok(quote_spanned! {span =>
305        #[allow(unused, dead_code, non_camel_case_types)]
306        #[flux_tool::extern_spec]
307        const _: () = {
308            #mod_use
309
310            #item_trait
311        };
312    })
313}
314
315fn extern_impl_to_tokens(
316    span: Span,
317    mod_use: Option<UseWildcard>,
318    mut extern_item_impl: ExternItemImpl,
319) -> syn::Result<TokenStream> {
320    extern_item_impl.prepare();
321    let extern_item_impl = extern_item_impl; // no more mutation
322
323    let self_ty = &extern_item_impl.self_ty;
324    let (impl_generics, ty_generics, where_clause) = &extern_item_impl.generics.split_for_impl();
325
326    let dummy_ident = &extern_item_impl.dummy_ident;
327    let mut fields = generic_params_to_fields(&extern_item_impl.generics.params);
328    fields.push(parse_quote!(#self_ty));
329
330    let dummy_impl = if let Some((_, trait_, _)) = &extern_item_impl.trait_ {
331        Some(quote!(
332            impl #impl_generics #dummy_ident #ty_generics #where_clause {
333                fn __flux_extern_extract_impl_id() where #self_ty: #trait_ {}
334            }
335        ))
336    } else {
337        None
338    };
339
340    Ok(quote_spanned! {span=>
341        #[allow(unused, dead_code, non_camel_case_types)]
342        #[flux_tool::extern_spec]
343        const _: () = {
344            #mod_use
345
346            struct #dummy_ident #impl_generics ( #fields ) #where_clause;
347
348            #dummy_impl
349
350            #extern_item_impl
351        };
352    })
353}
354
355enum ExternItem {
356    Struct(syn::ItemStruct),
357    Enum(syn::ItemEnum),
358    Trait(ExternItemTrait),
359    Fn(ExternFn),
360    Impl(ExternItemImpl),
361}
362
363impl ExternItem {
364    fn replace_attrs(&mut self, new: Vec<Attribute>) -> Vec<Attribute> {
365        match self {
366            ExternItem::Struct(syn::ItemStruct { attrs, .. })
367            | ExternItem::Enum(syn::ItemEnum { attrs, .. })
368            | ExternItem::Trait(ExternItemTrait { attrs, .. })
369            | ExternItem::Fn(ExternFn { attrs, .. })
370            | ExternItem::Impl(ExternItemImpl { attrs, .. }) => mem::replace(attrs, new),
371        }
372    }
373}
374
375impl Parse for ExternItem {
376    fn parse(input: ParseStream) -> syn::Result<Self> {
377        let mut attrs = input.call(Attribute::parse_outer)?;
378        let lookahead = input.lookahead1();
379        let mut item = if lookahead.peek(Token![fn]) || lookahead.peek(Token![unsafe]) {
380            ExternItem::Fn(input.parse()?)
381        } else if lookahead.peek(Token![impl]) {
382            ExternItem::Impl(input.parse()?)
383        } else if lookahead.peek(Token![struct]) {
384            ExternItem::Struct(input.parse()?)
385        } else if lookahead.peek(Token![enum]) {
386            let enm = input.parse();
387            ExternItem::Enum(enm?)
388        } else if lookahead.peek(Token![trait]) {
389            ExternItem::Trait(input.parse()?)
390        } else {
391            return Err(lookahead.error());
392        };
393
394        attrs.extend(item.replace_attrs(Vec::new()));
395        item.replace_attrs(attrs);
396        Ok(item)
397    }
398}
399
400struct ExternItemImpl {
401    attrs: Vec<Attribute>,
402    impl_token: Token![impl],
403    generics: Generics,
404    trait_: Option<(Option<Token![!]>, syn::Path, Token![for])>,
405    self_ty: Box<Type>,
406    brace_token: Brace,
407    items: Vec<ExternFn>,
408    dummy_ident: Ident,
409}
410
411impl ExternItemImpl {
412    fn prepare(&mut self) {
413        flux_tool_attrs(&mut self.attrs);
414        let cx = if let Some(trait_) = self.trait_.as_ref().map(|(_, path, _)| path) {
415            FnCtxt::TraitImpl { trait_, self_ty: &self.self_ty }
416        } else {
417            FnCtxt::InherentImpl { self_ty: &self.self_ty }
418        };
419
420        for item in &mut self.items {
421            item.prepare(&cx, false);
422        }
423    }
424}
425
426impl ToTokens for ExternItemImpl {
427    fn to_tokens(&self, tokens: &mut TokenStream) {
428        let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
429
430        tokens.append_all(outer(&self.attrs));
431
432        self.impl_token.to_tokens(tokens);
433        impl_generics.to_tokens(tokens);
434
435        self.dummy_ident.to_tokens(tokens);
436        ty_generics.to_tokens(tokens);
437
438        where_clause.to_tokens(tokens);
439        self.brace_token.surround(tokens, |tokens| {
440            tokens.append_all(inner(&self.attrs));
441            for item in &self.items {
442                item.to_tokens(tokens);
443            }
444        });
445    }
446}
447
448struct ExternItemTrait {
449    attrs: Vec<Attribute>,
450    trait_token: Token![trait],
451    ident: Ident,
452    generics: Generics,
453    supertrait: Option<syn::Path>,
454    brace_token: Brace,
455    items: Vec<ExternFn>,
456}
457
458impl ExternItemTrait {
459    fn prepare(&mut self) {
460        let dummy_ident = format_ident!("__FluxExternTrait{}", self.ident);
461        let ident = std::mem::replace(&mut self.ident, dummy_ident);
462
463        let ident_span = self.ident.span();
464        let args = GenericArgs(&self.generics);
465        let trait_ = parse_quote_spanned!(ident_span=> #ident #args);
466
467        flux_tool_attrs(&mut self.attrs);
468
469        let cx = FnCtxt::Trait { trait_: &trait_ };
470        for item in &mut self.items {
471            item.prepare(&cx, false);
472        }
473
474        self.supertrait = Some(trait_);
475    }
476}
477
478impl ToTokens for ExternItemTrait {
479    fn to_tokens(&self, tokens: &mut TokenStream) {
480        tokens.append_all(outer(&self.attrs));
481        self.trait_token.to_tokens(tokens);
482        self.ident.to_tokens(tokens);
483        self.generics.to_tokens(tokens);
484        if let Some(supertrait) = &self.supertrait {
485            tokens.extend(quote!(: #supertrait));
486        }
487        self.generics.where_clause.to_tokens(tokens);
488        self.brace_token.surround(tokens, |tokens| {
489            tokens.append_all(inner(&self.attrs));
490            for item in &self.items {
491                item.to_tokens(tokens);
492            }
493        })
494    }
495}
496
497enum FnCtxt<'a> {
498    TraitImpl { self_ty: &'a syn::Type, trait_: &'a syn::Path },
499    InherentImpl { self_ty: &'a syn::Type },
500    Trait { trait_: &'a syn::Path },
501    Free,
502}
503
504struct ExternFn {
505    attrs: Vec<Attribute>,
506    sig: Signature,
507    block: Option<TokenStream>,
508}
509
510impl ExternFn {
511    fn prepare(&mut self, cx: &FnCtxt, mangle: bool) {
512        flux_tool_attrs(&mut self.attrs);
513        if let FnCtxt::TraitImpl { self_ty, .. } | FnCtxt::InherentImpl { self_ty } = cx {
514            struct ReplaceSelf<'a> {
515                self_ty: &'a syn::Type,
516            }
517
518            impl syn::visit_mut::VisitMut for ReplaceSelf<'_> {
519                fn visit_type_mut(&mut self, ty: &mut syn::Type) {
520                    if let syn::Type::Path(type_path) = ty {
521                        if type_path.path.is_ident("Self") {
522                            *ty = self.self_ty.clone();
523                        }
524                    }
525                }
526            }
527
528            syn::visit_mut::visit_signature_mut(&mut ReplaceSelf { self_ty }, &mut self.sig);
529
530            self.change_receiver(self_ty);
531        }
532        self.fill_body(cx);
533        if mangle {
534            self.sig.ident = format_ident!("__flux_extern_spec_{}", self.sig.ident);
535        }
536    }
537
538    fn change_receiver(&mut self, self_ty: &syn::Type) {
539        if let Some(first) = self.sig.inputs.first_mut() {
540            if let FnArg::Receiver(receiver) = first {
541                let ident = format_ident!("__self", span = receiver.self_token.span);
542
543                *first = if receiver.colon_token.is_some() {
544                    // If there's a colon this is an arbitrary self types and we leave it as is.
545                    let receiver_ty = &receiver.ty;
546                    parse_quote!(#ident : #receiver_ty)
547                } else if let Some((ampersand, lft)) = &receiver.reference {
548                    let mutbl = receiver.mutability;
549                    parse_quote!(#ident : #ampersand #lft #mutbl #self_ty)
550                } else {
551                    parse_quote!(#ident : #self_ty)
552                };
553            }
554        }
555    }
556
557    fn fill_body(&mut self, cx: &FnCtxt) {
558        let ident = &self.sig.ident;
559        let fn_path = match cx {
560            FnCtxt::TraitImpl { self_ty, trait_ } => quote!(< #self_ty as #trait_ > :: #ident),
561            FnCtxt::InherentImpl { self_ty } => quote!(< #self_ty > :: #ident),
562            FnCtxt::Trait { trait_ } => quote!(< Self as #trait_ > :: #ident),
563            FnCtxt::Free => quote!(#ident),
564        };
565        let generic_args = generic_params_to_args(&self.sig.generics.params);
566        let fn_args = fn_params_to_args(&self.sig.inputs);
567        if self.sig.unsafety.is_some() {
568            self.block = Some(quote!({ unsafe { #fn_path :: <#generic_args> ( #fn_args ) } }));
569        } else {
570            self.block = Some(quote!({ #fn_path :: <#generic_args> ( #fn_args ) }));
571        }
572    }
573}
574
575impl ToTokens for ExternFn {
576    fn to_tokens(&self, tokens: &mut TokenStream) {
577        debug_assert!(self.block.is_some());
578        tokens.append_all(&self.attrs);
579        self.sig.to_tokens(tokens);
580        self.block.to_tokens(tokens);
581    }
582}
583
584impl Parse for ExternFn {
585    fn parse(input: ParseStream) -> syn::Result<Self> {
586        let attrs = input.call(Attribute::parse_outer)?;
587        let sig = input.parse()?;
588        input.parse::<Token![;]>()?;
589        Ok(ExternFn { attrs, sig, block: None })
590    }
591}
592
593impl Parse for ExternItemImpl {
594    fn parse(input: ParseStream) -> syn::Result<Self> {
595        let mut attrs = input.call(Attribute::parse_outer)?;
596        let impl_token = input.parse()?;
597        let mut generics: Generics = input.parse()?;
598
599        let mut first_ty: Type = input.parse()?;
600        let self_ty: Type;
601        let trait_;
602
603        let is_impl_for = input.peek(Token![for]);
604        if is_impl_for {
605            let for_token: Token![for] = input.parse()?;
606            let mut first_ty_ref = &first_ty;
607            while let Type::Group(ty) = first_ty_ref {
608                first_ty_ref = &ty.elem;
609            }
610            if let Type::Path(TypePath { qself: None, .. }) = first_ty_ref {
611                while let Type::Group(ty) = first_ty {
612                    first_ty = *ty.elem;
613                }
614                if let Type::Path(TypePath { qself: None, path }) = first_ty {
615                    trait_ = Some((None, path, for_token));
616                } else {
617                    unreachable!();
618                }
619            } else {
620                trait_ = None;
621            }
622            self_ty = input.parse()?;
623        } else {
624            trait_ = None;
625            self_ty = first_ty;
626        }
627
628        generics.where_clause = input.parse()?;
629
630        let content;
631        let brace_token = braced!(content in input);
632        parse_inner(&content, &mut attrs)?;
633        let mut items = Vec::new();
634        while !content.is_empty() {
635            items.push(content.parse()?);
636        }
637
638        let mut dummy_prefix = "__FluxExternImplStruct".to_string();
639        if let Some(trait_path) = trait_.as_ref().map(|(_, path, _)| path) {
640            dummy_prefix.push_str(&create_dummy_string_from_path(trait_path)?);
641        }
642        let dummy_ident = create_dummy_ident(&mut dummy_prefix, &self_ty)?;
643
644        Ok(ExternItemImpl {
645            attrs,
646            impl_token,
647            generics,
648            trait_,
649            self_ty: Box::new(self_ty),
650            brace_token,
651            items,
652            dummy_ident,
653        })
654    }
655}
656
657impl Parse for ExternItemTrait {
658    fn parse(input: ParseStream) -> syn::Result<Self> {
659        let mut attrs = input.call(Attribute::parse_outer)?;
660        let trait_token = input.parse()?;
661        let ident: Ident = input.parse()?;
662        let mut generics: syn::Generics = input.parse()?;
663        generics.where_clause = input.parse()?;
664
665        let supertrait;
666        let has_super_trait = input.peek(Token![:]);
667        if has_super_trait {
668            input.parse::<Token![:]>()?;
669            supertrait = Some(input.parse::<syn::Path>()?);
670        } else {
671            supertrait = None;
672        }
673        let content;
674        let brace_token = braced!(content in input);
675        parse_inner(&content, &mut attrs)?;
676        let mut items = Vec::new();
677        while !content.is_empty() {
678            items.push(content.parse()?);
679        }
680
681        Ok(ExternItemTrait { attrs, trait_token, ident, generics, supertrait, brace_token, items })
682    }
683}
684
685fn create_dummy_ident(dummy_prefix: &mut String, ty: &syn::Type) -> syn::Result<Ident> {
686    use syn::Type::*;
687    match ty {
688        Reference(ty_ref) => {
689            if ty_ref.mutability.is_some() {
690                dummy_prefix.push_str("Mut");
691            };
692            dummy_prefix.push_str("Ref");
693            create_dummy_ident(dummy_prefix, ty_ref.elem.as_ref())
694        }
695        Slice(ty_slice) => {
696            dummy_prefix.push_str("Slice");
697            create_dummy_ident(dummy_prefix, ty_slice.elem.as_ref())
698        }
699        Path(ty_path) => create_dummy_ident_from_path(dummy_prefix, &ty_path.path),
700        Ptr(ty_ptr) => {
701            if ty_ptr.mutability.is_some() {
702                dummy_prefix.push_str("MutPtr");
703            } else {
704                dummy_prefix.push_str("ConstPtr");
705            };
706            create_dummy_ident(dummy_prefix, ty_ptr.elem.as_ref())
707        }
708        Array(ty_array) => {
709            dummy_prefix.push_str("Array");
710            create_dummy_ident(dummy_prefix, ty_array.elem.as_ref())
711        }
712        _ => {
713            Err(syn::Error::new(
714                ty.span(),
715                format!("invalid extern_spec: unsupported type {:?}", ty),
716            ))
717        }
718    }
719}
720
721fn create_dummy_string_from_path(path: &syn::Path) -> syn::Result<String> {
722    if let Some(path_segment) = path.segments.last() {
723        // Mangle the identifier using the dummy_prefix
724        let str = format!("{}", path_segment.ident);
725        Ok(str)
726    } else {
727        Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
728    }
729}
730
731fn create_dummy_ident_from_path(dummy_prefix: &str, path: &syn::Path) -> syn::Result<Ident> {
732    // For paths, we mangle the last identifier
733    if let Some(path_segment) = path.segments.last() {
734        // Mangle the identifier using the dummy_prefix
735        let ident = Ident::new(
736            &format!("{}{}", dummy_prefix, path_segment.ident),
737            path_segment.ident.span(),
738        );
739        Ok(ident)
740    } else {
741        Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
742    }
743}
744
745struct GenericArgs<'a>(&'a syn::Generics);
746
747impl ToTokens for GenericArgs<'_> {
748    fn to_tokens(&self, tokens: &mut TokenStream) {
749        tokens_or_default(self.0.lt_token.as_ref(), tokens);
750        for param in self.0.params.pairs() {
751            match param.value() {
752                GenericParam::Lifetime(param) => {
753                    param.lifetime.to_tokens(tokens);
754                }
755                GenericParam::Type(param) => {
756                    param.ident.to_tokens(tokens);
757                }
758                GenericParam::Const(param) => {
759                    param.ident.to_tokens(tokens);
760                }
761            }
762            param.punct().to_tokens(tokens);
763        }
764        tokens_or_default(self.0.gt_token.as_ref(), tokens);
765    }
766}
767
768// Cribbed from Prusti's extern_spec_rewriter
769fn generic_params_to_args(
770    generic_params: &Punctuated<GenericParam, Token!(,)>,
771) -> Punctuated<GenericArgument, Token!(,)> {
772    generic_params
773        .iter()
774        .map(|param| -> GenericArgument {
775            let span = param.span();
776            match param {
777                GenericParam::Type(syn::TypeParam { ident, .. }) => {
778                    parse_quote_spanned!(span => #ident )
779                }
780                GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
781                    parse_quote_spanned!(span => #lifetime )
782                }
783                GenericParam::Const(syn::ConstParam { ident, .. }) => {
784                    parse_quote_spanned!(span => #ident )
785                }
786            }
787        })
788        .collect()
789}
790
791/// Given a list of generic parameters creates a list of fields that use all non-const parameters
792fn generic_params_to_fields(
793    params: &Punctuated<GenericParam, Token![,]>,
794) -> Punctuated<syn::Field, Token![,]> {
795    params
796        .iter()
797        .filter_map(|param| -> Option<syn::Field> {
798            let span = param.span();
799            match param {
800                GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
801                    Some(parse_quote_spanned!(span=> &#lifetime ()))
802                }
803                GenericParam::Type(syn::TypeParam { ident, .. }) => {
804                    Some(parse_quote_spanned!(span=> #ident))
805                }
806                GenericParam::Const(..) => None,
807            }
808        })
809        .collect()
810}
811
812// Cribbed from Prusti's extern_spec_rewriter
813fn fn_params_to_args(params: &Punctuated<FnArg, Token!(,)>) -> Punctuated<Expr, Token!(,)> {
814    params
815        .iter()
816        .map(|param| -> Expr {
817            match param {
818                FnArg::Typed(pat_type) => {
819                    match pat_type.pat.as_ref() {
820                        syn::Pat::Ident(pat) => {
821                            let ident = &pat.ident;
822                            parse_quote!(#ident)
823                        }
824                        _ => {
825                            unimplemented!(
826                                "extern specs don't support patterns other than simple identifiers"
827                            )
828                        }
829                    }
830                }
831                FnArg::Receiver(_) => {
832                    let span = param.span();
833                    parse_quote_spanned!(span=> self)
834                }
835            }
836        })
837        .collect()
838}
839
840struct UseWildcard(syn::Path);
841
842impl ToTokens for UseWildcard {
843    fn to_tokens(&self, tokens: &mut TokenStream) {
844        let path = &self.0;
845        tokens.extend(quote!(use #path::*;))
846    }
847}
848
849#[cfg(test)]
850mod tests {
851    use quote::quote;
852
853    use super::{select_doc_source, stable_doc_hash, transform_extern_spec_doc};
854
855    #[test]
856    fn documents_free_function_contract() {
857        let tokens = transform_extern_spec_doc(
858            quote!(core::mem),
859            quote!(
860                #[sig(fn(usize) -> usize)]
861                fn size_of_val(value: usize) -> usize;
862            ),
863        )
864        .unwrap()
865        .to_string();
866
867        assert!(tokens.contains("cfg (doc)"));
868        assert!(tokens.contains("pub struct size_of_valSpec_"));
869        assert!(tokens.contains("sig (fn (usize) -> usize)"));
870        assert!(tokens.contains("core::mem::size_of_val"));
871        assert!(tokens.contains("not a real Rust item"));
872    }
873
874    #[test]
875    fn documents_impl_methods_as_nested_modules() {
876        let tokens = transform_extern_spec_doc(
877            quote!(core::option),
878            quote!(
879                impl<T> Option<T> {
880                    #[sig(fn(&Self[@b]) -> bool[b])]
881                    const fn is_some(&self) -> bool;
882                }
883            ),
884        )
885        .unwrap()
886        .to_string();
887
888        assert!(tokens.contains("pub mod OptionSpec_"));
889        assert!(tokens.contains("pub mod is_some"));
890        assert!(tokens.contains("sig (fn (& Self [@ b]) -> bool [b])"));
891        assert!(tokens.contains("core::option"));
892    }
893
894    #[test]
895    fn target_path_affects_name_and_summary() {
896        let item = quote!(
897            struct Ordering;
898        );
899        let core = transform_extern_spec_doc(quote!(core::cmp), item.clone())
900            .unwrap()
901            .to_string();
902        let std = transform_extern_spec_doc(quote!(std::cmp), item)
903            .unwrap()
904            .to_string();
905
906        assert_ne!(core, std);
907        assert!(core.contains("core::cmp::Ordering"));
908        assert!(std.contains("std::cmp::Ordering"));
909    }
910
911    #[test]
912    fn documentation_hash_is_stable() {
913        assert_eq!(stable_doc_hash("flux"), 0xd61b_dd79_08af_2642);
914    }
915
916    #[test]
917    fn macro_metavariables_fall_back_to_expanded_tokens() {
918        assert_eq!(select_doc_source(Some("impl $T {}".into()), "impl usize {}"), "impl usize {}");
919    }
920
921    #[test]
922    fn equivalent_source_preserves_authored_formatting() {
923        let source = "fn get(\n    value: usize,\n) -> usize;";
924        assert_eq!(
925            select_doc_source(Some(source.into()), "fn get (value : usize ,) -> usize ;"),
926            source
927        );
928    }
929}