Skip to content

Commit bbba7c1

Browse files
authored
Report error locus within instrumented code (#170)
* Add report locus test * Give Function a higher precedence than Impl when parsing * Let rust parse impl automatically Letting Rust parse the impl block allows us to keep the same TokenStream as input in the autometrics macro code, even when async_trait is involved. Keeping the original TokenStream keeps the Span information in method/function bodies so error reporting in instrumented code is not absorbed in "#[autometrics]" span. This also simplifies the parsing code a little bit.
1 parent 8aa7cf9 commit bbba7c1

18 files changed

+56
-33
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Update `http` to `1.0`. This fixes compatibility with `axum 0.7` (#167)
1313
- Explicitly set default timeout and period for the OTEL push exporter (#168)
14+
- Fix a regression that made all compilation errors in instrumented code appear as located in
15+
the `#[autometrics]` annotation instead of the original location (#170)
1416

1517
## [1.0.0](https://github.com/autometrics-dev/autometrics-rs/releases/tag/v1.0.0) - 2023-12-01
1618

autometrics-macros/src/lib.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ pub fn autometrics(
2525
) -> proc_macro::TokenStream {
2626
let args = parse_macro_input!(args as AutometricsArgs);
2727

28-
let (async_trait, item) = check_async_trait(item);
28+
let async_trait = check_async_trait(&item);
2929
let item = parse_macro_input!(item as Item);
3030

3131
let result = match item {
32-
Item::Function(item) => instrument_function(&args, item, &None),
32+
Item::Function(item) => instrument_function(&args, item, None),
3333
Item::Impl(item) => instrument_impl_block(&args, item, &async_trait),
3434
};
3535

@@ -41,22 +41,15 @@ pub fn autometrics(
4141
output.into()
4242
}
4343

44-
/// returns a tuple of two containing:
45-
/// - `async_trait` attributes that have to be re-added after our instrumentation magic has been added
46-
/// - `input` but without the `async_trait` attributes
47-
fn check_async_trait(input: proc_macro::TokenStream) -> (String, proc_macro::TokenStream) {
44+
/// returns the `async_trait` attributes that have to be re-added after our instrumentation magic has been added
45+
fn check_async_trait(input: &proc_macro::TokenStream) -> String {
4846
let regex = Regex::new(r#"#\[[^\]]*async_trait\]"#)
4947
.expect("The regex is hardcoded and thus guaranteed to be successfully parseable");
5048

5149
let original = input.to_string();
52-
5350
let attributes: Vec<_> = regex.find_iter(&original).map(|m| m.as_str()).collect();
54-
let replaced = regex.replace_all(&original, "");
55-
56-
// .unwrap is safe because we only remove tokens from the existing stream, we dont add new ones
57-
let ts = proc_macro::TokenStream::from_str(replaced.as_ref()).unwrap();
5851

59-
(attributes.join("\n"), ts)
52+
attributes.join("\n")
6053
}
6154

6255
#[proc_macro_derive(ResultLabels, attributes(label))]
@@ -71,7 +64,7 @@ pub fn result_labels(input: proc_macro::TokenStream) -> proc_macro::TokenStream
7164
fn instrument_function(
7265
args: &AutometricsArgs,
7366
item: ItemFn,
74-
struct_name: &Option<String>,
67+
struct_name: Option<&str>,
7568
) -> Result<TokenStream> {
7669
let sig = item.sig;
7770
let block = item.block;
@@ -364,7 +357,7 @@ fn instrument_impl_block(
364357
sig: method.sig,
365358
block: Box::new(method.block),
366359
};
367-
let tokens = match instrument_function(args, item_fn, &struct_name) {
360+
let tokens = match instrument_function(args, item_fn, struct_name.as_deref()) {
368361
Ok(tokens) => tokens,
369362
Err(err) => err.to_compile_error(),
370363
};

autometrics-macros/src/parse.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ pub(crate) enum Item {
1919

2020
impl Parse for Item {
2121
fn parse(input: ParseStream) -> Result<Self> {
22-
let lookahead = input.lookahead1();
23-
if lookahead.peek(Token![impl]) {
24-
input.parse().map(Item::Impl)
25-
} else {
26-
input.parse().map(Item::Function)
27-
}
22+
input
23+
.parse()
24+
.map(Item::Function)
25+
.or_else(|_| input.parse().map(Item::Impl))
2826
}
2927
}
3028

autometrics/tests/compilation.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! Tests relying on macros or compiler diagnostics
2+
3+
#[test]
4+
fn harness() {
5+
let t = trybuild::TestCases::new();
6+
7+
// Test the ResultLabels macro
8+
t.pass("tests/compilation/result_labels/pass/*.rs");
9+
t.compile_fail("tests/compilation/result_labels/fail/*.rs");
10+
11+
// Test that compiler reports errors in the correct location
12+
t.compile_fail("tests/compilation/error_locus/fail/*.rs");
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This test ensures that when an instrumented function has a compilation error,
2+
// then the error is reported at the correct line in the original code.
3+
use autometrics::autometrics;
4+
5+
#[autometrics]
6+
fn bad_function() {
7+
// This vec is not mut
8+
let contents: Vec<u32> = Vec::new();
9+
10+
contents.push(2);
11+
}
12+
13+
fn main() {
14+
bad_function();
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error[E0596]: cannot borrow `contents` as mutable, as it is not declared as mutable
2+
--> tests/compilation/error_locus/fail/report_original_line.rs:10:5
3+
|
4+
10 | contents.push(2);
5+
| ^^^^^^^^ cannot borrow as mutable
6+
|
7+
help: consider changing this to be mutable
8+
|
9+
8 | let mut contents: Vec<u32> = Vec::new();
10+
| +++
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: Only `label(result = "RES")` (RES can be "ok" or "error") is supported
2-
--> tests/result_labels/fail/wrong_attribute.rs:11:7
2+
--> tests/compilation/result_labels/fail/wrong_attribute.rs:11:7
33
|
44
11 | #[label]
55
| ^^^^^
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: Only `label(result = "RES")` (RES can be "ok" or "error") is supported
2-
--> tests/result_labels/fail/wrong_kv_attribute.rs:11:7
2+
--> tests/compilation/result_labels/fail/wrong_kv_attribute.rs:11:7
33
|
44
11 | #[label = "error"]
55
| ^^^^^^^^^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: Only `result = "RES"` (RES can be "ok" or "error") is supported
2-
--> tests/result_labels/fail/wrong_result_name.rs:11:13
2+
--> tests/compilation/result_labels/fail/wrong_result_name.rs:11:13
33
|
44
11 | #[label(unknown = "ok")]
55
| ^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: Only "ok" or "error" are accepted as result values
2-
--> tests/result_labels/fail/wrong_variant.rs:12:22
2+
--> tests/compilation/result_labels/fail/wrong_variant.rs:12:22
33
|
44
12 | #[label(result = "not ok")]
55
| ^^^^^^^^

autometrics/tests/result_labels/pass/async_trait_support.rs renamed to autometrics/tests/compilation/result_labels/pass/async_trait_support.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl TestTrait for TestStruct {
2727
fn main() {
2828
let ts = TestStruct::default();
2929

30-
async move {
30+
let _ = async move {
3131
<TestStruct as TestTrait>::method().await;
3232
ts.self_method().await;
3333
};

autometrics/tests/result_labels.rs

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)