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
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
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() {}
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
@@ -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
@@ -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
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
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

0 comments on commit 7739ac2

Please sign in to comment.