metrics/derive/src/metric.rs (153 lines of code) (raw):

// Copyright 2021 Twitter, Inc. // Licensed under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 use crate::args::ArgName; use proc_macro2::{Span, TokenStream}; use proc_macro_crate::FoundCrate; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{parse_quote, Error, Expr, Ident, ItemStatic, Path, Token}; struct SingleArg<T> { ident: ArgName, eq: Token![=], value: T, } impl<T: Parse> Parse for SingleArg<T> { fn parse(input: ParseStream) -> syn::Result<Self> { Ok(Self { ident: input.parse()?, eq: input.parse()?, value: input.parse()?, }) } } impl<T: ToTokens> ToTokens for SingleArg<T> { fn to_tokens(&self, tokens: &mut TokenStream) { self.ident.to_tokens(tokens); self.eq.to_tokens(tokens); self.value.to_tokens(tokens); } } #[derive(Default)] struct MetricArgs { name: Option<SingleArg<Expr>>, namespace: Option<SingleArg<Expr>>, description: Option<SingleArg<Expr>>, krate: Option<SingleArg<Path>>, } impl Parse for MetricArgs { fn parse(input: ParseStream) -> syn::Result<Self> { let mut args = MetricArgs::default(); let mut first = true; fn duplicate_arg_error( span: Span, arg: &impl std::fmt::Display, ) -> syn::Result<MetricArgs> { Err(Error::new( span, format!("Unexpected duplicate argument '{}'", arg), )) } while !input.is_empty() { if !first { let _: Token![,] = input.parse()?; } first = false; let arg: ArgName = input.fork().parse()?; match &*arg.to_string() { "name" => { let name = input.parse()?; match args.name { None => args.name = Some(name), Some(_) => return duplicate_arg_error(name.span(), &arg), } } "namespace" => { let namespace = input.parse()?; match args.namespace { None => args.namespace = Some(namespace), Some(_) => return duplicate_arg_error(namespace.span(), &arg), } } "description" => { let description = input.parse()?; match args.description { None => args.description = Some(description), Some(_) => return duplicate_arg_error(description.span(), &arg), } } "crate" => { let krate = SingleArg { ident: input.parse()?, eq: input.parse()?, value: Path::parse_mod_style(input)?, }; match args.krate { None => args.krate = Some(krate), Some(_) => return duplicate_arg_error(krate.span(), &arg), } } x => { return Err(Error::new( arg.span(), format!("Unrecognized argument '{}'", x), )) } } } Ok(args) } } pub(crate) fn metric( attr_: proc_macro::TokenStream, item_: proc_macro::TokenStream, ) -> syn::Result<TokenStream> { let mut item: ItemStatic = syn::parse(item_)?; let args: MetricArgs = syn::parse(attr_)?; let krate: TokenStream = match args.krate { Some(krate) => krate.value.to_token_stream(), None => proc_macro_crate::crate_name("rustcommon-metrics") .map(|krate| match krate { FoundCrate::Name(name) => { assert_ne!(name, ""); Ident::new(&name, Span::call_site()).to_token_stream() } FoundCrate::Itself => quote! { rustcommon_metrics }, }) .unwrap_or(quote! { rustcommon_metrics }), }; let name: TokenStream = match args.name { Some(name) => name.value.to_token_stream(), None => { let item_name = &item.ident; quote! { concat!(module_path!(), "::", stringify!(#item_name)) } } }; let namespace: TokenStream = match args.namespace { Some(namespace) => namespace.value.to_token_stream(), None => { quote! {""} } }; let description: TokenStream = match args.description { Some(description) => description.value.to_token_stream(), None => { quote! {""} } }; let static_name = &item.ident; let static_expr = &item.expr; let static_type = &item.ty; item.expr = Box::new(parse_quote! {{ // Rustc reserves attributes that start with "rustc". Since rustcommon // starts with "rustc" we can't use it directly within attributes. To // work around this, we first import the exports submodule and then use // that for the attributes. use #krate::export; #[export::linkme::distributed_slice(export::METRICS)] #[linkme(crate = export::linkme)] static __: #krate::MetricEntry = #krate::MetricEntry::_new_const( #krate::MetricWrapper(&#static_name.metric), #static_name.name(), #namespace, #description ); #krate::MetricInstance::new(#static_expr, #name, #description) }}); item.ty = Box::new(parse_quote! { #krate::MetricInstance<#static_type> }); Ok(quote! { #item }) }