diff --git a/src/lib.rs b/src/lib.rs index 806c712..3d4754d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,11 @@ enum Args { err_level: Option, input_format: Option, }, + Option { + some_level: Option, + none_level: Option, + input_format: Option, + }, } impl Parse for Args { @@ -51,6 +56,8 @@ impl Parse for Args { simple_level: Option, ok_level: Option, err_level: Option, + some_level: Option, + none_level: Option, input_format: Option, } @@ -76,6 +83,24 @@ impl Parse for Args { input.parse::()?; let level = input.parse::()?; match ident.to_string().as_str() { + "some" => { + if ctx.some_level.is_some() { + return Err(syn::Error::new( + level.span(), + "some_level specified multiple times", + )); + } + ctx.some_level = Some(level.value()); + } + "none" => { + if ctx.none_level.is_some() { + return Err(syn::Error::new( + level.span(), + "none_level specified multiple times", + )); + } + ctx.none_level = Some(level.value()); + } "ok" => { if ctx.ok_level.is_some() { return Err(syn::Error::new( @@ -126,20 +151,37 @@ impl Parse for Args { simple_level, ok_level, err_level, + some_level, + none_level, input_format, } = input.parse::()?; + if ok_level.is_some() || err_level.is_some() { if simple_level.is_some() { abort_call_site!("plain level cannot be specified with `ok` or `err` levels"); } + if some_level.is_some() || none_level.is_some() { + abort_call_site!( + "`some` and `none` levels cannot be specified with `ok` or `err` levels" + ); + } Ok(Args::Result { ok_level, err_level, input_format, }) + } else if some_level.is_some() || none_level.is_some() { + if simple_level.is_some() { + abort_call_site!("plain level cannot be specified with `some` or `none` levels"); + } + Ok(Args::Option { + some_level, + none_level, + input_format, + }) } else { Ok(Args::Simple { - level: simple_level.unwrap_or_else(|| "debug".to_string()), + level: simple_level.unwrap_or_else(|| "info".to_string()), input_format, }) } @@ -236,121 +278,247 @@ fn gen_block( Args::Simple { level, input_format, - } => { - // Generate the instrumented function body. - // If the function is an `async fn`, this will wrap it in an async block. - if async_context { - let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); - let log = gen_log(&level, "__input_string", "__ret_value"); - let block = quote::quote_spanned!(block.span()=> - #[allow(unknown_lints)] - #[allow(clippy::useless_format)] - let __input_string = format!(#input_format); - #[allow(unknown_lints)] - let __ret_value = async { #block }.await; - #log; - __ret_value - ); - - if async_keyword { - block - } else { - quote::quote_spanned!(block.span()=> - async move { - #block - } - ) - } - } else { - let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); - let log = gen_log(&level, "__input_string", "__ret_value"); - quote::quote_spanned!(block.span()=> - #[allow(unknown_lints)] - #[allow(clippy::useless_format)] - let __input_string = format!(#input_format); - #[allow(unknown_lints)] - #[allow(clippy::redundant_closure_call)] - #[allow(clippy::let_unit_value)] - let __ret_value = (move || #block)(); - #log; - __ret_value - ) - } - } + } => gen_plain_label_block( + block, + async_context, + async_keyword, + sig, + &level, + input_format, + ), Args::Result { ok_level, err_level, input_format, - } => { - let ok_arm = if let Some(ok_level) = ok_level { - let log_ok = gen_log(&ok_level, "__input_string", "__ret_value"); - quote::quote_spanned!(block.span()=> - __ret_value@Ok(_) => { - #log_ok; - __ret_value - } - ) - } else { - quote::quote_spanned!(block.span()=> - Ok(__ret_value) => Ok(__ret_value), - ) - }; - let err_arm = if let Some(err_level) = err_level { - let log_err = gen_log(&err_level, "__input_string", "__ret_value"); - quote::quote_spanned!(block.span()=> - __ret_value@Err(_) => { - #log_err; - __ret_value - } - ) - } else { - quote::quote_spanned!(block.span()=> - Err(__ret_value) => Err(__ret_value), - ) - }; - - // Generate the instrumented function body. - // If the function is an `async fn`, this will wrap it in an async block. - if async_context { - let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); - let block = quote::quote_spanned!(block.span()=> - #[allow(unknown_lints)] - #[allow(clippy::useless_format)] - let __input_string = format!(#input_format); - #[allow(unknown_lints)] - let __ret_value = async { #block }.await; - match __ret_value { - #ok_arm - #err_arm - } - ); + } => gen_result_label_block( + block, + async_context, + async_keyword, + sig, + ok_level, + err_level, + input_format, + ), + Args::Option { + some_level, + none_level, + input_format, + } => gen_option_label_block( + block, + async_context, + async_keyword, + sig, + some_level, + none_level, + input_format, + ), + } +} - if async_keyword { - block - } else { - quote::quote_spanned!(block.span()=> - async move { - #block - } - ) +fn gen_plain_label_block( + block: &Block, + async_context: bool, + async_keyword: bool, + sig: &Signature, + level: &str, + input_format: Option, +) -> proc_macro2::TokenStream { + // Generate the instrumented function body. + // If the function is an `async fn`, this will wrap it in an async block. + if async_context { + let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); + let log = gen_log(level, "__input_string", "__ret_value"); + let block = quote::quote_spanned!(block.span()=> + #[allow(unknown_lints)] + #[allow(clippy::useless_format)] + let __input_string = format!(#input_format); + #[allow(unknown_lints)] + let __ret_value = async { #block }.await; + #log; + __ret_value + ); + + if async_keyword { + block + } else { + quote::quote_spanned!(block.span()=> + async move { + #block } - } else { - let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); - quote::quote_spanned!(block.span()=> - #[allow(unknown_lints)] - #[allow(clippy::useless_format)] - let __input_string = format!(#input_format); - #[allow(unknown_lints)] - #[allow(clippy::redundant_closure_call)] - #[allow(clippy::let_unit_value)] - let __ret_value = (move || #block)(); - match __ret_value { - #ok_arm - #err_arm - } - ) + ) + } + } else { + let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); + let log = gen_log(level, "__input_string", "__ret_value"); + quote::quote_spanned!(block.span()=> + #[allow(unknown_lints)] + #[allow(clippy::useless_format)] + let __input_string = format!(#input_format); + #[allow(unknown_lints)] + #[allow(clippy::redundant_closure_call)] + #[allow(clippy::let_unit_value)] + let __ret_value = (move || #block)(); + #log; + __ret_value + ) + } +} + +fn gen_result_label_block( + block: &Block, + async_context: bool, + async_keyword: bool, + sig: &Signature, + ok_level: Option, + err_level: Option, + input_format: Option, +) -> proc_macro2::TokenStream { + let ok_arm = if let Some(ok_level) = ok_level { + let log_ok = gen_log(&ok_level, "__input_string", "__ret_value"); + quote::quote_spanned!(block.span()=> + __ret_value@Ok(_) => { + #log_ok; + __ret_value + } + ) + } else { + quote::quote_spanned!(block.span()=> + Ok(__ret_value) => Ok(__ret_value), + ) + }; + let err_arm = if let Some(err_level) = err_level { + let log_err = gen_log(&err_level, "__input_string", "__ret_value"); + quote::quote_spanned!(block.span()=> + __ret_value@Err(_) => { + #log_err; + __ret_value } + ) + } else { + quote::quote_spanned!(block.span()=> + Err(__ret_value) => Err(__ret_value), + ) + }; + + // Generate the instrumented function body. + // If the function is an `async fn`, this will wrap it in an async block. + if async_context { + let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); + let block = quote::quote_spanned!(block.span()=> + #[allow(unknown_lints)] + #[allow(clippy::useless_format)] + let __input_string = format!(#input_format); + #[allow(unknown_lints)] + let __ret_value = async { #block }.await; + match __ret_value { + #ok_arm + #err_arm + } + ); + + if async_keyword { + block + } else { + quote::quote_spanned!(block.span()=> + async move { + #block + } + ) } + } else { + let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); + quote::quote_spanned!(block.span()=> + #[allow(unknown_lints)] + #[allow(clippy::useless_format)] + let __input_string = format!(#input_format); + #[allow(unknown_lints)] + #[allow(clippy::redundant_closure_call)] + #[allow(clippy::let_unit_value)] + let __ret_value = (move || #block)(); + match __ret_value { + #ok_arm + #err_arm + } + ) + } +} + +fn gen_option_label_block( + block: &Block, + async_context: bool, + async_keyword: bool, + sig: &Signature, + some_level: Option, + none_level: Option, + input_format: Option, +) -> proc_macro2::TokenStream { + let some_arm = if let Some(some_level) = some_level { + let log_some = gen_log(&some_level, "__input_string", "__ret_value"); + quote::quote_spanned!(block.span()=> + __ret_value@Some(_) => { + #log_some; + __ret_value + } + ) + } else { + quote::quote_spanned!(block.span()=> + Some(__ret_value) => Some(__ret_value), + ) + }; + let none_arm = if let Some(none_level) = none_level { + let log_none = gen_log(&none_level, "__input_string", "__ret_value"); + quote::quote_spanned!(block.span()=> + None => { + #log_none; + None + } + ) + } else { + quote::quote_spanned!(block.span()=> + None => None, + ) + }; + + // Generate the instrumented function body. + // If the function is an `async fn`, this will wrap it in an async block. + if async_context { + let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); + let block = quote::quote_spanned!(block.span()=> + #[allow(unknown_lints)] + #[allow(clippy::useless_format)] + let __input_string = format!(#input_format); + #[allow(unknown_lints)] + let __ret_value = async { #block }.await; + match __ret_value { + #some_arm + #none_arm + } + ); + + if async_keyword { + block + } else { + quote::quote_spanned!(block.span()=> + async move { + #block + } + ) + } + } else { + let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); + quote::quote_spanned!(block.span()=> + #[allow(unknown_lints)] + #[allow(clippy::useless_format)] + let __input_string = format!(#input_format); + #[allow(unknown_lints)] + #[allow(clippy::redundant_closure_call)] + #[allow(clippy::let_unit_value)] + let __ret_value = (move || #block)(); + match __ret_value { + #some_arm + #none_arm + } + ) } } diff --git a/tests/ui/err/multiple-args.rs b/tests/ui/err/multiple-args.rs new file mode 100644 index 0000000..1cef575 --- /dev/null +++ b/tests/ui/err/multiple-args.rs @@ -0,0 +1,31 @@ +#[logcall::logcall("info", ok = "info")] +fn f0() {} + +#[logcall::logcall("info", err = "info")] +fn f1() {} + +#[logcall::logcall("info", ok = "info", err = "info")] +fn f2() {} + +#[logcall::logcall("info", some = "info")] +fn f3() {} + +#[logcall::logcall("info", none = "info")] +fn f4() {} + +#[logcall::logcall("info", some = "info", none = "info")] +fn f5() {} + +#[logcall::logcall(some = "info", ok = "info")] +fn f6() {} + +#[logcall::logcall(ok = "info", some = "info")] +fn f7() {} + +#[logcall::logcall(some = "info", none = "info", ok = "info", err = "info")] +fn f8() {} + +#[logcall::logcall("info", some = "info", none = "info", ok = "info", err = "info")] +fn f9() {} + +fn main() {} diff --git a/tests/ui/err/multiple-args.stderr b/tests/ui/err/multiple-args.stderr new file mode 100644 index 0000000..bdeaaa0 --- /dev/null +++ b/tests/ui/err/multiple-args.stderr @@ -0,0 +1,79 @@ +error: plain level cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:1:1 + | +1 | #[logcall::logcall("info", ok = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: plain level cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:4:1 + | +4 | #[logcall::logcall("info", err = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: plain level cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:7:1 + | +7 | #[logcall::logcall("info", ok = "info", err = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: plain level cannot be specified with `some` or `none` levels + --> tests/ui/err/multiple-args.rs:10:1 + | +10 | #[logcall::logcall("info", some = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: plain level cannot be specified with `some` or `none` levels + --> tests/ui/err/multiple-args.rs:13:1 + | +13 | #[logcall::logcall("info", none = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: plain level cannot be specified with `some` or `none` levels + --> tests/ui/err/multiple-args.rs:16:1 + | +16 | #[logcall::logcall("info", some = "info", none = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `some` and `none` levels cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:19:1 + | +19 | #[logcall::logcall(some = "info", ok = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `some` and `none` levels cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:22:1 + | +22 | #[logcall::logcall(ok = "info", some = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `some` and `none` levels cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:25:1 + | +25 | #[logcall::logcall(some = "info", none = "info", ok = "info", err = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: plain level cannot be specified with `ok` or `err` levels + --> tests/ui/err/multiple-args.rs:28:1 + | +28 | #[logcall::logcall("info", some = "info", none = "info", ok = "info", err = "info")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/ok/async-in-trait.rs b/tests/ui/ok/async-in-trait.rs index 7071846..e9b2842 100644 --- a/tests/ui/ok/async-in-trait.rs +++ b/tests/ui/ok/async-in-trait.rs @@ -1,5 +1,6 @@ trait MyTrait { async fn work(&self) -> Result; + async fn run(&self) -> Option; } struct MyStruct; @@ -10,6 +11,12 @@ impl MyTrait for MyStruct { async fn work(&self) -> Result { Ok(1) } + + #[logcall::logcall("debug")] + #[logcall::logcall(some = "debug", none = "error")] + async fn run(&self) -> Option { + Some(1) + } } fn main() {} diff --git a/tests/ui/ok/async.rs b/tests/ui/ok/async.rs index c77043a..06d083c 100644 --- a/tests/ui/ok/async.rs +++ b/tests/ui/ok/async.rs @@ -18,10 +18,28 @@ async fn i(a: u32) -> Result { Ok(a) } +#[logcall::logcall(some = "info")] +async fn j(a: u32) -> Option { + Some(a) +} + +#[logcall::logcall(none = "info")] +async fn k(a: u32) -> Option { + Some(a) +} + +#[logcall::logcall(some = "info", none = "info")] +async fn l(a: u32) -> Option { + Some(a) +} + #[tokio::main] async fn main() { f(1).await; g(1).await.ok(); h(1).await.ok(); i(1).await.ok(); + j(1).await.unwrap(); + k(1).await.unwrap(); + l(1).await.unwrap(); } diff --git a/tests/ui/ok/sync.rs b/tests/ui/ok/sync.rs index 9a3ff0c..4c2981c 100644 --- a/tests/ui/ok/sync.rs +++ b/tests/ui/ok/sync.rs @@ -18,9 +18,27 @@ fn i(a: u32) -> Result { Ok(a) } +#[logcall::logcall(some = "info")] +fn j(a: u32) -> Option { + Some(a) +} + +#[logcall::logcall(none = "info")] +fn k(a: u32) -> Option { + Some(a) +} + +#[logcall::logcall(some = "info", none = "info")] +fn l(a: u32) -> Option { + Some(a) +} + fn main() { f(1); g(1).ok(); h(1).ok(); i(1).ok(); + j(1).unwrap(); + k(1).unwrap(); + l(1).unwrap(); }