Skip to content

Commit

Permalink
Add the Location struct and register it as implicitly generated data
Browse files Browse the repository at this point in the history
We had to inline usages of `map_err` because those combinators don't
have `#[track_caller]`. Other combinators, such as those in futures,
are more difficult to fix, so we offer some workarounds.

This requires Rust 1.46, so added a new feature flag.
  • Loading branch information
shepmaster committed Sep 25, 2021
1 parent 4b5ebfc commit 7739ac2
Show file tree
Hide file tree
Showing 13 changed files with 824 additions and 46 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ exclude = [
features = [ "std", "backtraces", "futures", "guide" ]

[features]
default = ["std"]
default = ["std", "rust_1_46"]

# Implement the `std::error::Error` trait.
std = []

# Add support for `#[track_caller]`
rust_1_46 = ["snafu-derive/rust_1_46"]

# Makes the backtrace type live
backtraces = ["std", "backtrace"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,16 @@ mod backtrace {
}
}

mod implicit {
use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum EnumError {
AVariant {
#[snafu(implicit(false))]
not_location: u8,
},
}
}

fn main() {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ error: `backtrace(false)` attribute is only valid on a field named "backtrace",
|
19 | #[snafu(backtrace(false))]
| ^^^^^^^^^^^^^^^^

error: `implicit(false)` attribute is only valid on a field named "location", not on other fields
--> $DIR/attribute-misuse-opt-out-wrong-field.rs:31:21
|
31 | #[snafu(implicit(false))]
| ^^^^^^^^^^^^^^^
2 changes: 2 additions & 0 deletions compatibility-tests/futures/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![cfg(test)]

mod location;

mod api {
use futures::{stream, StreamExt, TryStream};
use snafu::prelude::*;
Expand Down
254 changes: 254 additions & 0 deletions compatibility-tests/futures/src/location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
use futures::{executor::block_on, prelude::*};
use snafu::{location, prelude::*, Location};

#[derive(Debug, Copy, Clone, Snafu)]
struct InnerError {
location: Location,
}

#[derive(Debug, Copy, Clone, Snafu)]
struct WrappedError {
source: InnerError,
location: Location,
}

#[derive(Debug, Snafu)]
struct ManuallyWrappedError {
source: InnerError,
#[snafu(implicit(false))]
location: Location,
}

#[derive(Debug, Snafu)]
#[snafu(display("{}", message))]
#[snafu(whatever)]
pub struct MyWhatever {
#[snafu(source(from(Box<dyn std::error::Error>, Some)))]
source: Option<Box<dyn std::error::Error>>,
message: String,
location: Location,
}

mod try_future {
use super::*;

#[test]
fn location_macro_uses_creation_location() {
block_on(async {
let base_line = line!();
let error_future = async { InnerSnafu.fail::<()>() };
let wrapped_error_future = error_future.with_context(|| ManuallyWrappedSnafu {
location: location!(),
});
let wrapped_error = wrapped_error_future.await.unwrap_err();

assert_eq!(
wrapped_error.location.line,
base_line + 3,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn async_block_uses_creation_location() {
block_on(async {
let base_line = line!();
let error_future = async { InnerSnafu.fail::<()>() };
let wrapped_error_future = async { error_future.await.context(WrappedSnafu) };
let wrapped_error = wrapped_error_future.await.unwrap_err();

assert_eq!(
wrapped_error.location.line,
base_line + 2,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_context_poll() {
block_on(async {
let base_line = line!();
let error_future = async { InnerSnafu.fail::<()>() };
let wrapped_error_future = error_future.context(WrappedSnafu);
let wrapped_error = wrapped_error_future.await.unwrap_err();

// `.await` calls our implementation of `poll`, so the
// location corresponds to that line.
assert_eq!(
wrapped_error.location.line,
base_line + 3,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_with_context_poll() {
block_on(async {
let base_line = line!();
let error_future = async { InnerSnafu.fail::<()>() };
let wrapped_error_future = error_future.with_context(|| WrappedSnafu);
let wrapped_error = wrapped_error_future.await.unwrap_err();

// `.await` calls our implementation of `poll`, so the
// location corresponds to that line.
assert_eq!(
wrapped_error.location.line,
base_line + 3,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_whatever_context_poll() {
block_on(async {
let base_line = line!();
let error_future = async { InnerSnafu.fail::<()>() };
let wrapped_error_future = error_future.whatever_context("bang");
let wrapped_error: MyWhatever = wrapped_error_future.await.unwrap_err();

// `.await` calls our implementation of `poll`, so the
// location corresponds to that line.
assert_eq!(
wrapped_error.location.line,
base_line + 3,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_with_whatever_context_poll() {
block_on(async {
let base_line = line!();
let error_future = async { InnerSnafu.fail::<()>() };
let wrapped_error_future = error_future.with_whatever_context(|_| "bang");
let wrapped_error: MyWhatever = wrapped_error_future.await.unwrap_err();

// `.await` calls our implementation of `poll`, so the
// location corresponds to that line.
assert_eq!(
wrapped_error.location.line,
base_line + 3,
"Actual location: {}",
wrapped_error.location,
);
});
}
}

mod try_stream {
use super::*;

#[test]
fn location_macro_uses_creation_location() {
block_on(async {
let base_line = line!();
let error_stream = stream::repeat(InnerSnafu.fail::<()>());
let mut wrapped_error_stream = error_stream.with_context(|| ManuallyWrappedSnafu {
location: location!(),
});
let wrapped_error = wrapped_error_stream.next().await.unwrap().unwrap_err();

assert_eq!(
wrapped_error.location.line,
base_line + 3,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn async_block_uses_creation_location() {
block_on(async {
let base_line = line!();
let error_stream = stream::repeat(InnerSnafu.fail::<()>());
let mut wrapped_error_stream = error_stream.map(|r| r.context(WrappedSnafu));
let wrapped_error = wrapped_error_stream.next().await.unwrap().unwrap_err();

assert_eq!(
wrapped_error.location.line,
base_line + 2,
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_context_poll() {
block_on(async {
let error_stream = stream::repeat(InnerSnafu.fail::<()>());
let mut wrapped_error_stream = error_stream.context(WrappedSnafu);
let wrapped_error = wrapped_error_stream.next().await.unwrap().unwrap_err();

// `StreamExt::next` doesn't have `[track_caller]`, so the
// location is inside the futures library.
assert!(
wrapped_error.location.file.contains("/futures-util-"),
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_with_context_poll() {
block_on(async {
let error_stream = stream::repeat(InnerSnafu.fail::<()>());
let mut wrapped_error_stream = error_stream.with_context(|| WrappedSnafu);
let wrapped_error = wrapped_error_stream.next().await.unwrap().unwrap_err();

// `StreamExt::next` doesn't have `[track_caller]`, so the
// location is inside the futures library.
assert!(
wrapped_error.location.file.contains("/futures-util-"),
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_whatever_context_poll() {
block_on(async {
let error_stream = stream::repeat(InnerSnafu.fail::<()>());
let mut wrapped_error_stream = error_stream.whatever_context("bang");
let wrapped_error: MyWhatever = wrapped_error_stream.next().await.unwrap().unwrap_err();

// `StreamExt::next` doesn't have `[track_caller]`, so the
// location is inside the futures library.
assert!(
wrapped_error.location.file.contains("/futures-util-"),
"Actual location: {}",
wrapped_error.location,
);
});
}

#[test]
fn track_caller_is_applied_on_with_whatever_context_poll() {
block_on(async {
let error_stream = stream::repeat(InnerSnafu.fail::<()>());
let mut wrapped_error_stream = error_stream.with_whatever_context(|_| "bang");
let wrapped_error: MyWhatever = wrapped_error_stream.next().await.unwrap().unwrap_err();

// `StreamExt::next` doesn't have `[track_caller]`, so the
// location is inside the futures library.
assert!(
wrapped_error.location.file.contains("/futures-util-"),
"Actual location: {}",
wrapped_error.location,
);
});
}
}
1 change: 1 addition & 0 deletions snafu-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/shepmaster/snafu"
license = "MIT OR Apache-2.0"

[features]
rust_1_46 = []
unstable-backtraces-impl-std = []

[lib]
Expand Down
13 changes: 12 additions & 1 deletion snafu-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,11 @@ const ATTR_IMPLICIT: OnlyValidOn = OnlyValidOn {
valid_on: "enum variant or struct fields with a name",
};

const ATTR_IMPLICIT_FALSE: WrongField = WrongField {
attribute: "implicit(false)",
valid_field: "location",
};

const ATTR_VISIBILITY: OnlyValidOn = OnlyValidOn {
attribute: "visibility",
valid_on: "an enum, enum variants, or a struct with named fields",
Expand Down Expand Up @@ -704,6 +709,7 @@ fn field_container(
// exclude fields even if they have the "source" or "backtrace" name.
let mut source_opt_out = false;
let mut backtrace_opt_out = false;
let mut implicit_opt_out = false;

let mut field_errors = errors.scoped(ErrorLocation::OnField);

Expand Down Expand Up @@ -753,6 +759,10 @@ fn field_container(
Att::Implicit(tokens, v) => {
if v {
implicit_attrs.add((), tokens);
} else if name == "location" {
implicit_opt_out = true;
} else {
field_errors.add(tokens, ATTR_IMPLICIT_FALSE);
}
}
Att::Visibility(tokens, ..) => field_errors.add(tokens, ATTR_VISIBILITY),
Expand Down Expand Up @@ -789,7 +799,8 @@ fn field_container(
}
});

let implicit_attr = implicit_attr.is_some();
let implicit_attr =
implicit_attr.is_some() || (field.name == "location" && !implicit_opt_out);

if let Some((maybe_transformation, location)) = source_attr {
let Field { name, ty, .. } = field;
Expand Down
Loading

0 comments on commit 7739ac2

Please sign in to comment.