diff options
Diffstat (limited to 'src/element.rs')
-rw-r--r-- | src/element.rs | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/src/element.rs b/src/element.rs new file mode 100644 index 0000000..7c85e30 --- /dev/null +++ b/src/element.rs @@ -0,0 +1,241 @@ +use crate::attr; +use proc_macro2::{Span, TokenStream, TokenTree}; +use quote::{format_ident, quote, quote_spanned}; +use std::iter::FromIterator; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{ + braced, parenthesized, parse_quote, Abi, Attribute, BareFnArg, BoundLifetimes, GenericParam, + Generics, Ident, Path, ReturnType, Token, Type, TypeBareFn, Visibility, WhereClause, +}; + +pub struct Element { + attrs: Vec<Attribute>, + vis: Visibility, + ident: Ident, + ty: Type, + expr: TokenStream, + orig_item: Option<TokenStream>, + start_span: Span, + end_span: Span, +} + +impl Parse for Element { + fn parse(input: ParseStream) -> Result<Self> { + let attrs = input.call(Attribute::parse_outer)?; + let item = input.cursor(); + let vis: Visibility = input.parse()?; + let static_token: Option<Token![static]> = input.parse()?; + if static_token.is_some() { + let mut_token: Option<Token![mut]> = input.parse()?; + if let Some(mut_token) = mut_token { + return Err(Error::new_spanned( + mut_token, + "static mut is not supported by distributed_slice", + )); + } + let ident: Ident = input.parse()?; + input.parse::<Token![:]>()?; + let start_span = input.span(); + let ty: Type = input.parse()?; + let end_span = quote!(#ty).into_iter().last().unwrap().span(); + input.parse::<Token![=]>()?; + let mut expr_semi = Vec::from_iter(input.parse::<TokenStream>()?); + if let Some(tail) = expr_semi.pop() { + syn::parse2::<Token![;]>(TokenStream::from(tail))?; + } + let expr = TokenStream::from_iter(expr_semi); + Ok(Element { + attrs, + vis, + ident, + ty, + expr, + orig_item: None, + start_span, + end_span, + }) + } else { + let constness: Option<Token![const]> = input.parse()?; + let asyncness: Option<Token![async]> = input.parse()?; + let unsafety: Option<Token![unsafe]> = input.parse()?; + let abi: Option<Abi> = input.parse()?; + let fn_token: Token![fn] = input.parse().map_err(|_| { + Error::new_spanned( + item.token_stream(), + "distributed element must be either static or function item", + ) + })?; + let ident: Ident = input.parse()?; + let generics: Generics = input.parse()?; + + let content; + let paren_token = parenthesized!(content in input); + let mut inputs = Punctuated::new(); + while !content.is_empty() { + content.parse::<Option<Token![mut]>>()?; + let ident = if let Some(wild) = content.parse::<Option<Token![_]>>()? { + Ident::from(wild) + } else { + content.parse()? + }; + let colon_token: Token![:] = content.parse()?; + let ty: Type = content.parse()?; + inputs.push_value(BareFnArg { + attrs: Vec::new(), + name: Some((ident, colon_token)), + ty, + }); + if !content.is_empty() { + let comma: Token![,] = content.parse()?; + inputs.push_punct(comma); + } + } + + let output: ReturnType = input.parse()?; + let where_clause: Option<WhereClause> = input.parse()?; + + let content; + braced!(content in input); + content.parse::<TokenStream>()?; + + if let Some(constness) = constness { + return Err(Error::new_spanned( + constness, + "const fn distributed slice element is not supported", + )); + } + + if let Some(asyncness) = asyncness { + return Err(Error::new_spanned( + asyncness, + "async fn distributed slice element is not supported", + )); + } + + let lifetimes = if generics.params.is_empty() { + None + } else { + let mut bound = BoundLifetimes { + for_token: Token![for](generics.lt_token.unwrap().span), + lt_token: generics.lt_token.unwrap(), + lifetimes: Punctuated::new(), + gt_token: generics.gt_token.unwrap(), + }; + for param in generics.params.into_pairs() { + let (param, punct) = param.into_tuple(); + if let GenericParam::Lifetime(_) = param { + bound.lifetimes.push_value(param); + if let Some(punct) = punct { + bound.lifetimes.push_punct(punct); + } + } else { + return Err(Error::new_spanned( + param, + "cannot have generic parameters on distributed slice element", + )); + } + } + Some(bound) + }; + + if let Some(where_clause) = where_clause { + return Err(Error::new_spanned( + where_clause, + "where-clause is not allowed on distributed slice elements", + )); + } + + let start_span = item.span(); + let end_span = quote!(#output) + .into_iter() + .last() + .as_ref() + .map_or(paren_token.span.close(), TokenTree::span); + let mut original_attrs = attrs; + let linkme_path = attr::linkme_path(&mut original_attrs)?; + + let attrs = vec![ + parse_quote! { + #[allow(non_upper_case_globals)] + }, + parse_quote! { + #[linkme(crate = #linkme_path)] + }, + ]; + let vis = Visibility::Inherited; + let expr = parse_quote!(#ident); + let ty = Type::BareFn(TypeBareFn { + lifetimes, + unsafety, + abi, + fn_token, + paren_token, + inputs, + variadic: None, + output, + }); + let ident = format_ident!("_LINKME_ELEMENT_{}", ident); + let item = item.token_stream(); + let orig_item = Some(quote!( + #(#original_attrs)* + #item + )); + + Ok(Element { + attrs, + vis, + ident, + ty, + expr, + orig_item, + start_span, + end_span, + }) + } + } +} + +pub fn expand(path: Path, pos: impl Into<Option<usize>>, input: Element) -> TokenStream { + let pos = pos.into(); + do_expand(path, pos, input) +} + +fn do_expand(path: Path, pos: Option<usize>, input: Element) -> TokenStream { + let mut attrs = input.attrs; + let vis = input.vis; + let ident = input.ident; + let ty = input.ty; + let expr = input.expr; + let orig_item = input.orig_item; + + let linkme_path = match attr::linkme_path(&mut attrs) { + Ok(path) => path, + Err(err) => return err.to_compile_error(), + }; + + let sort_key = pos.into_iter().map(|pos| format!("{:04}", pos)); + + let new = quote_spanned!(input.start_span=> __new); + let uninit = quote_spanned!(input.end_span=> #new()); + + quote! { + #path ! { + #( + #![linkme_macro = #path] + #![linkme_sort_key = #sort_key] + )* + #(#attrs)* + #vis static #ident : #ty = { + unsafe fn __typecheck(_: #linkme_path::__private::Void) { + let #new = #linkme_path::__private::value::<#ty>; + #linkme_path::DistributedSlice::private_typecheck(#path, #uninit) + } + + #expr + }; + } + + #orig_item + } +} |