diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b230d83..589134b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- +- Return types on functions annotated with `#[autometrics]` containing generic + `impl` types in their type arguments (`fn() -> Result`) + no longer fail to compile. ### Security diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index f22ff991..cfb39a9f 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -3,7 +3,10 @@ use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use proc_macro2::TokenStream; use quote::quote; use std::env; -use syn::{parse_macro_input, ImplItem, ItemFn, ItemImpl, Result, ReturnType, Type}; +use syn::{ + parse_macro_input, GenericArgument, ImplItem, ItemFn, ItemImpl, PathArguments, Result, + ReturnType, Type, +}; mod parse; mod result_labels; @@ -87,8 +90,66 @@ fn instrument_function(args: &AutometricsArgs, item: ItemFn) -> Result quote! { : () }, - ReturnType::Type(_, ref t) if matches!(t.as_ref(), &Type::ImplTrait(_)) => quote! {}, - ReturnType::Type(_, ref t) => quote! { : #t }, + ReturnType::Type(_, ref t) => match t.as_ref() { + Type::ImplTrait(_) => quote! {}, + Type::Path(path) => { + let mut ts = vec![]; + let mut first = true; + + for segment in &path.path.segments { + let ident = &segment.ident; + let args = &segment.arguments; + + // special handling in case the type is angle bracket with a `impl` trait + // in such a case, we would run into the following error + // + // ``` + // error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in variable bindings + // --> src/main.rs:11:28 + // | + // 11 | async fn hello() -> Result { + // | ^^^^^^^^^^^^^ + // ``` + // + // this whole block just re-creates the angle bracketed `` + // manually but the trait `impl` replaced with an infer `_`, which fixes this issue + let suffix = match args { + PathArguments::AngleBracketed(brackets) => { + let mut ts = vec![]; + + for args in &brackets.args { + ts.push(match args { + GenericArgument::Type(ty) + if matches!(ty, Type::ImplTrait(_)) => + { + quote! { _ } + } + generic_arg => quote! { #generic_arg }, + }); + } + + quote! { ::<#(#ts),*> } + } + _ => quote! {}, + }; + + // primitive way to check whenever this is the first iteration or not + // as on the first iteration, we don't want to prepend `::`, + // as types may be local and/or imported and then couldn't be found + if !first { + ts.push(quote! { :: }); + } else { + first = false; + } + + ts.push(quote! { #ident }); + ts.push(quote! { #suffix }); + } + + quote! { : #(#ts)* } + } + _ => quote! { : #t }, + }, }; // Wrap the body of the original function, using a slightly different approach based on whether the function is async diff --git a/autometrics/tests/compile_test.rs b/autometrics/tests/compile_test.rs new file mode 100644 index 00000000..7edb0cbe --- /dev/null +++ b/autometrics/tests/compile_test.rs @@ -0,0 +1,45 @@ +use std::io; +use autometrics::autometrics; + +// general purpose `Result`, part of the std prelude. +// notice both `Ok` and `Err` generic type arguments are explicitly provided +#[autometrics] +fn issue_121_a() -> Result { + Ok("a") +} + +// specialized `Result` which is part of std but not part of the std prelude. +// notice there is only an explicit `Ok` type in the generic args, the `Err` generic argument +// is type-defined +#[autometrics] +fn issue_121_b() -> io::Result { + Ok("b") +} + +// specialized `Result` which is part of a foreign crate +// notice there is only an explicit `Ok` type in the generic args, the `Err` generic argument +// is type-defined in the foreign crate +#[autometrics] +fn issue_121_c() -> ::http::Result { + // CODE STYLE: please keep return formatted this way (with the leading `::`) + Ok("c") +} + +// Result where both `Ok` and `Error` are `impl` types +#[autometrics] +fn issue_121_d() -> Result { + if true { + Ok("d") + } else { + Err(io::Error::new(io::ErrorKind::Other, "issue 121d")) + } +} + +#[test] +fn invoke_issue_121() { + // we need to handle all three code generation cases + issue_121_a().unwrap(); + issue_121_b().unwrap(); + issue_121_c().unwrap(); + issue_121_d().unwrap(); +}