diff --git a/tracing-attributes/src/lib.rs b/tracing-attributes/src/lib.rs index 01e5de4373..ff924f13e1 100644 --- a/tracing-attributes/src/lib.rs +++ b/tracing-attributes/src/lib.rs @@ -40,12 +40,14 @@ extern crate proc_macro; use std::collections::HashSet; +use std::iter; use proc_macro::TokenStream; use quote::{quote, quote_spanned, ToTokens}; use syn::{ - spanned::Spanned, AttributeArgs, FnArg, Ident, ItemFn, Lit, LitInt, Meta, MetaList, - MetaNameValue, NestedMeta, Pat, PatIdent, PatType, Signature, + spanned::Spanned, AttributeArgs, FieldPat, FnArg, Ident, ItemFn, Lit, LitInt, Meta, MetaList, + MetaNameValue, NestedMeta, Pat, PatIdent, PatReference, PatStruct, PatTuple, PatTupleStruct, + PatType, Signature, }; /// Instruments a function to create and enter a `tracing` [span] every time @@ -154,12 +156,9 @@ pub fn instrument(args: TokenStream, item: TokenStream) -> TokenStream { let param_names: Vec = params .clone() .into_iter() - .filter_map(|param| match param { - FnArg::Typed(PatType { pat, .. }) => match *pat { - Pat::Ident(PatIdent { ident, .. }) => Some(ident), - _ => None, - }, - _ => None, + .flat_map(|param| match param { + FnArg::Typed(PatType { pat, .. }) => param_names(*pat), + FnArg::Receiver(_) => Box::new(iter::once(Ident::new("self", param.span()))), }) .filter(|ident| !skips.contains(ident)) .collect(); @@ -212,6 +211,29 @@ pub fn instrument(args: TokenStream, item: TokenStream) -> TokenStream { .into() } +fn param_names(pat: Pat) -> Box> { + match pat { + Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once(ident)), + Pat::Reference(PatReference { pat, .. }) => param_names(*pat), + Pat::Struct(PatStruct { fields, .. }) => Box::new( + fields + .into_iter() + .flat_map(|FieldPat { pat, .. }| param_names(*pat)), + ), + Pat::Tuple(PatTuple { elems, .. }) => Box::new(elems.into_iter().flat_map(param_names)), + Pat::TupleStruct(PatTupleStruct { + pat: PatTuple { elems, .. }, + .. + }) => Box::new(elems.into_iter().flat_map(param_names)), + + // The above *should* cover all cases of irrefutable patterns, + // but we purposefully don't do any funny business here + // (such as panicking) because that would obscure rustc's + // much more informative error message. + _ => Box::new(iter::empty()), + } +} + fn skips(args: &AttributeArgs) -> Result, impl ToTokens> { let mut skips = args.iter().filter_map(|arg| match arg { NestedMeta::Meta(Meta::List(MetaList { diff --git a/tracing-attributes/tests/destructuring.rs b/tracing-attributes/tests/destructuring.rs new file mode 100644 index 0000000000..9d01d415ed --- /dev/null +++ b/tracing-attributes/tests/destructuring.rs @@ -0,0 +1,215 @@ +mod support; +use support::*; + +use tracing::subscriber::with_default; +use tracing_attributes::instrument; + +#[test] +fn destructure_tuples() { + #[instrument] + fn my_fn((arg1, arg2): (usize, usize)) {} + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone().with_field( + field::mock("arg1") + .with_value(&format_args!("1")) + .and(field::mock("arg2").with_value(&format_args!("2"))) + .only(), + ), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + my_fn((1, 2)); + }); + + handle.assert_finished(); +} + +#[test] +fn destructure_nested_tuples() { + #[instrument] + fn my_fn(((arg1, arg2), (arg3, arg4)): ((usize, usize), (usize, usize))) {} + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone().with_field( + field::mock("arg1") + .with_value(&format_args!("1")) + .and(field::mock("arg2").with_value(&format_args!("2"))) + .and(field::mock("arg3").with_value(&format_args!("3"))) + .and(field::mock("arg4").with_value(&format_args!("4"))) + .only(), + ), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + my_fn(((1, 2), (3, 4))); + }); + + handle.assert_finished(); +} + +#[test] +fn destructure_refs() { + #[instrument] + fn my_fn(&arg1: &usize) {} + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone() + .with_field(field::mock("arg1").with_value(&format_args!("1")).only()), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + my_fn(&1); + }); + + handle.assert_finished(); +} + +#[test] +fn destructure_tuple_structs() { + struct Foo(usize, usize); + + #[instrument] + fn my_fn(Foo(arg1, arg2): Foo) {} + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone().with_field( + field::mock("arg1") + .with_value(&format_args!("1")) + .and(field::mock("arg2").with_value(&format_args!("2"))) + .only(), + ), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + my_fn(Foo(1, 2)); + }); + + handle.assert_finished(); +} + +#[test] +fn destructure_structs() { + struct Foo { + bar: usize, + baz: usize, + } + + #[instrument] + fn my_fn( + Foo { + bar: arg1, + baz: arg2, + }: Foo, + ) { + let _ = (arg1, arg2); + } + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone().with_field( + field::mock("arg1") + .with_value(&format_args!("1")) + .and(field::mock("arg2").with_value(&format_args!("2"))) + .only(), + ), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + my_fn(Foo { bar: 1, baz: 2 }); + }); + + handle.assert_finished(); +} + +#[test] +fn destructure_everything() { + struct Foo { + bar: Bar, + baz: (usize, usize), + qux: NoDebug, + } + struct Bar((usize, usize)); + struct NoDebug; + + #[instrument] + fn my_fn( + &Foo { + bar: Bar((arg1, arg2)), + baz: (arg3, arg4), + .. + }: &Foo, + ) { + let _ = (arg1, arg2, arg3, arg4); + } + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone().with_field( + field::mock("arg1") + .with_value(&format_args!("1")) + .and(field::mock("arg2").with_value(&format_args!("2"))) + .and(field::mock("arg3").with_value(&format_args!("3"))) + .and(field::mock("arg4").with_value(&format_args!("4"))) + .only(), + ), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + let foo = Foo { + bar: Bar((1, 2)), + baz: (3, 4), + qux: NoDebug, + }; + let _ = foo.qux; // to eliminate unused field warning + my_fn(&foo); + }); + + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/instrument.rs b/tracing-attributes/tests/instrument.rs index 2045a47f61..d4ebcdba25 100644 --- a/tracing-attributes/tests/instrument.rs +++ b/tracing-attributes/tests/instrument.rs @@ -166,3 +166,37 @@ fn generics() { handle.assert_finished(); } + +#[test] +fn methods() { + #[derive(Debug)] + struct Foo; + + impl Foo { + #[instrument] + fn my_fn(&self, arg1: usize) {} + } + + let span = span::mock().named("my_fn"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + span.clone().with_field( + field::mock("self") + .with_value(&format_args!("Foo")) + .and(field::mock("arg1").with_value(&format_args!("42"))), + ), + ) + .enter(span.clone()) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, || { + let foo = Foo; + foo.my_fn(42); + }); + + handle.assert_finished(); +}