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.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}