use std::collections::BTreeMap;
use proc_macro2::{Span, TokenStream};
use syn::{Attribute, FieldValue, Ident, parse::Parse, spanned::Spanned};
use crate::{
capture, hook,
util::{AttributeCfg, ExprIsLocalVariable, FieldValueKey, maybe_cfg},
};
#[derive(Debug)]
pub struct Props {
match_value_tokens: Vec<TokenStream>,
match_binding_tokens: Vec<TokenStream>,
key_values: BTreeMap<String, KeyValue>,
key_value_index: usize,
}
impl Parse for Props {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let fv = input.parse_terminated(FieldValue::parse, Token![,])?;
let mut props = Props::new();
for fv in fv {
props.push(&fv, capture::default_fn_name(&fv), false, true)?;
}
Ok(props)
}
}
#[derive(Debug)]
pub struct KeyValue {
match_bound_tokens: TokenStream,
direct_bound_tokens: TokenStream,
raw_bound_tokens: TokenStream,
label: Ident,
span: Span,
pub interpolated: bool,
pub captured: bool,
pub cfg_attr: Option<Attribute>,
pub attrs: Vec<Attribute>,
}
impl KeyValue {
pub fn span(&self) -> Span {
self.span.clone()
}
pub fn hole_tokens(&self) -> TokenStream {
let label = &self.label;
let attrs = &self.attrs;
quote!(#(#attrs)* #label)
}
}
impl Props {
pub fn new() -> Self {
Props {
match_value_tokens: Vec::new(),
match_binding_tokens: Vec::new(),
key_values: BTreeMap::new(),
key_value_index: 0,
}
}
pub fn match_input_tokens(&self) -> impl Iterator<Item = &TokenStream> {
self.match_value_tokens.iter()
}
pub fn match_binding_tokens(&self) -> impl Iterator<Item = &TokenStream> {
self.match_binding_tokens.iter()
}
pub fn match_bound_tokens(&self) -> TokenStream {
Self::sorted_props_tokens(self.key_values.values().map(|kv| &kv.match_bound_tokens))
}
pub fn props_tokens(&self) -> TokenStream {
Self::sorted_props_tokens(self.key_values.values().map(|kv| &kv.direct_bound_tokens))
}
pub fn raw_props_tokens(&self) -> Result<TokenStream, syn::Error> {
for (k, v) in &self.key_values {
if v.attrs.len() != 0 {
return Err(syn::Error::new(
v.span,
format!("attributes on {k} are not supported when capturing directly"),
));
}
}
Ok(match self.key_values.len() {
0 => quote!(emit::Empty),
1 => self
.key_values
.first_key_value()
.unwrap()
.1
.raw_bound_tokens
.clone(),
_ => {
let key_values = self.key_values.values().map(|kv| &kv.raw_bound_tokens);
quote!(emit::__private::__PrivateTupleMacroProps::new((#(#key_values),*)))
}
})
}
fn sorted_props_tokens<'a>(
key_values: impl Iterator<Item = &'a TokenStream> + 'a,
) -> TokenStream {
quote!(emit::__private::__PrivateMacroProps::from_array([#(#key_values),*]))
}
fn next_match_binding_ident(&mut self, span: Span) -> Ident {
let i = Ident::new(&format!("__tmp{}", self.key_value_index), span);
self.key_value_index += 1;
i
}
pub fn get(&self, label: &str) -> Option<&KeyValue> {
self.key_values.get(label)
}
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a KeyValue)> + 'a {
self.key_values.iter().map(|(k, v)| (&**k, v))
}
pub fn push(
&mut self,
fv: &FieldValue,
fn_name: TokenStream,
interpolated: bool,
captured: bool,
) -> Result<(), syn::Error> {
let mut attrs = vec![];
let mut cfg_attr = None;
for attr in &fv.attrs {
if attr.is_cfg() {
if cfg_attr.is_some() {
return Err(syn::Error::new(
attr.span(),
"only a single #[cfg] is supported on key-value pairs",
));
}
cfg_attr = Some(attr.clone());
} else {
attrs.push(attr.clone());
}
}
let match_bound_ident = self.next_match_binding_ident(fv.span());
let key_value_tokens = maybe_cfg(
cfg_attr.as_ref(),
fv.span(),
capture::eval_key_value_with_hook(&attrs, &fv, &fn_name, interpolated, captured)?,
);
let direct_bound_tokens = quote_spanned!(fv.span()=> #key_value_tokens);
let raw_bound_tokens = capture::raw_key_value(fv)?;
let (match_value_tokens, match_bound_tokens) = if fv.expr.is_local_variable() {
let match_value_tokens = quote_spanned!(fv.span()=>#key_value_tokens);
let match_bound_tokens =
quote_spanned!(fv.span()=>#cfg_attr (#match_bound_ident.0, #match_bound_ident.1));
(match_value_tokens, match_bound_tokens)
} else {
let key_tokens = capture::eval_key_with_hook(&attrs, &fv, interpolated, captured)?;
let value_expr = &fv.expr;
let match_value_tokens = maybe_cfg(
cfg_attr.as_ref(),
fv.span(),
quote_spanned!(fv.span()=> (#key_tokens, #value_expr)),
);
let bound_value_tokens = capture::value_with_hook(
&syn::parse_quote_spanned!(fv.span()=>#match_bound_ident.1),
&fn_name,
interpolated,
captured,
);
let bound_value_tokens = hook::eval_hooks(
&attrs,
syn::parse_quote_spanned!(fv.span()=>#bound_value_tokens),
)?;
let match_bound_tokens = quote!(#cfg_attr (#match_bound_ident.0, #bound_value_tokens));
(match_value_tokens, match_bound_tokens)
};
self.match_value_tokens.push(match_value_tokens);
if let Some(cfg_attr) = &cfg_attr {
let cfg_attr = cfg_attr
.invert_cfg()
.ok_or_else(|| syn::Error::new(cfg_attr.span(), "attribute is not a #[cfg]"))?;
self.match_value_tokens
.push(quote_spanned!(fv.span()=> #cfg_attr ()));
}
self.match_binding_tokens
.push(quote_spanned!(fv.span()=> #match_bound_ident));
if fv.colon_token.is_some() && !captured {
return Err(syn::Error::new(
fv.span(),
"uncaptured key values must be plain identifiers",
));
}
let previous = self.key_values.insert(
fv.key_name()?,
KeyValue {
match_bound_tokens,
direct_bound_tokens,
raw_bound_tokens,
span: fv.span(),
label: fv.key_ident()?,
cfg_attr,
attrs,
captured,
interpolated,
},
);
if previous.is_some() {
return Err(syn::Error::new(fv.span(), "keys cannot be duplicated"));
}
Ok(())
}
}
pub fn check_evt_props(props: &Props) -> Result<(), syn::Error> {
for (k, v) in &props.key_values {
match &**k {
emit_core::well_known::KEY_MDL => {
return Err(syn::Error::new(
v.span(),
"specify the module using the `mdl` control parameter before the template",
));
}
emit_core::well_known::KEY_TPL => {
return Err(syn::Error::new(
v.span(),
"the template is specified as a string literal before properties",
));
}
emit_core::well_known::KEY_MSG => {
return Err(syn::Error::new(
v.span(),
"the message is specified as a string literal template before properties",
));
}
emit_core::well_known::KEY_TS => {
return Err(syn::Error::new(
v.span(),
"specify the timestamp using the `extent` control parameter before the template",
));
}
emit_core::well_known::KEY_TS_START => {
return Err(syn::Error::new(
v.span(),
"specify the start timestamp using the `extent` control parameter before the template",
));
}
_ => (),
}
}
Ok(())
}
pub fn check_span_props(props: &Props) -> Result<(), syn::Error> {
for (k, v) in &props.key_values {
match &**k {
emit_core::well_known::KEY_EVT_KIND => {
return Err(syn::Error::new(
v.span(),
"the `evt_kind` property is always given the value `\"span\"`",
));
}
emit_core::well_known::KEY_SPAN_NAME => {
return Err(syn::Error::new(
v.span(),
"specify the span name using the `name` control parameter before the template",
));
}
_ => (),
}
}
Ok(())
}
pub fn push_evt_props(props: &mut Props, level: Option<TokenStream>) -> Result<(), syn::Error> {
if let Some(level_value) = level {
let level_ident = Ident::new(emit_core::well_known::KEY_LVL, Span::call_site());
let fv = syn::parse2::<FieldValue>(quote!(#level_ident: #level_value))?;
props.push(&fv, capture::default_fn_name(&fv), false, true)?;
}
Ok(())
}