Repository: eyre-rs/eyre (Rust)

Issue:
Why does the Location section sometimes point inside eyre itself?
Sometimes when using eyre and printing out the error, the location sections points inside of eyre itself instead of the line at which the error occurred. What is the cause of this, and how do I avoid it?

Example error message:
```
Error: 
   0: Could not open file at "/bogus/path"
   1: No such file or directory (os error 2)

Location:
   /home/ryan/.cargo/registry/src/github.com-1ecc6299db9ec823/eyre-0.6.3/src/context.rs:25

Backtrace omitted.
Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
```

Code snippet:
```rust
use eyre::{Result, WrapErr};

fn main() -> Result<()> {
    color_eyre::install()?;

    let args = Args::parse();

    let mut reader: Box<dyn Read + Send> = if let Some(ref path) = args.path {
        let reader = std::fs::File::open(path)
            .wrap_err(format!("Could not open file at {:?}", path))?;
        Box::new(reader)
    } else {
        // ....
    };

    //...
}
```


Discussion:
@TheButlah this is always a bug, and it just means there's a method that can construct another `eyre::Report` that I missed and need to add `#[track_caller]` to. There's nothing you can do to avoid it other than report them to me as you see them, so thank you for giving me the heads up ^_^.

https://github.com/yaahc/eyre/pull/48 should fix it, I should have a new release with the fixes up on crates in just a few minutes.




Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 879ee8f..f4ae5a3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,23 +24,6 @@ jobs:
         with:
           command: check
 
-  test-features:
-    name: Test Suite
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v1
-      - uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          override: true
-      - uses: actions-rs/cargo@v1
-        with:
-          command: test
-      - uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --all-features
-
   test-versions:
     name: Test Suite
     runs-on: ubuntu-latest
@@ -51,6 +34,9 @@ jobs:
           - beta
           - nightly
           - 1.39.0
+        features:
+          - --all-features
+          - --no-default-features
     steps:
       - uses: actions/checkout@v1
       - uses: actions-rs/toolchain@v1
@@ -60,6 +46,7 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: test
+          args: ${{ matrix.features }}
 
   test-os:
     name: Test Suite
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 76d40b0..904a170 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased] - ReleaseDate
 
+## [0.6.4] - 2021-01-04
+### Fixed
+- added missing track_caller annotations to `wrap_err` related trait methods
+
 ## [0.6.3] - 2020-11-10
 ### Fixed
 - added missing track_caller annotation to autoref specialization functions
@@ -22,7 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 
 <!-- next-url -->
-[Unreleased]: https://github.com/yaahc/eyre/compare/v0.6.3...HEAD
+[Unreleased]: https://github.com/yaahc/eyre/compare/v0.6.4...HEAD
+[0.6.4]: https://github.com/yaahc/eyre/compare/v0.6.3...v0.6.4
 [0.6.3]: https://github.com/yaahc/eyre/compare/v0.6.2...v0.6.3
 [0.6.2]: https://github.com/yaahc/eyre/compare/v0.6.1...v0.6.2
 [0.6.1]: https://github.com/yaahc/eyre/releases/tag/v0.6.1
diff --git a/Cargo.toml b/Cargo.toml
index a53d84b..6776810 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "eyre"
-version = "0.6.4-alpha.0"
+version = "0.6.4"
 authors = ["David Tolnay <dtolnay@gmail.com>", "Jane Lusby <jlusby42@gmail.com>"]
 edition = "2018"
 license = "MIT OR Apache-2.0"
@@ -30,6 +30,9 @@ once_cell = "1.4.0"
 targets = ["x86_64-unknown-linux-gnu"]
 rustdoc-args = ["--cfg", "doc_cfg"]
 
+[package.metadata.release]
+no-dev-version = true
+
 [[package.metadata.release.pre-release-replacements]]
 file = "CHANGELOG.md"
 search = "Unreleased"
