use crate::serde_helpers;
use crate::spec::ProbeArgSpecification;
use crate::{TracersError, TracersResult};
use proc_macro2::Span;
use serde::{Deserialize, Serialize};
use std::fmt;
use syn::spanned::Spanned;
use syn::Visibility;
use syn::{FnArg, Ident, ItemTrait, ReturnType, TraitItemMethod};
#[derive(Serialize, Deserialize, Clone)]
pub(crate) struct ProbeSpecification {
pub name: String,
#[serde(with = "serde_helpers::syn")]
pub method_name: Ident,
#[serde(with = "serde_helpers::syn")]
pub original_method: TraitItemMethod,
#[serde(with = "serde_helpers::syn")]
pub vis: Visibility,
#[serde(with = "serde_helpers::span")]
pub span: Span,
pub args: Vec<ProbeArgSpecification>,
}
impl fmt::Debug for ProbeSpecification {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ProbeSpecification(name={}, args=(", self.name)?;
for arg in self.args.iter() {
writeln!(f, "{:?}", arg)?;
}
write!(f, ")")
}
}
impl ProbeSpecification {
pub(crate) fn from_method(
item: &ItemTrait,
method: &TraitItemMethod,
) -> TracersResult<ProbeSpecification> {
if method.default != None {
return Err(TracersError::invalid_provider(
"Probe methods must NOT have a default implementation",
method,
));
} else if method.sig.constness != None
|| method.sig.unsafety != None
|| method.sig.asyncness != None
|| method.sig.abi != None
{
return Err(TracersError::invalid_provider(
"Probe methods cannot be `const`, `unsafe`, `async`, or `extern \"C\"`",
method,
));
} else if method.sig.generics.type_params().next() != None {
return Err(TracersError::invalid_provider(
"Probe methods must not take any type parameters; generics are not supported in probes",
method,
));
} else if method.sig.variadic != None {
return Err(TracersError::invalid_provider(
"Probe methods cannot have variadic arguments",
method,
));
} else if method.sig.output != ReturnType::Default {
return Err(TracersError::invalid_provider(
"Probe methods must not have an explicit return type (they return `()` implicitly)",
method,
));
};
let first_arg = method.sig.inputs.iter().next();
if let Some(FnArg::Receiver(_)) = first_arg {
return Err(TracersError::invalid_provider(
"Probe methods must not have any `&self` or `self` args",
method,
));
}
let mut args: Vec<ProbeArgSpecification> = Vec::new();
for (idx, arg) in method.sig.inputs.iter().enumerate() {
args.push(ProbeArgSpecification::from_fnarg(method, idx, arg)?);
}
let spec = ProbeSpecification {
name: method.sig.ident.to_string(),
method_name: method.sig.ident.clone(),
original_method: method.clone(),
vis: item.vis.clone(),
span: method.span(),
args,
};
Ok(spec)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::syn_helpers;
use crate::testdata::*;
use quote::quote;
use syn::{parse_quote, TraitItemMethod};
impl PartialEq<ProbeSpecification> for ProbeSpecification {
fn eq(&self, other: &ProbeSpecification) -> bool {
self.name == other.name
&& self.method_name == other.method_name
&& self.original_method == other.original_method
&& self.vis == other.vis
&& self.args == other.args
}
}
impl PartialEq<TestProbe> for ProbeSpecification {
fn eq(&self, other: &TestProbe) -> bool {
self.name == other.name
&& self.args.len() == other.args.len()
&& self
.args
.iter()
.zip(other.args.iter())
.all(|(self_arg, spec_arg)| {
let (self_arg_name, self_arg_type) = (self_arg.ident(), self_arg.syn_typ());
let (spec_arg_name, spec_arg_type, _) = spec_arg;
self_arg_name.ident.to_string() == *spec_arg_name
&& self_arg_type == spec_arg_type
})
}
}
mod data {
use super::*;
pub(crate) fn trait_item() -> ItemTrait {
parse_quote! { trait SomeTrait {} }
}
pub(crate) fn valid_test_cases() -> Vec<TraitItemMethod> {
vec![
parse_quote! { fn probe0(arg0: i32); },
parse_quote! { fn probe1(arg0: &str); },
parse_quote! { fn probe2(arg0: &str, arg1: usize); },
parse_quote! { fn probe3(arg0: &str, arg1: &usize, arg2: &Option<i32>); },
]
}
pub(crate) fn invalid_test_cases() -> Vec<TraitItemMethod> {
vec![
parse_quote! { const fn probe0(arg0: i32); },
parse_quote! { unsafe fn probe0(arg0: i32); },
parse_quote! { extern "C" fn probe0(arg0: i32); },
parse_quote! { fn probe0<T: Debug>(arg0: T); },
parse_quote! { fn probe0(arg0: usize) -> (); },
parse_quote! { fn probe0(arg0: usize) -> bool; },
parse_quote! { fn probe0(arg0: i32) { prinln!("{}", arg0); } },
parse_quote! { fn probe0(&self, arg0: i32); },
parse_quote! { fn probe0(&mut self, arg0: i32); },
parse_quote! { fn probe0(self, arg0: i32); },
]
}
}
#[test]
fn works_with_valid_cases() {
for input in data::valid_test_cases().iter() {
let input_string = quote! { #input }.to_string();
ProbeSpecification::from_method(&data::trait_item(), input).expect(&format!(
"This should be treated as a valid method: {}",
input_string
));
}
}
#[test]
fn works_with_invalid_cases() {
for input in data::invalid_test_cases().iter() {
let input_string = quote! { #input }.to_string();
ProbeSpecification::from_method(&data::trait_item(), input)
.err()
.expect(&format!(
"This should be treated as an invalid method: {}",
input_string
));
}
}
#[test]
fn decorates_args_with_lifetime_params() {
let test_cases: Vec<(syn::TraitItemMethod, proc_macro2::TokenStream)> = vec![
(
parse_quote! { fn probe0(arg0: i32); },
quote! { fn probe0(arg0: i32); },
),
(
parse_quote! {fn probe1(arg0: &str);},
quote! {fn probe1(arg0: &'probe1_arg0_1 str);},
),
(
parse_quote! { fn probe2(arg0: &str, arg1: usize); },
quote! { fn probe2(arg0: &'probe2_arg0_1 str, arg1: usize); },
),
(
parse_quote! { fn probe2(arg0: &str, arg1: usize); },
quote! { fn probe2(arg0: &'probe2_arg0_1 str, arg1: usize); },
),
(
parse_quote! { fn probe3(arg0: &str, arg1: &usize, arg2: &Option<&str>); },
quote! { fn probe3(arg0: &'probe3_arg0_1 str, arg1: &'probe3_arg1_1 usize, arg2: &'probe3_arg2_1 Option<&'probe3_arg2_2 str>); },
),
];
for (method, expected) in test_cases.iter() {
let probe =
ProbeSpecification::from_method(&data::trait_item(), method).expect(&format!(
"This method should be valid: {}",
syn_helpers::convert_to_string(method)
));
let args = probe.args.iter().map(|arg| {
let (nam, typ) = (&arg.ident(), &arg.syn_typ_with_lifetimes());
quote! { #nam: #typ }
});
let name = probe.method_name;
let result = quote! {
fn #name(#(#args),*);
};
assert_eq!(expected.to_string(), result.to_string());
}
}
}