linkme_impl/
element.rs

1use crate::{attr, ty};
2use proc_macro2::{Span, TokenStream, TokenTree};
3use quote::{format_ident, quote, quote_spanned};
4use syn::parse::{Error, Parse, ParseStream, Result};
5use syn::punctuated::Punctuated;
6use syn::{
7    braced, parenthesized, parse_quote, Abi, Attribute, BareFnArg, BoundLifetimes, GenericParam,
8    Generics, Ident, Path, ReturnType, Token, Type, TypeBareFn, Visibility, WhereClause,
9};
10
11pub struct Element {
12    attrs: Vec<Attribute>,
13    vis: Visibility,
14    ident: Ident,
15    ty: Type,
16    expr: TokenStream,
17    orig_item: Option<TokenStream>,
18    start_span: Span,
19    end_span: Span,
20}
21
22impl Parse for Element {
23    fn parse(input: ParseStream) -> Result<Self> {
24        let attrs = input.call(Attribute::parse_outer)?;
25        let item = input.cursor();
26        let vis: Visibility = input.parse()?;
27        let static_token: Option<Token![static]> = input.parse()?;
28        if static_token.is_some() {
29            let mut_token: Option<Token![mut]> = input.parse()?;
30            if let Some(mut_token) = mut_token {
31                return Err(Error::new_spanned(
32                    mut_token,
33                    "static mut is not supported by distributed_slice",
34                ));
35            }
36            let ident: Ident = input.parse()?;
37            input.parse::<Token![:]>()?;
38            let start_span = input.span();
39            let ty: Type = input.parse()?;
40            let end_span = quote!(#ty).into_iter().last().unwrap().span();
41            input.parse::<Token![=]>()?;
42            let mut expr_semi = Vec::from_iter(input.parse::<TokenStream>()?);
43            if let Some(tail) = expr_semi.pop() {
44                syn::parse2::<Token![;]>(TokenStream::from(tail))?;
45            }
46            let expr = TokenStream::from_iter(expr_semi);
47            Ok(Element {
48                attrs,
49                vis,
50                ident,
51                ty,
52                expr,
53                orig_item: None,
54                start_span,
55                end_span,
56            })
57        } else {
58            let constness: Option<Token![const]> = input.parse()?;
59            let asyncness: Option<Token![async]> = input.parse()?;
60            let unsafety: Option<Token![unsafe]> = input.parse()?;
61            let abi: Option<Abi> = input.parse()?;
62            let fn_token: Token![fn] = input.parse().map_err(|_| {
63                Error::new_spanned(
64                    item.token_stream(),
65                    "distributed element must be either static or function item",
66                )
67            })?;
68            let ident: Ident = input.parse()?;
69            let generics: Generics = input.parse()?;
70
71            let content;
72            let paren_token = parenthesized!(content in input);
73            let mut inputs = Punctuated::new();
74            while !content.is_empty() {
75                content.parse::<Option<Token![mut]>>()?;
76                let ident = if let Some(wild) = content.parse::<Option<Token![_]>>()? {
77                    Ident::from(wild)
78                } else {
79                    content.parse()?
80                };
81                let colon_token: Token![:] = content.parse()?;
82                let ty: Type = content.parse()?;
83                inputs.push_value(BareFnArg {
84                    attrs: Vec::new(),
85                    name: Some((ident, colon_token)),
86                    ty,
87                });
88                if !content.is_empty() {
89                    let comma: Token![,] = content.parse()?;
90                    inputs.push_punct(comma);
91                }
92            }
93
94            let output: ReturnType = input.parse()?;
95            let where_clause: Option<WhereClause> = input.parse()?;
96
97            let content;
98            braced!(content in input);
99            content.parse::<TokenStream>()?;
100
101            if let Some(constness) = constness {
102                return Err(Error::new_spanned(
103                    constness,
104                    "const fn distributed slice element is not supported",
105                ));
106            }
107
108            if let Some(asyncness) = asyncness {
109                return Err(Error::new_spanned(
110                    asyncness,
111                    "async fn distributed slice element is not supported",
112                ));
113            }
114
115            let lifetimes = if generics.params.is_empty() {
116                None
117            } else {
118                let mut bound = BoundLifetimes {
119                    for_token: Token![for](generics.lt_token.unwrap().span),
120                    lt_token: generics.lt_token.unwrap(),
121                    lifetimes: Punctuated::new(),
122                    gt_token: generics.gt_token.unwrap(),
123                };
124                for param in generics.params.into_pairs() {
125                    let (param, punct) = param.into_tuple();
126                    if let GenericParam::Lifetime(_) = param {
127                        bound.lifetimes.push_value(param);
128                        if let Some(punct) = punct {
129                            bound.lifetimes.push_punct(punct);
130                        }
131                    } else {
132                        return Err(Error::new_spanned(
133                            param,
134                            "cannot have generic parameters on distributed slice element",
135                        ));
136                    }
137                }
138                Some(bound)
139            };
140
141            if let Some(where_clause) = where_clause {
142                return Err(Error::new_spanned(
143                    where_clause,
144                    "where-clause is not allowed on distributed slice elements",
145                ));
146            }
147
148            let start_span = item.span();
149            let end_span = quote!(#output)
150                .into_iter()
151                .last()
152                .as_ref()
153                .map_or(paren_token.span.close(), TokenTree::span);
154            let mut original_attrs = attrs;
155            let linkme_path = attr::linkme_path(&mut original_attrs)?;
156
157            let attrs = vec![
158                parse_quote! {
159                    #[allow(non_upper_case_globals)]
160                },
161                parse_quote! {
162                    #[linkme(crate = #linkme_path)]
163                },
164            ];
165            let vis = Visibility::Inherited;
166            let expr = parse_quote!(#ident);
167            let ty = Type::BareFn(TypeBareFn {
168                lifetimes,
169                unsafety,
170                abi,
171                fn_token,
172                paren_token,
173                inputs,
174                variadic: None,
175                output,
176            });
177            let ident = format_ident!("_LINKME_ELEMENT_{}", ident);
178            let item = item.token_stream();
179            let orig_item = Some(quote!(
180                #(#original_attrs)*
181                #item
182            ));
183
184            Ok(Element {
185                attrs,
186                vis,
187                ident,
188                ty,
189                expr,
190                orig_item,
191                start_span,
192                end_span,
193            })
194        }
195    }
196}
197
198pub fn expand(path: Path, pos: impl Into<Option<usize>>, input: Element) -> TokenStream {
199    let pos = pos.into();
200    do_expand(path, pos, input)
201}
202
203fn do_expand(path: Path, pos: Option<usize>, input: Element) -> TokenStream {
204    let mut attrs = input.attrs;
205    let vis = input.vis;
206    let ident = input.ident;
207    let mut ty = input.ty;
208    let expr = input.expr;
209    let orig_item = input.orig_item;
210
211    ty::populate_static_lifetimes(&mut ty);
212
213    let linkme_path = match attr::linkme_path(&mut attrs) {
214        Ok(path) => path,
215        Err(err) => return err.to_compile_error(),
216    };
217
218    let sort_key = pos.into_iter().map(|pos| format!("{:04}", pos));
219
220    let factory = quote_spanned!(input.start_span=> __new);
221    let get = quote_spanned!(input.end_span=> #factory());
222
223    quote! {
224        #path ! {
225            #(
226                #![linkme_macro = #path]
227                #![linkme_sort_key = #sort_key]
228            )*
229            #(#attrs)*
230            #vis static #ident : #ty = {
231                #[allow(clippy::no_effect_underscore_binding)]
232                unsafe fn __typecheck(_: #linkme_path::__private::Void) {
233                    #[allow(clippy::ref_option_ref)]
234                    let #factory = || -> fn() -> &'static #ty { || &#ident };
235                    unsafe {
236                        #linkme_path::DistributedSlice::private_typecheck(#path, #get);
237                    }
238                }
239
240                #expr
241            };
242        }
243
244        #orig_item
245    }
246}