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 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 .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 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 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 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; 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 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 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 if let Some(path_segment) = path.segments.last() {
734 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
768fn 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
791fn 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
812fn 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}