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, true);
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, true);
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, false);
567 let fn_args = fn_params_to_args(&self.sig.inputs);
568 if self.sig.unsafety.is_some() {
569 self.block = Some(quote!({ unsafe { #fn_path :: <#generic_args> ( #fn_args ) } }));
570 } else {
571 self.block = Some(quote!({ #fn_path :: <#generic_args> ( #fn_args ) }));
572 }
573 }
574}
575
576impl ToTokens for ExternFn {
577 fn to_tokens(&self, tokens: &mut TokenStream) {
578 debug_assert!(self.block.is_some());
579 tokens.append_all(&self.attrs);
580 self.sig.to_tokens(tokens);
581 self.block.to_tokens(tokens);
582 }
583}
584
585impl Parse for ExternFn {
586 fn parse(input: ParseStream) -> syn::Result<Self> {
587 let attrs = input.call(Attribute::parse_outer)?;
588 let sig = input.parse()?;
589 input.parse::<Token![;]>()?;
590 Ok(ExternFn { attrs, sig, block: None })
591 }
592}
593
594impl Parse for ExternItemImpl {
595 fn parse(input: ParseStream) -> syn::Result<Self> {
596 let mut attrs = input.call(Attribute::parse_outer)?;
597 let impl_token = input.parse()?;
598 let mut generics: Generics = input.parse()?;
599
600 let mut first_ty: Type = input.parse()?;
601 let self_ty: Type;
602 let trait_;
603
604 let is_impl_for = input.peek(Token![for]);
605 if is_impl_for {
606 let for_token: Token![for] = input.parse()?;
607 let mut first_ty_ref = &first_ty;
608 while let Type::Group(ty) = first_ty_ref {
609 first_ty_ref = &ty.elem;
610 }
611 if let Type::Path(TypePath { qself: None, .. }) = first_ty_ref {
612 while let Type::Group(ty) = first_ty {
613 first_ty = *ty.elem;
614 }
615 if let Type::Path(TypePath { qself: None, path }) = first_ty {
616 trait_ = Some((None, path, for_token));
617 } else {
618 unreachable!();
619 }
620 } else {
621 trait_ = None;
622 }
623 self_ty = input.parse()?;
624 } else {
625 trait_ = None;
626 self_ty = first_ty;
627 }
628
629 generics.where_clause = input.parse()?;
630
631 let content;
632 let brace_token = braced!(content in input);
633 parse_inner(&content, &mut attrs)?;
634 let mut items = Vec::new();
635 while !content.is_empty() {
636 items.push(content.parse()?);
637 }
638
639 let mut dummy_prefix = "__FluxExternImplStruct".to_string();
640 if let Some(trait_path) = trait_.as_ref().map(|(_, path, _)| path) {
641 dummy_prefix.push_str(&create_dummy_string_from_path(trait_path)?);
642 }
643 let dummy_ident = create_dummy_ident(&mut dummy_prefix, &self_ty)?;
644
645 Ok(ExternItemImpl {
646 attrs,
647 impl_token,
648 generics,
649 trait_,
650 self_ty: Box::new(self_ty),
651 brace_token,
652 items,
653 dummy_ident,
654 })
655 }
656}
657
658impl Parse for ExternItemTrait {
659 fn parse(input: ParseStream) -> syn::Result<Self> {
660 let mut attrs = input.call(Attribute::parse_outer)?;
661 let trait_token = input.parse()?;
662 let ident: Ident = input.parse()?;
663 let mut generics: syn::Generics = input.parse()?;
664 generics.where_clause = input.parse()?;
665
666 let supertrait;
667 let has_super_trait = input.peek(Token![:]);
668 if has_super_trait {
669 input.parse::<Token![:]>()?;
670 supertrait = Some(input.parse::<syn::Path>()?);
671 } else {
672 supertrait = None;
673 }
674 let content;
675 let brace_token = braced!(content in input);
676 parse_inner(&content, &mut attrs)?;
677 let mut items = Vec::new();
678 while !content.is_empty() {
679 items.push(content.parse()?);
680 }
681
682 Ok(ExternItemTrait { attrs, trait_token, ident, generics, supertrait, brace_token, items })
683 }
684}
685
686fn create_dummy_ident(dummy_prefix: &mut String, ty: &syn::Type) -> syn::Result<Ident> {
687 use syn::Type::*;
688 match ty {
689 Reference(ty_ref) => {
690 if ty_ref.mutability.is_some() {
691 dummy_prefix.push_str("Mut");
692 };
693 dummy_prefix.push_str("Ref");
694 create_dummy_ident(dummy_prefix, ty_ref.elem.as_ref())
695 }
696 Slice(ty_slice) => {
697 dummy_prefix.push_str("Slice");
698 create_dummy_ident(dummy_prefix, ty_slice.elem.as_ref())
699 }
700 Path(ty_path) => create_dummy_ident_from_path(dummy_prefix, &ty_path.path),
701 Ptr(ty_ptr) => {
702 if ty_ptr.mutability.is_some() {
703 dummy_prefix.push_str("MutPtr");
704 } else {
705 dummy_prefix.push_str("ConstPtr");
706 };
707 create_dummy_ident(dummy_prefix, ty_ptr.elem.as_ref())
708 }
709 Array(ty_array) => {
710 dummy_prefix.push_str("Array");
711 create_dummy_ident(dummy_prefix, ty_array.elem.as_ref())
712 }
713 _ => {
714 Err(syn::Error::new(
715 ty.span(),
716 format!("invalid extern_spec: unsupported type {:?}", ty),
717 ))
718 }
719 }
720}
721
722fn create_dummy_string_from_path(path: &syn::Path) -> syn::Result<String> {
723 if let Some(path_segment) = path.segments.last() {
724 let str = format!("{}", path_segment.ident);
726 Ok(str)
727 } else {
728 Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
729 }
730}
731
732fn create_dummy_ident_from_path(dummy_prefix: &str, path: &syn::Path) -> syn::Result<Ident> {
733 if let Some(path_segment) = path.segments.last() {
735 let ident = Ident::new(
737 &format!("{}{}", dummy_prefix, path_segment.ident),
738 path_segment.ident.span(),
739 );
740 Ok(ident)
741 } else {
742 Err(syn::Error::new(path.span(), format!("invalid extern_spec: empty Path {:?}", path)))
743 }
744}
745
746struct GenericArgs<'a>(&'a syn::Generics);
747
748impl ToTokens for GenericArgs<'_> {
749 fn to_tokens(&self, tokens: &mut TokenStream) {
750 tokens_or_default(self.0.lt_token.as_ref(), tokens);
751 for param in self.0.params.pairs() {
752 match param.value() {
753 GenericParam::Lifetime(param) => {
754 param.lifetime.to_tokens(tokens);
755 }
756 GenericParam::Type(param) => {
757 param.ident.to_tokens(tokens);
758 }
759 GenericParam::Const(param) => {
760 param.ident.to_tokens(tokens);
761 }
762 }
763 param.punct().to_tokens(tokens);
764 }
765 tokens_or_default(self.0.gt_token.as_ref(), tokens);
766 }
767}
768
769fn generic_params_to_args(
772 generic_params: &Punctuated<GenericParam, Token!(,)>,
773 keep_lifetimes: bool,
774) -> Punctuated<GenericArgument, Token!(,)> {
775 generic_params
776 .iter()
777 .filter(|param| keep_lifetimes || !matches!(param, GenericParam::Lifetime(_)))
778 .map(|param| -> GenericArgument {
779 let span = param.span();
780 match param {
781 GenericParam::Type(syn::TypeParam { ident, .. }) => {
782 parse_quote_spanned!(span => #ident )
783 }
784 GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
785 parse_quote_spanned!(span => #lifetime )
786 }
787 GenericParam::Const(syn::ConstParam { ident, .. }) => {
788 parse_quote_spanned!(span => #ident )
789 }
790 }
791 })
792 .collect()
793}
794
795fn generic_params_to_fields(
797 params: &Punctuated<GenericParam, Token![,]>,
798) -> Punctuated<syn::Field, Token![,]> {
799 params
800 .iter()
801 .filter_map(|param| -> Option<syn::Field> {
802 let span = param.span();
803 match param {
804 GenericParam::Lifetime(syn::LifetimeParam { lifetime, .. }) => {
805 Some(parse_quote_spanned!(span=> &#lifetime ()))
806 }
807 GenericParam::Type(syn::TypeParam { ident, .. }) => {
810 Some(parse_quote_spanned!(span=> ::core::marker::PhantomData<#ident>))
811 }
812 GenericParam::Const(..) => None,
813 }
814 })
815 .collect()
816}
817
818fn fn_params_to_args(params: &Punctuated<FnArg, Token!(,)>) -> Punctuated<Expr, Token!(,)> {
820 params
821 .iter()
822 .map(|param| -> Expr {
823 match param {
824 FnArg::Typed(pat_type) => {
825 match pat_type.pat.as_ref() {
826 syn::Pat::Ident(pat) => {
827 let ident = &pat.ident;
828 parse_quote!(#ident)
829 }
830 _ => {
831 unimplemented!(
832 "extern specs don't support patterns other than simple identifiers"
833 )
834 }
835 }
836 }
837 FnArg::Receiver(_) => {
838 let span = param.span();
839 parse_quote_spanned!(span=> self)
840 }
841 }
842 })
843 .collect()
844}
845
846struct UseWildcard(syn::Path);
847
848impl ToTokens for UseWildcard {
849 fn to_tokens(&self, tokens: &mut TokenStream) {
850 let path = &self.0;
851 tokens.extend(quote!(use #path::*;))
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use quote::quote;
858
859 use super::{select_doc_source, stable_doc_hash, transform_extern_spec_doc};
860
861 #[test]
862 fn documents_free_function_contract() {
863 let tokens = transform_extern_spec_doc(
864 quote!(core::mem),
865 quote!(
866 #[sig(fn(usize) -> usize)]
867 fn size_of_val(value: usize) -> usize;
868 ),
869 )
870 .unwrap()
871 .to_string();
872
873 assert!(tokens.contains("cfg (doc)"));
874 assert!(tokens.contains("pub struct size_of_valSpec_"));
875 assert!(tokens.contains("sig (fn (usize) -> usize)"));
876 assert!(tokens.contains("core::mem::size_of_val"));
877 assert!(tokens.contains("not a real Rust item"));
878 }
879
880 #[test]
881 fn documents_impl_methods_as_nested_modules() {
882 let tokens = transform_extern_spec_doc(
883 quote!(core::option),
884 quote!(
885 impl<T> Option<T> {
886 #[sig(fn(&Self[@b]) -> bool[b])]
887 const fn is_some(&self) -> bool;
888 }
889 ),
890 )
891 .unwrap()
892 .to_string();
893
894 assert!(tokens.contains("pub mod OptionSpec_"));
895 assert!(tokens.contains("pub mod is_some"));
896 assert!(tokens.contains("sig (fn (& Self [@ b]) -> bool [b])"));
897 assert!(tokens.contains("core::option"));
898 }
899
900 #[test]
901 fn target_path_affects_name_and_summary() {
902 let item = quote!(
903 struct Ordering;
904 );
905 let core = transform_extern_spec_doc(quote!(core::cmp), item.clone())
906 .unwrap()
907 .to_string();
908 let std = transform_extern_spec_doc(quote!(std::cmp), item)
909 .unwrap()
910 .to_string();
911
912 assert_ne!(core, std);
913 assert!(core.contains("core::cmp::Ordering"));
914 assert!(std.contains("std::cmp::Ordering"));
915 }
916
917 #[test]
918 fn documentation_hash_is_stable() {
919 assert_eq!(stable_doc_hash("flux"), 0xd61b_dd79_08af_2642);
920 }
921
922 #[test]
923 fn macro_metavariables_fall_back_to_expanded_tokens() {
924 assert_eq!(select_doc_source(Some("impl $T {}".into()), "impl usize {}"), "impl usize {}");
925 }
926
927 #[test]
928 fn equivalent_source_preserves_authored_formatting() {
929 let source = "fn get(\n value: usize,\n) -> usize;";
930 assert_eq!(
931 select_doc_source(Some(source.into()), "fn get (value : usize ,) -> usize ;"),
932 source
933 );
934 }
935}