diff --git a/src/context.rs b/src/context.rs
index 6c4a55c..fef121f 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -9,6 +9,7 @@ mod ext {
     use super::*;
 
     pub trait StdError {
+        #[cfg_attr(track_caller, track_caller)]
         fn ext_report<D>(self, msg: D) -> Report
         where
             D: Display + Send + Sync + 'static;
@@ -44,7 +45,10 @@ where
     where
         D: Display + Send + Sync + 'static,
     {
-        self.map_err(|error| error.ext_report(msg))
+        match self {
+            Ok(t) => Ok(t),
+            Err(e) => Err(e.ext_report(msg)),
+        }
     }
 
     fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
@@ -52,7 +56,10 @@ where
         D: Display + Send + Sync + 'static,
         F: FnOnce() -> D,
     {
-        self.map_err(|error| error.ext_report(msg()))
+        match self {
+            Ok(t) => Ok(t),
+            Err(e) => Err(e.ext_report(msg())),
+        }
     }
 
     fn context<D>(self, msg: D) -> Result<T, Report>
@@ -91,7 +98,10 @@ impl<T> ContextCompat<T> for Option<T> {
     where
         D: Display + Send + Sync + 'static,
     {
-        self.ok_or_else(|| Report::from_display(msg))
+        match self {
+            Some(t) => Ok(t),
+            None => Err(Report::from_display(msg)),
+        }
     }
 
     fn with_context<D, F>(self, msg: F) -> Result<T, Report>
@@ -99,7 +109,10 @@ impl<T> ContextCompat<T> for Option<T> {
         D: Display + Send + Sync + 'static,
         F: FnOnce() -> D,
     {
-        self.ok_or_else(|| Report::from_display(msg()))
+        match self {
+            Some(t) => Ok(t),
+            None => Err(Report::from_display(msg())),
+        }
     }
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index 15708b5..8f366dd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -283,7 +283,7 @@
 //! [`simple-eyre`]: https://github.com/yaahc/simple-eyre
 //! [`color-spantrace`]: https://github.com/yaahc/color-spantrace
 //! [`color-backtrace`]: https://github.com/athre0z/color-backtrace
-#![doc(html_root_url = "https://docs.rs/eyre/0.6.3")]
+#![doc(html_root_url = "https://docs.rs/eyre/0.6.4")]
 #![warn(
     missing_debug_implementations,
     missing_docs,
@@ -1010,23 +1010,27 @@ pub type Result<T, E = Report> = core::result::Result<T, E>;
 ///     ```
 pub trait WrapErr<T, E>: context::private::Sealed {
     /// Wrap the error value with a new adhoc error
+    #[cfg_attr(track_caller, track_caller)]
     fn wrap_err<D>(self, msg: D) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static;
 
     /// Wrap the error value with a new adhoc error that is evaluated lazily
     /// only once an error does occur.
+    #[cfg_attr(track_caller, track_caller)]
     fn wrap_err_with<D, F>(self, f: F) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static,
         F: FnOnce() -> D;
 
     /// Compatibility re-export of wrap_err for interopt with `anyhow`
+    #[cfg_attr(track_caller, track_caller)]
     fn context<D>(self, msg: D) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static;
 
     /// Compatibility re-export of wrap_err_with for interopt with `anyhow`
+    #[cfg_attr(track_caller, track_caller)]
     fn with_context<D, F>(self, f: F) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static,
@@ -1079,23 +1083,27 @@ pub trait WrapErr<T, E>: context::private::Sealed {
 pub trait ContextCompat<T>: context::private::Sealed {
     /// Compatibility version of `wrap_err` for creating new errors with new source on `Option`
     /// when porting from `anyhow`
+    #[cfg_attr(track_caller, track_caller)]
     fn context<D>(self, msg: D) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static;
 
     /// Compatibility version of `wrap_err_with` for creating new errors with new source on `Option`
     /// when porting from `anyhow`
+    #[cfg_attr(track_caller, track_caller)]
     fn with_context<D, F>(self, f: F) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static,
         F: FnOnce() -> D;
 
     /// Compatibility re-export of `context` for porting from `anyhow` to `eyre`
+    #[cfg_attr(track_caller, track_caller)]
     fn wrap_err<D>(self, msg: D) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static;
 
     /// Compatibility re-export of `with_context` for porting from `anyhow` to `eyre`
+    #[cfg_attr(track_caller, track_caller)]
     fn wrap_err_with<D, F>(self, f: F) -> Result<T, Report>
     where
         D: Display + Send + Sync + 'static,


Test patch:
diff --git a/tests/test_location.rs b/tests/test_location.rs
new file mode 100644
index 0000000..d851f5a
--- /dev/null
+++ b/tests/test_location.rs
@@ -0,0 +1,156 @@
+use std::panic::Location;
+
+use eyre::WrapErr;
+
+struct LocationHandler {
+    actual: Option<&'static str>,
+    expected: &'static str,
+}
+
+impl LocationHandler {
+    fn new(expected: &'static str) -> Self {
+        LocationHandler {
+            actual: None,
+            expected,
+        }
+    }
+}
+
+impl eyre::EyreHandler for LocationHandler {
+    fn debug(
+        &self,
+        _error: &(dyn std::error::Error + 'static),
+        _f: &mut std::fmt::Formatter<'_>,
+    ) -> std::fmt::Result {
+        // we assume that if the compiler is new enough to support
+        // `track_caller` that we will always have `actual` be `Some`, so we can
+        // safely skip the assertion if the location is `None` which should only
+        // happen in older rust versions.
+        if let Some(actual) = self.actual {
+            assert_eq!(self.expected, actual);
+        }
+
+        Ok(())
+    }
+
+    fn track_caller(&mut self, location: &'static Location<'static>) {
+        dbg!(location);
+        self.actual = Some(location.file());
+    }
+}
+
+#[test]
+fn test_wrap_err() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .wrap_err("oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_wrap_err_with() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .wrap_err_with(|| "oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_context() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .context("oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_with_context() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    let err = std::fs::read_to_string("totally_fake_path")
+        .with_context(|| "oopsie")
+        .unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_option_compat_wrap_err() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    use eyre::ContextCompat;
+    let err = None::<()>.wrap_err("oopsie").unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_option_compat_wrap_err_with() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    use eyre::ContextCompat;
+    let err = None::<()>.wrap_err_with(|| "oopsie").unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_option_compat_context() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    use eyre::ContextCompat;
+    let err = None::<()>.context("oopsie").unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}
+
+#[test]
+fn test_option_compat_with_context() {
+    let _ = eyre::set_hook(Box::new(|_e| {
+        let expected_location = file!();
+        Box::new(LocationHandler::new(expected_location))
+    }));
+
+    use eyre::ContextCompat;
+    let err = None::<()>.with_context(|| "oopsie").unwrap_err();
+
+    // should panic if the location isn't in our crate
+    println!("{:?}", err);
+}