Import crate tracing-attributes am: fa35e1ea17 am: 6f5623c352 am: e69cd6ba41 am: 26ef07ebe5

Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tracing-attributes/+/2506420

Change-Id: I64e615112fbcbb6f4e10045fcf92b0972b57049d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..42cb09b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,327 @@
+# 0.1.23 (October 6, 2022)
+
+This release of `tracing-attributes` fixes a bug where compiler diagnostic spans
+for type errors in `#[instrument]`ed `async fn`s have the location of the
+`#[instrument]` attribute rather than the location of the actual error, and a
+bug where inner attributes in `#[instrument]`ed functions would cause a compiler
+error.
+### Fixed
+
+- Fix incorrect handling of inner attributes in `#[instrument]`ed functions ([#2307])
+- Add fake return to improve spans generated for type errors in `async fn`s ([#2270])
+- Updated `syn` dependency to fix compilation with `-Z minimal-versions`
+  ([#2246])
+
+Thanks to new contributors @compiler-errors and @e-nomem, as well as @CAD97, for
+contributing to this release!
+
+[#2307]: https://github.com/tokio-rs/tracing/pull/2307
+[#2270]: https://github.com/tokio-rs/tracing/pull/2270
+[#2246]: https://github.com/tokio-rs/tracing/pull/2246
+
+# 0.1.22 (July 1, 2022)
+
+This release fixes an issue where using the `err` or `ret` arguments to
+`#[instrument]` along with an overridden target, such as
+
+```rust
+#[instrument(target = "...", err, ret)]
+```
+
+would not propagate the overridden target to the events generated for
+errors/return values.
+
+### Fixed
+
+- Error and return value events generated by `#[instrument(err)]` or
+  `#[instrument(ret)]` not inheriting an overridden target ([#2184])
+- Incorrect default level in documentation ([#2119])
+
+Thanks to new contributor @tbraun96 for contributing to this release!
+
+[#2184]: https://github.com/tokio-rs/tracing/pull/2184
+[#2119]: https://github.com/tokio-rs/tracing/pull/2119
+
+# 0.1.21 (April 26, 2022)
+
+This release adds support for setting explicit parent and follows-from spans
+in the `#[instrument]` attribute.
+
+### Added
+
+- `#[instrument(follows_from = ...)]` argument for setting one or more
+  follows-from span ([#2093])
+- `#[instrument(parent = ...)]` argument for overriding the generated span's
+  parent ([#2091])
+
+### Fixed
+
+- Extra braces around `async` blocks in expanded code (causes a Clippy warning)
+  ([#2090])
+- Broken documentation links ([#2068], [#2077])
+
+Thanks to @jarrodldavis, @ben0x539, and new contributor @jswrenn for
+contributing to this release!
+
+
+[#2093]: https://github.com/tokio-rs/tracing/pull/2093
+[#2091]: https://github.com/tokio-rs/tracing/pull/2091
+[#2090]: https://github.com/tokio-rs/tracing/pull/2090
+[#2077]: https://github.com/tokio-rs/tracing/pull/2077
+[#2068]: https://github.com/tokio-rs/tracing/pull/2068
+
+# 0.1.20 (March 8, 2022)
+
+### Fixed
+
+- Compilation failure with `--minimal-versions` due to a too-permissive `syn`
+  dependency ([#1960])
+
+### Changed
+
+- Bumped minimum supported Rust version (MSRV) to 1.49.0 ([#1913])
+
+Thanks to new contributor @udoprog for contributing to this release!
+
+[#1960]: https://github.com/tokio-rs/tracing/pull/1960
+[#1913]: https://github.com/tokio-rs/tracing/pull/1913
+
+# 0.1.19 (February 3, 2022)
+
+This release introduces a new `#[instrument(ret)]` argument to emit an event
+with the return value of an instrumented function.
+
+### Added
+
+- `#[instrument(ret)]` to record the return value of a function ([#1716])
+- added `err(Debug)` argument to cause `#[instrument(err)]` to record errors
+  with `Debug` rather than `Display ([#1631])
+
+### Fixed
+
+- incorrect code generation for functions returning async blocks ([#1866])
+- incorrect diagnostics when using `rust-analyzer` ([#1634])
+
+Thanks to @Swatinem, @hkmatsumoto, @cynecx, and @ciuncan for contributing to
+this release!
+
+[#1716]: https://github.com/tokio-rs/tracing/pull/1716
+[#1631]: https://github.com/tokio-rs/tracing/pull/1631
+[#1634]: https://github.com/tokio-rs/tracing/pull/1634
+[#1866]: https://github.com/tokio-rs/tracing/pull/1866
+
+# 0.1.18 (October 5, 2021)
+
+This release fixes issues introduced in v0.1.17.
+
+### Fixed
+
+- fixed mismatched types compiler error that may occur when using
+  `#[instrument]` on an `async fn` that returns an `impl Trait` value that
+  includes a closure ([#1616])
+- fixed false positives for `clippy::suspicious_else_formatting` warnings due to
+  rust-lang/rust-clippy#7760 and rust-lang/rust-clippy#6249 ([#1617])
+- fixed `clippy::let_unit_value` lints when using `#[instrument]` ([#1614])
+
+[#1617]: https://github.com/tokio-rs/tracing/pull/1617
+[#1616]: https://github.com/tokio-rs/tracing/pull/1616
+[#1614]: https://github.com/tokio-rs/tracing/pull/1614
+
+# 0.1.17 (YANKED) (October 1, 2021)
+
+This release significantly improves performance when `#[instrument]`-generated
+spans are below the maximum enabled level.
+
+### Added
+
+- improve performance when skipping `#[instrument]`-generated spans below the
+  max level ([#1600], [#1605])
+
+Thanks to @oli-obk for contributing to this release!
+
+[#1600]: https://github.com/tokio-rs/tracing/pull/1600
+[#1605]: https://github.com/tokio-rs/tracing/pull/1605
+
+# 0.1.16 (September 13, 2021)
+
+This release adds a new `#[instrument(skip_all)]` option to skip recording *all*
+arguments to an instrumented function as fields. Additionally, it adds support
+for recording arguments that are `tracing` primitive types as typed values,
+rather than as `fmt::Debug`.
+
+### Added
+
+- add `skip_all` option to `#[instrument]` ([#1548])
+- record primitive types as primitive values rather than as `fmt::Debug`
+  ([#1378])
+- added support for `f64`s as typed values ([#1522])
+
+Thanks to @Folyd and @jsgf for contributing to this release!
+
+[#1548]: https://github.com/tokio-rs/tracing/pull/1548
+[#1378]: https://github.com/tokio-rs/tracing/pull/1378
+[#1522]: https://github.com/tokio-rs/tracing/pull/1524
+
+# 0.1.15 (March 12, 2021)
+
+### Fixed
+
+- `#[instrument]` on functions returning `Box::pin`ned futures incorrectly
+  skipping function bodies prior to returning a future ([#1297])
+
+Thanks to @nightmared for contributing to this release!
+
+[#1297]: https://github.com/tokio-rs/tracing/pull/1297
+
+# 0.1.14 (March 10, 2021)
+
+### Fixed
+
+- Compatibility between `#[instrument]` and `async-trait` v0.1.43 and newer
+  ([#1228])
+
+Thanks to @nightmared for lots of hard work on this fix!
+
+[#1228]: https://github.com/tokio-rs/tracing/pull/1228
+
+# 0.1.13 (February 17, 2021)
+
+### Fixed
+
+- Compiler error when using `#[instrument(err)]` on functions which return `impl
+  Trait` ([#1236])
+
+[#1236]: https://github.com/tokio-rs/tracing/pull/1236
+
+# 0.1.12 (February 4, 2021)
+
+### Fixed
+
+- Compiler error when using `#[instrument(err)]` on functions with mutable
+  parameters ([#1167])
+- Missing function visibility modifier when using `#[instrument]` with
+  `async-trait` ([#977])
+- Multiple documentation fixes and improvements ([#965], [#981], [#1215])
+
+### Changed
+
+- `tracing-futures` dependency is no longer required when using `#[instrument]`
+  on async functions ([#808])
+
+Thanks to @nagisa, @Txuritan, @TaKO8Ki, and @okready for contributing to this
+release!
+
+[#1167]: https://github.com/tokio-rs/tracing/pull/1167
+[#977]: https://github.com/tokio-rs/tracing/pull/977
+[#965]: https://github.com/tokio-rs/tracing/pull/965
+[#981]: https://github.com/tokio-rs/tracing/pull/981
+[#1215]: https://github.com/tokio-rs/tracing/pull/1215
+[#808]: https://github.com/tokio-rs/tracing/pull/808
+
+# 0.1.11 (August 18, 2020)
+
+### Fixed
+
+- Corrected wrong minimum supported Rust version note in docs (#941)
+- Removed unused `syn` features (#928)
+
+Thanks to new contributor @jhpratt for contributing to this release!
+
+# 0.1.10 (August 10, 2020)
+
+### Added
+
+- Support for using `self` in field expressions when instrumenting `async-trait`
+  functions (#875)
+- Several documentation improvements (#832, #897, #911, #913)
+
+Thanks to @anton-dutov and @nightmared for contributing to this release!
+
+# 0.1.9 (July 8, 2020)
+
+### Added
+
+- Support for arbitrary expressions as fields in `#[instrument]` (#672)
+
+### Changed
+
+- `#[instrument]` now emits a compiler warning when ignoring unrecognized
+  input (#672, #786)
+
+# 0.1.8 (May 13, 2020)
+
+### Added
+
+- Support for using `#[instrument]` on methods that are part of [`async-trait`]
+  trait implementations (#711)
+- Optional `#[instrument(err)]` argument to automatically emit an event if an
+  instrumented function returns `Err` (#637) 
+
+Thanks to @ilana and @nightmared for contributing to this release!
+
+[`async-trait`]: https://crates.io/crates/async-trait
+
+# 0.1.7 (February 26, 2020)
+
+### Added
+
+- Support for adding arbitrary literal fields to spans generated by
+  `#[instrument]` (#569)
+- `#[instrument]` now emits a helpful compiler error when attempting to skip a
+  function parameter (#600)
+
+Thanks to @Kobzol for contributing to this release!
+
+# 0.1.6 (December 20, 2019)
+
+### Added
+
+-  Updated documentation (#468)
+
+# 0.1.5 (October 22, 2019)
+
+### Added
+
+- Support for destructuring in arguments to `#[instrument]`ed functions (#397)
+- Generated field for `self` parameters when `#[instrument]`ing methods (#397)
+
+# 0.1.4 (September 26, 2019)
+
+### Added
+
+- Optional `skip` argument to `#[instrument]` for excluding function parameters
+  from generated spans (#359)
+
+# 0.1.3 (September 12, 2019)
+
+### Fixed
+
+- Fixed `#[instrument]`ed async functions not compiling on `nightly-2019-09-11`
+  or newer (#342)
+
+# 0.1.2 (August 19, 2019)
+
+### Changed
+
+- Updated `syn` and `quote` dependencies to 1.0 (#292)
+- Removed direct dependency on `proc-macro2` to avoid potential version
+  conflicts (#296)
+
+### Fixed
+
+- Outdated idioms in examples (#271, #273)
+
+# 0.1.1 (August 9, 2019)
+
+### Changed
+
+- Using the `#[instrument]` attribute on `async fn`s no longer requires a
+  feature flag (#258)
+
+### Fixed
+
+- The `#[instrument]` macro now works on generic functions (#262)
+
+# 0.1.0 (August 8, 2019)
+
+- Initial release
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..5639de0
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,91 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+rust-version = "1.49.0"
+name = "tracing-attributes"
+version = "0.1.23"
+authors = [
+    "Tokio Contributors <team@tokio.rs>",
+    "Eliza Weisman <eliza@buoyant.io>",
+    "David Barsky <dbarsky@amazon.com>",
+]
+description = """
+Procedural macro attributes for automatically instrumenting functions.
+"""
+homepage = "https://tokio.rs"
+readme = "README.md"
+keywords = [
+    "logging",
+    "tracing",
+    "macro",
+    "instrument",
+    "log",
+]
+categories = [
+    "development-tools::debugging",
+    "development-tools::profiling",
+    "asynchronous",
+]
+license = "MIT"
+repository = "https://github.com/tokio-rs/tracing"
+
+[lib]
+proc-macro = true
+
+[dependencies.proc-macro2]
+version = "1"
+
+[dependencies.quote]
+version = "1"
+
+[dependencies.syn]
+version = "1.0.98"
+features = [
+    "full",
+    "parsing",
+    "printing",
+    "visit",
+    "visit-mut",
+    "clone-impls",
+    "extra-traits",
+    "proc-macro",
+]
+default-features = false
+
+[dev-dependencies.async-trait]
+version = "0.1.56"
+
+[dev-dependencies.rustversion]
+version = "1.0.9"
+
+[dev-dependencies.tokio-test]
+version = "0.3.0"
+
+[dev-dependencies.tracing]
+version = "0.1.35"
+
+[dev-dependencies.tracing-core]
+version = "0.1.28"
+
+[dev-dependencies.tracing-subscriber]
+version = "0.3.0"
+features = ["env-filter"]
+
+[dev-dependencies.trybuild]
+version = "1.0.64"
+
+[features]
+async-await = []
+
+[badges.maintenance]
+status = "experimental"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..d6e3adb
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,57 @@
+[package]
+name = "tracing-attributes"
+# When releasing to crates.io:
+# - Remove path dependencies
+# - Update html_root_url.
+# - Update doc url
+#   - Cargo.toml
+#   - README.md
+# - Update CHANGELOG.md.
+# - Create "v0.1.x" git tag.
+version = "0.1.23"
+authors = [
+    "Tokio Contributors <team@tokio.rs>",
+    "Eliza Weisman <eliza@buoyant.io>",
+    "David Barsky <dbarsky@amazon.com>",
+]
+repository = "https://github.com/tokio-rs/tracing"
+homepage = "https://tokio.rs"
+description = """
+Procedural macro attributes for automatically instrumenting functions.
+"""
+categories = [
+    "development-tools::debugging",
+    "development-tools::profiling",
+    "asynchronous",
+]
+keywords = ["logging", "tracing", "macro", "instrument", "log"]
+license = "MIT"
+readme = "README.md"
+edition = "2018"
+rust-version = "1.49.0"
+
+[lib]
+proc-macro = true
+
+[features]
+
+# This feature flag is no longer necessary.
+async-await = []
+
+[dependencies]
+proc-macro2 = "1"
+syn = { version = "1.0.98", default-features = false, features = ["full", "parsing", "printing", "visit", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] }
+quote = "1"
+
+[dev-dependencies]
+tracing = { path = "../tracing", version = "0.1.35" }
+tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] }
+tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["env-filter"] }
+tokio-test = { version = "0.3.0" }
+tracing-core = { path = "../tracing-core", version = "0.1.28"}
+async-trait = "0.1.56"
+trybuild = "1.0.64"
+rustversion = "1.0.9"
+
+[badges]
+maintenance = { status = "experimental" }
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cdb28b4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2019 Tokio Contributors
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..ff17979
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "tracing-attributes"
+description: "Macro attributes for application-level tracing."
+third_party {
+  url {
+    type: HOMEPAGE
+    value: "https://crates.io/crates/tracing-attributes"
+  }
+  url {
+    type: ARCHIVE
+    value: "https://static.crates.io/crates/tracing-attributes/tracing-attributes-0.1.23.crate"
+  }
+  version: "0.1.23"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 3
+    day: 3
+  }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..356b511
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+![Tracing — Structured, application-level diagnostics][splash]
+
+[splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg
+
+# tracing-attributes
+
+Macro attributes for application-level tracing.
+
+[![Crates.io][crates-badge]][crates-url]
+[![Documentation][docs-badge]][docs-url]
+[![Documentation (master)][docs-master-badge]][docs-master-url]
+[![MIT licensed][mit-badge]][mit-url]
+[![Build Status][actions-badge]][actions-url]
+[![Discord chat][discord-badge]][discord-url]
+
+[Documentation][docs-url] | [Chat][discord-url]
+
+[crates-badge]: https://img.shields.io/crates/v/tracing-attributes.svg
+[crates-url]: https://crates.io/crates/tracing-attributes
+[docs-badge]: https://docs.rs/tracing-attributes/badge.svg
+[docs-url]: https://docs.rs/tracing-attributes/0.1.23
+[docs-master-badge]: https://img.shields.io/badge/docs-master-blue
+[docs-master-url]: https://tracing-rs.netlify.com/tracing_attributes
+[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
+[mit-url]: LICENSE
+[actions-badge]: https://github.com/tokio-rs/tracing/workflows/CI/badge.svg
+[actions-url]:https://github.com/tokio-rs/tracing/actions?query=workflow%3ACI
+[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white
+[discord-url]: https://discord.gg/EeF3cQw
+
+## Overview
+
+[`tracing`] is a framework for instrumenting Rust programs to collect
+structured, event-based diagnostic information. This crate provides the
+`#[instrument]` attribute for automatically instrumenting functions using
+`tracing`.
+
+Note that this macro is also re-exported by the main `tracing` crate.
+
+*Compiler support: [requires `rustc` 1.49+][msrv]*
+
+[msrv]: #supported-rust-versions
+
+## Usage
+
+First, add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+tracing-attributes = "0.1.23"
+```
+
+
+This crate provides the `#[instrument]` attribute for instrumenting a function
+with a `tracing` [span]. For example:
+
+```rust
+use tracing_attributes::instrument;
+
+#[instrument]
+pub fn my_function(my_arg: usize) {
+    // ...
+}
+```
+
+[`tracing`]: https://crates.io/crates/tracing
+[span]: https://docs.rs/tracing/latest/tracing/span/index.html
+
+## Supported Rust Versions
+
+Tracing is built against the latest stable release. The minimum supported
+version is 1.49. The current Tracing version is not guaranteed to build on Rust
+versions earlier than the minimum supported version.
+
+Tracing follows the same compiler support policies as the rest of the Tokio
+project. The current stable Rust compiler and the three most recent minor
+versions before it will always be supported. For example, if the current stable
+compiler version is 1.45, the minimum supported version will not be increased
+past 1.42, three minor versions prior. Increasing the minimum supported compiler
+version is not considered a semver breaking change as long as doing so complies
+with this policy.
+
+## License
+
+This project is licensed under the [MIT license](LICENSE).
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in Tokio by you, shall be licensed as MIT, without any additional
+terms or conditions.
diff --git a/src/attr.rs b/src/attr.rs
new file mode 100644
index 0000000..ff875e1
--- /dev/null
+++ b/src/attr.rs
@@ -0,0 +1,413 @@
+use std::collections::HashSet;
+use syn::{punctuated::Punctuated, Expr, Ident, LitInt, LitStr, Path, Token};
+
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::ext::IdentExt as _;
+use syn::parse::{Parse, ParseStream};
+
+#[derive(Clone, Default, Debug)]
+pub(crate) struct InstrumentArgs {
+    level: Option<Level>,
+    pub(crate) name: Option<LitStr>,
+    target: Option<LitStr>,
+    pub(crate) parent: Option<Expr>,
+    pub(crate) follows_from: Option<Expr>,
+    pub(crate) skips: HashSet<Ident>,
+    pub(crate) skip_all: bool,
+    pub(crate) fields: Option<Fields>,
+    pub(crate) err_mode: Option<FormatMode>,
+    pub(crate) ret_mode: Option<FormatMode>,
+    /// Errors describing any unrecognized parse inputs that we skipped.
+    parse_warnings: Vec<syn::Error>,
+}
+
+impl InstrumentArgs {
+    pub(crate) fn level(&self) -> impl ToTokens {
+        fn is_level(lit: &LitInt, expected: u64) -> bool {
+            match lit.base10_parse::<u64>() {
+                Ok(value) => value == expected,
+                Err(_) => false,
+            }
+        }
+
+        match &self.level {
+            Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => {
+                quote!(tracing::Level::TRACE)
+            }
+            Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => {
+                quote!(tracing::Level::DEBUG)
+            }
+            Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => {
+                quote!(tracing::Level::INFO)
+            }
+            Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => {
+                quote!(tracing::Level::WARN)
+            }
+            Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => {
+                quote!(tracing::Level::ERROR)
+            }
+            Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE),
+            Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG),
+            Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO),
+            Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN),
+            Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR),
+            Some(Level::Path(ref pat)) => quote!(#pat),
+            Some(_) => quote! {
+                compile_error!(
+                    "unknown verbosity level, expected one of \"trace\", \
+                     \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5"
+                )
+            },
+            None => quote!(tracing::Level::INFO),
+        }
+    }
+
+    pub(crate) fn target(&self) -> impl ToTokens {
+        if let Some(ref target) = self.target {
+            quote!(#target)
+        } else {
+            quote!(module_path!())
+        }
+    }
+
+    /// Generate "deprecation" warnings for any unrecognized attribute inputs
+    /// that we skipped.
+    ///
+    /// For backwards compatibility, we need to emit compiler warnings rather
+    /// than errors for unrecognized inputs. Generating a fake deprecation is
+    /// the only way to do this on stable Rust right now.
+    pub(crate) fn warnings(&self) -> impl ToTokens {
+        let warnings = self.parse_warnings.iter().map(|err| {
+            let msg = format!("found unrecognized input, {}", err);
+            let msg = LitStr::new(&msg, err.span());
+            // TODO(eliza): This is a bit of a hack, but it's just about the
+            // only way to emit warnings from a proc macro on stable Rust.
+            // Eventually, when the `proc_macro::Diagnostic` API stabilizes, we
+            // should definitely use that instead.
+            quote_spanned! {err.span()=>
+                #[warn(deprecated)]
+                {
+                    #[deprecated(since = "not actually deprecated", note = #msg)]
+                    const TRACING_INSTRUMENT_WARNING: () = ();
+                    let _ = TRACING_INSTRUMENT_WARNING;
+                }
+            }
+        });
+        quote! {
+            { #(#warnings)* }
+        }
+    }
+}
+
+impl Parse for InstrumentArgs {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut args = Self::default();
+        while !input.is_empty() {
+            let lookahead = input.lookahead1();
+            if lookahead.peek(kw::name) {
+                if args.name.is_some() {
+                    return Err(input.error("expected only a single `name` argument"));
+                }
+                let name = input.parse::<StrArg<kw::name>>()?.value;
+                args.name = Some(name);
+            } else if lookahead.peek(LitStr) {
+                // XXX: apparently we support names as either named args with an
+                // sign, _or_ as unnamed string literals. That's weird, but
+                // changing it is apparently breaking.
+                if args.name.is_some() {
+                    return Err(input.error("expected only a single `name` argument"));
+                }
+                args.name = Some(input.parse()?);
+            } else if lookahead.peek(kw::target) {
+                if args.target.is_some() {
+                    return Err(input.error("expected only a single `target` argument"));
+                }
+                let target = input.parse::<StrArg<kw::target>>()?.value;
+                args.target = Some(target);
+            } else if lookahead.peek(kw::parent) {
+                if args.target.is_some() {
+                    return Err(input.error("expected only a single `parent` argument"));
+                }
+                let parent = input.parse::<ExprArg<kw::parent>>()?;
+                args.parent = Some(parent.value);
+            } else if lookahead.peek(kw::follows_from) {
+                if args.target.is_some() {
+                    return Err(input.error("expected only a single `follows_from` argument"));
+                }
+                let follows_from = input.parse::<ExprArg<kw::follows_from>>()?;
+                args.follows_from = Some(follows_from.value);
+            } else if lookahead.peek(kw::level) {
+                if args.level.is_some() {
+                    return Err(input.error("expected only a single `level` argument"));
+                }
+                args.level = Some(input.parse()?);
+            } else if lookahead.peek(kw::skip) {
+                if !args.skips.is_empty() {
+                    return Err(input.error("expected only a single `skip` argument"));
+                }
+                if args.skip_all {
+                    return Err(input.error("expected either `skip` or `skip_all` argument"));
+                }
+                let Skips(skips) = input.parse()?;
+                args.skips = skips;
+            } else if lookahead.peek(kw::skip_all) {
+                if args.skip_all {
+                    return Err(input.error("expected only a single `skip_all` argument"));
+                }
+                if !args.skips.is_empty() {
+                    return Err(input.error("expected either `skip` or `skip_all` argument"));
+                }
+                let _ = input.parse::<kw::skip_all>()?;
+                args.skip_all = true;
+            } else if lookahead.peek(kw::fields) {
+                if args.fields.is_some() {
+                    return Err(input.error("expected only a single `fields` argument"));
+                }
+                args.fields = Some(input.parse()?);
+            } else if lookahead.peek(kw::err) {
+                let _ = input.parse::<kw::err>();
+                let mode = FormatMode::parse(input)?;
+                args.err_mode = Some(mode);
+            } else if lookahead.peek(kw::ret) {
+                let _ = input.parse::<kw::ret>()?;
+                let mode = FormatMode::parse(input)?;
+                args.ret_mode = Some(mode);
+            } else if lookahead.peek(Token![,]) {
+                let _ = input.parse::<Token![,]>()?;
+            } else {
+                // We found a token that we didn't expect!
+                // We want to emit warnings for these, rather than errors, so
+                // we'll add it to the list of unrecognized inputs we've seen so
+                // far and keep going.
+                args.parse_warnings.push(lookahead.error());
+                // Parse the unrecognized token tree to advance the parse
+                // stream, and throw it away so we can keep parsing.
+                let _ = input.parse::<proc_macro2::TokenTree>();
+            }
+        }
+        Ok(args)
+    }
+}
+
+struct StrArg<T> {
+    value: LitStr,
+    _p: std::marker::PhantomData<T>,
+}
+
+impl<T: Parse> Parse for StrArg<T> {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let _ = input.parse::<T>()?;
+        let _ = input.parse::<Token![=]>()?;
+        let value = input.parse()?;
+        Ok(Self {
+            value,
+            _p: std::marker::PhantomData,
+        })
+    }
+}
+
+struct ExprArg<T> {
+    value: Expr,
+    _p: std::marker::PhantomData<T>,
+}
+
+impl<T: Parse> Parse for ExprArg<T> {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let _ = input.parse::<T>()?;
+        let _ = input.parse::<Token![=]>()?;
+        let value = input.parse()?;
+        Ok(Self {
+            value,
+            _p: std::marker::PhantomData,
+        })
+    }
+}
+
+struct Skips(HashSet<Ident>);
+
+impl Parse for Skips {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let _ = input.parse::<kw::skip>();
+        let content;
+        let _ = syn::parenthesized!(content in input);
+        let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?;
+        let mut skips = HashSet::new();
+        for name in names {
+            if skips.contains(&name) {
+                return Err(syn::Error::new(
+                    name.span(),
+                    "tried to skip the same field twice",
+                ));
+            } else {
+                skips.insert(name);
+            }
+        }
+        Ok(Self(skips))
+    }
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub(crate) enum FormatMode {
+    Default,
+    Display,
+    Debug,
+}
+
+impl Default for FormatMode {
+    fn default() -> Self {
+        FormatMode::Default
+    }
+}
+
+impl Parse for FormatMode {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        if !input.peek(syn::token::Paren) {
+            return Ok(FormatMode::default());
+        }
+        let content;
+        let _ = syn::parenthesized!(content in input);
+        let maybe_mode: Option<Ident> = content.parse()?;
+        maybe_mode.map_or(Ok(FormatMode::default()), |ident| {
+            match ident.to_string().as_str() {
+                "Debug" => Ok(FormatMode::Debug),
+                "Display" => Ok(FormatMode::Display),
+                _ => Err(syn::Error::new(
+                    ident.span(),
+                    "unknown error mode, must be Debug or Display",
+                )),
+            }
+        })
+    }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Fields(pub(crate) Punctuated<Field, Token![,]>);
+
+#[derive(Clone, Debug)]
+pub(crate) struct Field {
+    pub(crate) name: Punctuated<Ident, Token![.]>,
+    pub(crate) value: Option<Expr>,
+    pub(crate) kind: FieldKind,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) enum FieldKind {
+    Debug,
+    Display,
+    Value,
+}
+
+impl Parse for Fields {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let _ = input.parse::<kw::fields>();
+        let content;
+        let _ = syn::parenthesized!(content in input);
+        let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?;
+        Ok(Self(fields))
+    }
+}
+
+impl ToTokens for Fields {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        self.0.to_tokens(tokens)
+    }
+}
+
+impl Parse for Field {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let mut kind = FieldKind::Value;
+        if input.peek(Token![%]) {
+            input.parse::<Token![%]>()?;
+            kind = FieldKind::Display;
+        } else if input.peek(Token![?]) {
+            input.parse::<Token![?]>()?;
+            kind = FieldKind::Debug;
+        };
+        let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?;
+        let value = if input.peek(Token![=]) {
+            input.parse::<Token![=]>()?;
+            if input.peek(Token![%]) {
+                input.parse::<Token![%]>()?;
+                kind = FieldKind::Display;
+            } else if input.peek(Token![?]) {
+                input.parse::<Token![?]>()?;
+                kind = FieldKind::Debug;
+            };
+            Some(input.parse()?)
+        } else {
+            None
+        };
+        Ok(Self { name, value, kind })
+    }
+}
+
+impl ToTokens for Field {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some(ref value) = self.value {
+            let name = &self.name;
+            let kind = &self.kind;
+            tokens.extend(quote! {
+                #name = #kind#value
+            })
+        } else if self.kind == FieldKind::Value {
+            // XXX(eliza): I don't like that fields without values produce
+            // empty fields rather than local variable shorthand...but,
+            // we've released a version where field names without values in
+            // `instrument` produce empty field values, so changing it now
+            // is a breaking change. agh.
+            let name = &self.name;
+            tokens.extend(quote!(#name = tracing::field::Empty))
+        } else {
+            self.kind.to_tokens(tokens);
+            self.name.to_tokens(tokens);
+        }
+    }
+}
+
+impl ToTokens for FieldKind {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            FieldKind::Debug => tokens.extend(quote! { ? }),
+            FieldKind::Display => tokens.extend(quote! { % }),
+            _ => {}
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+enum Level {
+    Str(LitStr),
+    Int(LitInt),
+    Path(Path),
+}
+
+impl Parse for Level {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let _ = input.parse::<kw::level>()?;
+        let _ = input.parse::<Token![=]>()?;
+        let lookahead = input.lookahead1();
+        if lookahead.peek(LitStr) {
+            Ok(Self::Str(input.parse()?))
+        } else if lookahead.peek(LitInt) {
+            Ok(Self::Int(input.parse()?))
+        } else if lookahead.peek(Ident) {
+            Ok(Self::Path(input.parse()?))
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+mod kw {
+    syn::custom_keyword!(fields);
+    syn::custom_keyword!(skip);
+    syn::custom_keyword!(skip_all);
+    syn::custom_keyword!(level);
+    syn::custom_keyword!(target);
+    syn::custom_keyword!(parent);
+    syn::custom_keyword!(follows_from);
+    syn::custom_keyword!(name);
+    syn::custom_keyword!(err);
+    syn::custom_keyword!(ret);
+}
diff --git a/src/expand.rs b/src/expand.rs
new file mode 100644
index 0000000..7005b44
--- /dev/null
+++ b/src/expand.rs
@@ -0,0 +1,814 @@
+use std::iter;
+
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::visit_mut::VisitMut;
+use syn::{
+    punctuated::Punctuated, spanned::Spanned, Block, Expr, ExprAsync, ExprCall, FieldPat, FnArg,
+    Ident, Item, ItemFn, Pat, PatIdent, PatReference, PatStruct, PatTuple, PatTupleStruct, PatType,
+    Path, ReturnType, Signature, Stmt, Token, Type, TypePath,
+};
+
+use crate::{
+    attr::{Field, Fields, FormatMode, InstrumentArgs},
+    MaybeItemFn, MaybeItemFnRef,
+};
+
+/// Given an existing function, generate an instrumented version of that function
+pub(crate) fn gen_function<'a, B: ToTokens + 'a>(
+    input: MaybeItemFnRef<'a, B>,
+    args: InstrumentArgs,
+    instrumented_function_name: &str,
+    self_type: Option<&TypePath>,
+) -> proc_macro2::TokenStream {
+    // these are needed ahead of time, as ItemFn contains the function body _and_
+    // isn't representable inside a quote!/quote_spanned! macro
+    // (Syn's ToTokens isn't implemented for ItemFn)
+    let MaybeItemFnRef {
+        outer_attrs,
+        inner_attrs,
+        vis,
+        sig,
+        block,
+    } = input;
+
+    let Signature {
+        output,
+        inputs: params,
+        unsafety,
+        asyncness,
+        constness,
+        abi,
+        ident,
+        generics:
+            syn::Generics {
+                params: gen_params,
+                where_clause,
+                ..
+            },
+        ..
+    } = sig;
+
+    let warnings = args.warnings();
+
+    let (return_type, return_span) = if let ReturnType::Type(_, return_type) = &output {
+        (erase_impl_trait(return_type), return_type.span())
+    } else {
+        // Point at function name if we don't have an explicit return type
+        (syn::parse_quote! { () }, ident.span())
+    };
+    // Install a fake return statement as the first thing in the function
+    // body, so that we eagerly infer that the return type is what we
+    // declared in the async fn signature.
+    // The `#[allow(..)]` is given because the return statement is
+    // unreachable, but does affect inference, so it needs to be written
+    // exactly that way for it to do its magic.
+    let fake_return_edge = quote_spanned! {return_span=>
+        #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value)]
+        if false {
+            let __tracing_attr_fake_return: #return_type =
+                unreachable!("this is just for type inference, and is unreachable code");
+            return __tracing_attr_fake_return;
+        }
+    };
+    let block = quote! {
+        {
+            #fake_return_edge
+            #block
+        }
+    };
+
+    let body = gen_block(
+        &block,
+        params,
+        asyncness.is_some(),
+        args,
+        instrumented_function_name,
+        self_type,
+    );
+
+    quote!(
+        #(#outer_attrs) *
+        #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #output
+        #where_clause
+        {
+            #(#inner_attrs) *
+            #warnings
+            #body
+        }
+    )
+}
+
+/// Instrument a block
+fn gen_block<B: ToTokens>(
+    block: &B,
+    params: &Punctuated<FnArg, Token![,]>,
+    async_context: bool,
+    mut args: InstrumentArgs,
+    instrumented_function_name: &str,
+    self_type: Option<&TypePath>,
+) -> proc_macro2::TokenStream {
+    // generate the span's name
+    let span_name = args
+        // did the user override the span's name?
+        .name
+        .as_ref()
+        .map(|name| quote!(#name))
+        .unwrap_or_else(|| quote!(#instrumented_function_name));
+
+    let level = args.level();
+
+    let follows_from = args.follows_from.iter();
+    let follows_from = quote! {
+        #(for cause in #follows_from {
+            __tracing_attr_span.follows_from(cause);
+        })*
+    };
+
+    // generate this inside a closure, so we can return early on errors.
+    let span = (|| {
+        // Pull out the arguments-to-be-skipped first, so we can filter results
+        // below.
+        let param_names: Vec<(Ident, (Ident, RecordType))> = params
+            .clone()
+            .into_iter()
+            .flat_map(|param| match param {
+                FnArg::Typed(PatType { pat, ty, .. }) => {
+                    param_names(*pat, RecordType::parse_from_ty(&*ty))
+                }
+                FnArg::Receiver(_) => Box::new(iter::once((
+                    Ident::new("self", param.span()),
+                    RecordType::Debug,
+                ))),
+            })
+            // Little dance with new (user-exposed) names and old (internal)
+            // names of identifiers. That way, we could do the following
+            // even though async_trait (<=0.1.43) rewrites "self" as "_self":
+            // ```
+            // #[async_trait]
+            // impl Foo for FooImpl {
+            //     #[instrument(skip(self))]
+            //     async fn foo(&self, v: usize) {}
+            // }
+            // ```
+            .map(|(x, record_type)| {
+                // if we are inside a function generated by async-trait <=0.1.43, we need to
+                // take care to rewrite "_self" as "self" for 'user convenience'
+                if self_type.is_some() && x == "_self" {
+                    (Ident::new("self", x.span()), (x, record_type))
+                } else {
+                    (x.clone(), (x, record_type))
+                }
+            })
+            .collect();
+
+        for skip in &args.skips {
+            if !param_names.iter().map(|(user, _)| user).any(|y| y == skip) {
+                return quote_spanned! {skip.span()=>
+                    compile_error!("attempting to skip non-existent parameter")
+                };
+            }
+        }
+
+        let target = args.target();
+
+        let parent = args.parent.iter();
+
+        // filter out skipped fields
+        let quoted_fields: Vec<_> = param_names
+            .iter()
+            .filter(|(param, _)| {
+                if args.skip_all || args.skips.contains(param) {
+                    return false;
+                }
+
+                // If any parameters have the same name as a custom field, skip
+                // and allow them to be formatted by the custom field.
+                if let Some(ref fields) = args.fields {
+                    fields.0.iter().all(|Field { ref name, .. }| {
+                        let first = name.first();
+                        first != name.last() || !first.iter().any(|name| name == &param)
+                    })
+                } else {
+                    true
+                }
+            })
+            .map(|(user_name, (real_name, record_type))| match record_type {
+                RecordType::Value => quote!(#user_name = #real_name),
+                RecordType::Debug => quote!(#user_name = tracing::field::debug(&#real_name)),
+            })
+            .collect();
+
+        // replace every use of a variable with its original name
+        if let Some(Fields(ref mut fields)) = args.fields {
+            let mut replacer = IdentAndTypesRenamer {
+                idents: param_names.into_iter().map(|(a, (b, _))| (a, b)).collect(),
+                types: Vec::new(),
+            };
+
+            // when async-trait <=0.1.43 is in use, replace instances
+            // of the "Self" type inside the fields values
+            if let Some(self_type) = self_type {
+                replacer.types.push(("Self", self_type.clone()));
+            }
+
+            for e in fields.iter_mut().filter_map(|f| f.value.as_mut()) {
+                syn::visit_mut::visit_expr_mut(&mut replacer, e);
+            }
+        }
+
+        let custom_fields = &args.fields;
+
+        quote!(tracing::span!(
+            target: #target,
+            #(parent: #parent,)*
+            #level,
+            #span_name,
+            #(#quoted_fields,)*
+            #custom_fields
+
+        ))
+    })();
+
+    let target = args.target();
+
+    let err_event = match args.err_mode {
+        Some(FormatMode::Default) | Some(FormatMode::Display) => {
+            Some(quote!(tracing::error!(target: #target, error = %e)))
+        }
+        Some(FormatMode::Debug) => Some(quote!(tracing::error!(target: #target, error = ?e))),
+        _ => None,
+    };
+
+    let ret_event = match args.ret_mode {
+        Some(FormatMode::Display) => Some(quote!(
+            tracing::event!(target: #target, #level, return = %x)
+        )),
+        Some(FormatMode::Default) | Some(FormatMode::Debug) => Some(quote!(
+            tracing::event!(target: #target, #level, return = ?x)
+        )),
+        _ => None,
+    };
+
+    // Generate the instrumented function body.
+    // If the function is an `async fn`, this will wrap it in an async block,
+    // which is `instrument`ed using `tracing-futures`. Otherwise, this will
+    // enter the span and then perform the rest of the body.
+    // If `err` is in args, instrument any resulting `Err`s.
+    // If `ret` is in args, instrument any resulting `Ok`s when the function
+    // returns `Result`s, otherwise instrument any resulting values.
+    if async_context {
+        let mk_fut = match (err_event, ret_event) {
+            (Some(err_event), Some(ret_event)) => quote_spanned!(block.span()=>
+                async move {
+                    match async move #block.await {
+                        #[allow(clippy::unit_arg)]
+                        Ok(x) => {
+                            #ret_event;
+                            Ok(x)
+                        },
+                        Err(e) => {
+                            #err_event;
+                            Err(e)
+                        }
+                    }
+                }
+            ),
+            (Some(err_event), None) => quote_spanned!(block.span()=>
+                async move {
+                    match async move #block.await {
+                        #[allow(clippy::unit_arg)]
+                        Ok(x) => Ok(x),
+                        Err(e) => {
+                            #err_event;
+                            Err(e)
+                        }
+                    }
+                }
+            ),
+            (None, Some(ret_event)) => quote_spanned!(block.span()=>
+                async move {
+                    let x = async move #block.await;
+                    #ret_event;
+                    x
+                }
+            ),
+            (None, None) => quote_spanned!(block.span()=>
+                async move #block
+            ),
+        };
+
+        return quote!(
+            let __tracing_attr_span = #span;
+            let __tracing_instrument_future = #mk_fut;
+            if !__tracing_attr_span.is_disabled() {
+                #follows_from
+                tracing::Instrument::instrument(
+                    __tracing_instrument_future,
+                    __tracing_attr_span
+                )
+                .await
+            } else {
+                __tracing_instrument_future.await
+            }
+        );
+    }
+
+    let span = quote!(
+        // These variables are left uninitialized and initialized only
+        // if the tracing level is statically enabled at this point.
+        // While the tracing level is also checked at span creation
+        // time, that will still create a dummy span, and a dummy guard
+        // and drop the dummy guard later. By lazily initializing these
+        // variables, Rust will generate a drop flag for them and thus
+        // only drop the guard if it was created. This creates code that
+        // is very straightforward for LLVM to optimize out if the tracing
+        // level is statically disabled, while not causing any performance
+        // regression in case the level is enabled.
+        let __tracing_attr_span;
+        let __tracing_attr_guard;
+        if tracing::level_enabled!(#level) {
+            __tracing_attr_span = #span;
+            #follows_from
+            __tracing_attr_guard = __tracing_attr_span.enter();
+        }
+    );
+
+    match (err_event, ret_event) {
+        (Some(err_event), Some(ret_event)) => quote_spanned! {block.span()=>
+            #span
+            #[allow(clippy::redundant_closure_call)]
+            match (move || #block)() {
+                #[allow(clippy::unit_arg)]
+                Ok(x) => {
+                    #ret_event;
+                    Ok(x)
+                },
+                Err(e) => {
+                    #err_event;
+                    Err(e)
+                }
+            }
+        },
+        (Some(err_event), None) => quote_spanned!(block.span()=>
+            #span
+            #[allow(clippy::redundant_closure_call)]
+            match (move || #block)() {
+                #[allow(clippy::unit_arg)]
+                Ok(x) => Ok(x),
+                Err(e) => {
+                    #err_event;
+                    Err(e)
+                }
+            }
+        ),
+        (None, Some(ret_event)) => quote_spanned!(block.span()=>
+            #span
+            #[allow(clippy::redundant_closure_call)]
+            let x = (move || #block)();
+            #ret_event;
+            x
+        ),
+        (None, None) => quote_spanned!(block.span() =>
+            // Because `quote` produces a stream of tokens _without_ whitespace, the
+            // `if` and the block will appear directly next to each other. This
+            // generates a clippy lint about suspicious `if/else` formatting.
+            // Therefore, suppress the lint inside the generated code...
+            #[allow(clippy::suspicious_else_formatting)]
+            {
+                #span
+                // ...but turn the lint back on inside the function body.
+                #[warn(clippy::suspicious_else_formatting)]
+                #block
+            }
+        ),
+    }
+}
+
+/// Indicates whether a field should be recorded as `Value` or `Debug`.
+enum RecordType {
+    /// The field should be recorded using its `Value` implementation.
+    Value,
+    /// The field should be recorded using `tracing::field::debug()`.
+    Debug,
+}
+
+impl RecordType {
+    /// Array of primitive types which should be recorded as [RecordType::Value].
+    const TYPES_FOR_VALUE: &'static [&'static str] = &[
+        "bool",
+        "str",
+        "u8",
+        "i8",
+        "u16",
+        "i16",
+        "u32",
+        "i32",
+        "u64",
+        "i64",
+        "f32",
+        "f64",
+        "usize",
+        "isize",
+        "NonZeroU8",
+        "NonZeroI8",
+        "NonZeroU16",
+        "NonZeroI16",
+        "NonZeroU32",
+        "NonZeroI32",
+        "NonZeroU64",
+        "NonZeroI64",
+        "NonZeroUsize",
+        "NonZeroIsize",
+        "Wrapping",
+    ];
+
+    /// Parse `RecordType` from [Type] by looking up
+    /// the [RecordType::TYPES_FOR_VALUE] array.
+    fn parse_from_ty(ty: &Type) -> Self {
+        match ty {
+            Type::Path(TypePath { path, .. })
+                if path
+                    .segments
+                    .iter()
+                    .last()
+                    .map(|path_segment| {
+                        let ident = path_segment.ident.to_string();
+                        Self::TYPES_FOR_VALUE.iter().any(|&t| t == ident)
+                    })
+                    .unwrap_or(false) =>
+            {
+                RecordType::Value
+            }
+            Type::Reference(syn::TypeReference { elem, .. }) => RecordType::parse_from_ty(elem),
+            _ => RecordType::Debug,
+        }
+    }
+}
+
+fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Ident, RecordType)>> {
+    match pat {
+        Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once((ident, record_type))),
+        Pat::Reference(PatReference { pat, .. }) => param_names(*pat, record_type),
+        // We can't get the concrete type of fields in the struct/tuple
+        // patterns by using `syn`. e.g. `fn foo(Foo { x, y }: Foo) {}`.
+        // Therefore, the struct/tuple patterns in the arguments will just
+        // always be recorded as `RecordType::Debug`.
+        Pat::Struct(PatStruct { fields, .. }) => Box::new(
+            fields
+                .into_iter()
+                .flat_map(|FieldPat { pat, .. }| param_names(*pat, RecordType::Debug)),
+        ),
+        Pat::Tuple(PatTuple { elems, .. }) => Box::new(
+            elems
+                .into_iter()
+                .flat_map(|p| param_names(p, RecordType::Debug)),
+        ),
+        Pat::TupleStruct(PatTupleStruct {
+            pat: PatTuple { elems, .. },
+            ..
+        }) => Box::new(
+            elems
+                .into_iter()
+                .flat_map(|p| param_names(p, RecordType::Debug)),
+        ),
+
+        // The above *should* cover all cases of irrefutable patterns,
+        // but we purposefully don't do any funny business here
+        // (such as panicking) because that would obscure rustc's
+        // much more informative error message.
+        _ => Box::new(iter::empty()),
+    }
+}
+
+/// The specific async code pattern that was detected
+enum AsyncKind<'a> {
+    /// Immediately-invoked async fn, as generated by `async-trait <= 0.1.43`:
+    /// `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))`
+    Function(&'a ItemFn),
+    /// A function returning an async (move) block, optionally `Box::pin`-ed,
+    /// as generated by `async-trait >= 0.1.44`:
+    /// `Box::pin(async move { ... })`
+    Async {
+        async_expr: &'a ExprAsync,
+        pinned_box: bool,
+    },
+}
+
+pub(crate) struct AsyncInfo<'block> {
+    // statement that must be patched
+    source_stmt: &'block Stmt,
+    kind: AsyncKind<'block>,
+    self_type: Option<TypePath>,
+    input: &'block ItemFn,
+}
+
+impl<'block> AsyncInfo<'block> {
+    /// Get the AST of the inner function we need to hook, if it looks like a
+    /// manual future implementation.
+    ///
+    /// When we are given a function that returns a (pinned) future containing the
+    /// user logic, it is that (pinned) future that needs to be instrumented.
+    /// Were we to instrument its parent, we would only collect information
+    /// regarding the allocation of that future, and not its own span of execution.
+    ///
+    /// We inspect the block of the function to find if it matches any of the
+    /// following patterns:
+    ///
+    /// - Immediately-invoked async fn, as generated by `async-trait <= 0.1.43`:
+    ///   `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))`
+    ///
+    /// - A function returning an async (move) block, optionally `Box::pin`-ed,
+    ///   as generated by `async-trait >= 0.1.44`:
+    ///   `Box::pin(async move { ... })`
+    ///
+    /// We the return the statement that must be instrumented, along with some
+    /// other information.
+    /// 'gen_body' will then be able to use that information to instrument the
+    /// proper function/future.
+    ///
+    /// (this follows the approach suggested in
+    /// https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673)
+    pub(crate) fn from_fn(input: &'block ItemFn) -> Option<Self> {
+        // are we in an async context? If yes, this isn't a manual async-like pattern
+        if input.sig.asyncness.is_some() {
+            return None;
+        }
+
+        let block = &input.block;
+
+        // list of async functions declared inside the block
+        let inside_funs = block.stmts.iter().filter_map(|stmt| {
+            if let Stmt::Item(Item::Fn(fun)) = &stmt {
+                // If the function is async, this is a candidate
+                if fun.sig.asyncness.is_some() {
+                    return Some((stmt, fun));
+                }
+            }
+            None
+        });
+
+        // last expression of the block: it determines the return value of the
+        // block, this is quite likely a `Box::pin` statement or an async block
+        let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| {
+            if let Stmt::Expr(expr) = stmt {
+                Some((stmt, expr))
+            } else {
+                None
+            }
+        })?;
+
+        // is the last expression an async block?
+        if let Expr::Async(async_expr) = last_expr {
+            return Some(AsyncInfo {
+                source_stmt: last_expr_stmt,
+                kind: AsyncKind::Async {
+                    async_expr,
+                    pinned_box: false,
+                },
+                self_type: None,
+                input,
+            });
+        }
+
+        // is the last expression a function call?
+        let (outside_func, outside_args) = match last_expr {
+            Expr::Call(ExprCall { func, args, .. }) => (func, args),
+            _ => return None,
+        };
+
+        // is it a call to `Box::pin()`?
+        let path = match outside_func.as_ref() {
+            Expr::Path(path) => &path.path,
+            _ => return None,
+        };
+        if !path_to_string(path).ends_with("Box::pin") {
+            return None;
+        }
+
+        // Does the call take an argument? If it doesn't,
+        // it's not gonna compile anyway, but that's no reason
+        // to (try to) perform an out of bounds access
+        if outside_args.is_empty() {
+            return None;
+        }
+
+        // Is the argument to Box::pin an async block that
+        // captures its arguments?
+        if let Expr::Async(async_expr) = &outside_args[0] {
+            return Some(AsyncInfo {
+                source_stmt: last_expr_stmt,
+                kind: AsyncKind::Async {
+                    async_expr,
+                    pinned_box: true,
+                },
+                self_type: None,
+                input,
+            });
+        }
+
+        // Is the argument to Box::pin a function call itself?
+        let func = match &outside_args[0] {
+            Expr::Call(ExprCall { func, .. }) => func,
+            _ => return None,
+        };
+
+        // "stringify" the path of the function called
+        let func_name = match **func {
+            Expr::Path(ref func_path) => path_to_string(&func_path.path),
+            _ => return None,
+        };
+
+        // Was that function defined inside of the current block?
+        // If so, retrieve the statement where it was declared and the function itself
+        let (stmt_func_declaration, func) = inside_funs
+            .into_iter()
+            .find(|(_, fun)| fun.sig.ident == func_name)?;
+
+        // If "_self" is present as an argument, we store its type to be able to rewrite "Self" (the
+        // parameter type) with the type of "_self"
+        let mut self_type = None;
+        for arg in &func.sig.inputs {
+            if let FnArg::Typed(ty) = arg {
+                if let Pat::Ident(PatIdent { ref ident, .. }) = *ty.pat {
+                    if ident == "_self" {
+                        let mut ty = *ty.ty.clone();
+                        // extract the inner type if the argument is "&self" or "&mut self"
+                        if let Type::Reference(syn::TypeReference { elem, .. }) = ty {
+                            ty = *elem;
+                        }
+
+                        if let Type::Path(tp) = ty {
+                            self_type = Some(tp);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        Some(AsyncInfo {
+            source_stmt: stmt_func_declaration,
+            kind: AsyncKind::Function(func),
+            self_type,
+            input,
+        })
+    }
+
+    pub(crate) fn gen_async(
+        self,
+        args: InstrumentArgs,
+        instrumented_function_name: &str,
+    ) -> Result<proc_macro::TokenStream, syn::Error> {
+        // let's rewrite some statements!
+        let mut out_stmts: Vec<TokenStream> = self
+            .input
+            .block
+            .stmts
+            .iter()
+            .map(|stmt| stmt.to_token_stream())
+            .collect();
+
+        if let Some((iter, _stmt)) = self
+            .input
+            .block
+            .stmts
+            .iter()
+            .enumerate()
+            .find(|(_iter, stmt)| *stmt == self.source_stmt)
+        {
+            // instrument the future by rewriting the corresponding statement
+            out_stmts[iter] = match self.kind {
+                // `Box::pin(immediately_invoked_async_fn())`
+                AsyncKind::Function(fun) => {
+                    let fun = MaybeItemFn::from(fun.clone());
+                    gen_function(
+                        fun.as_ref(),
+                        args,
+                        instrumented_function_name,
+                        self.self_type.as_ref(),
+                    )
+                }
+                // `async move { ... }`, optionally pinned
+                AsyncKind::Async {
+                    async_expr,
+                    pinned_box,
+                } => {
+                    let instrumented_block = gen_block(
+                        &async_expr.block,
+                        &self.input.sig.inputs,
+                        true,
+                        args,
+                        instrumented_function_name,
+                        None,
+                    );
+                    let async_attrs = &async_expr.attrs;
+                    if pinned_box {
+                        quote! {
+                            Box::pin(#(#async_attrs) * async move { #instrumented_block })
+                        }
+                    } else {
+                        quote! {
+                            #(#async_attrs) * async move { #instrumented_block }
+                        }
+                    }
+                }
+            };
+        }
+
+        let vis = &self.input.vis;
+        let sig = &self.input.sig;
+        let attrs = &self.input.attrs;
+        Ok(quote!(
+            #(#attrs) *
+            #vis #sig {
+                #(#out_stmts) *
+            }
+        )
+        .into())
+    }
+}
+
+// Return a path as a String
+fn path_to_string(path: &Path) -> String {
+    use std::fmt::Write;
+    // some heuristic to prevent too many allocations
+    let mut res = String::with_capacity(path.segments.len() * 5);
+    for i in 0..path.segments.len() {
+        write!(&mut res, "{}", path.segments[i].ident)
+            .expect("writing to a String should never fail");
+        if i < path.segments.len() - 1 {
+            res.push_str("::");
+        }
+    }
+    res
+}
+
+/// A visitor struct to replace idents and types in some piece
+/// of code (e.g. the "self" and "Self" tokens in user-supplied
+/// fields expressions when the function is generated by an old
+/// version of async-trait).
+struct IdentAndTypesRenamer<'a> {
+    types: Vec<(&'a str, TypePath)>,
+    idents: Vec<(Ident, Ident)>,
+}
+
+impl<'a> VisitMut for IdentAndTypesRenamer<'a> {
+    // we deliberately compare strings because we want to ignore the spans
+    // If we apply clippy's lint, the behavior changes
+    #[allow(clippy::cmp_owned)]
+    fn visit_ident_mut(&mut self, id: &mut Ident) {
+        for (old_ident, new_ident) in &self.idents {
+            if id.to_string() == old_ident.to_string() {
+                *id = new_ident.clone();
+            }
+        }
+    }
+
+    fn visit_type_mut(&mut self, ty: &mut Type) {
+        for (type_name, new_type) in &self.types {
+            if let Type::Path(TypePath { path, .. }) = ty {
+                if path_to_string(path) == *type_name {
+                    *ty = Type::Path(new_type.clone());
+                }
+            }
+        }
+    }
+}
+
+// A visitor struct that replace an async block by its patched version
+struct AsyncTraitBlockReplacer<'a> {
+    block: &'a Block,
+    patched_block: Block,
+}
+
+impl<'a> VisitMut for AsyncTraitBlockReplacer<'a> {
+    fn visit_block_mut(&mut self, i: &mut Block) {
+        if i == self.block {
+            *i = self.patched_block.clone();
+        }
+    }
+}
+
+// Replaces any `impl Trait` with `_` so it can be used as the type in
+// a `let` statement's LHS.
+struct ImplTraitEraser;
+
+impl VisitMut for ImplTraitEraser {
+    fn visit_type_mut(&mut self, t: &mut Type) {
+        if let Type::ImplTrait(..) = t {
+            *t = syn::TypeInfer {
+                underscore_token: Token![_](t.span()),
+            }
+            .into();
+        } else {
+            syn::visit_mut::visit_type_mut(self, t);
+        }
+    }
+}
+
+fn erase_impl_trait(ty: &Type) -> Type {
+    let mut ty = ty.clone();
+    ImplTraitEraser.visit_type_mut(&mut ty);
+    ty
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..f5974e4
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,677 @@
+//! A procedural macro attribute for instrumenting functions with [`tracing`].
+//!
+//! [`tracing`] is a framework for instrumenting Rust programs to collect
+//! structured, event-based diagnostic information. This crate provides the
+//! [`#[instrument]`][instrument] procedural macro attribute.
+//!
+//! Note that this macro is also re-exported by the main `tracing` crate.
+//!
+//! *Compiler support: [requires `rustc` 1.49+][msrv]*
+//!
+//! [msrv]: #supported-rust-versions
+//!
+//! ## Usage
+//!
+//! First, add this to your `Cargo.toml`:
+//!
+//! ```toml
+//! [dependencies]
+//! tracing-attributes = "0.1.23"
+//! ```
+//!
+//! The [`#[instrument]`][instrument] attribute can now be added to a function
+//! to automatically create and enter `tracing` [span] when that function is
+//! called. For example:
+//!
+//! ```
+//! use tracing_attributes::instrument;
+//!
+//! #[instrument]
+//! pub fn my_function(my_arg: usize) {
+//!     // ...
+//! }
+//!
+//! # fn main() {}
+//! ```
+//!
+//! [`tracing`]: https://crates.io/crates/tracing
+//! [span]: https://docs.rs/tracing/latest/tracing/span/index.html
+//! [instrument]: macro@self::instrument
+//!
+//! ## Supported Rust Versions
+//!
+//! Tracing is built against the latest stable release. The minimum supported
+//! version is 1.49. The current Tracing version is not guaranteed to build on
+//! Rust versions earlier than the minimum supported version.
+//!
+//! Tracing follows the same compiler support policies as the rest of the Tokio
+//! project. The current stable Rust compiler and the three most recent minor
+//! versions before it will always be supported. For example, if the current
+//! stable compiler version is 1.45, the minimum supported version will not be
+//! increased past 1.42, three minor versions prior. Increasing the minimum
+//! supported compiler version is not considered a semver breaking change as
+//! long as doing so complies with this policy.
+//!
+#![doc(html_root_url = "https://docs.rs/tracing-attributes/0.1.23")]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png",
+    issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
+)]
+#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
+#![warn(
+    missing_debug_implementations,
+    missing_docs,
+    rust_2018_idioms,
+    unreachable_pub,
+    bad_style,
+    const_err,
+    dead_code,
+    improper_ctypes,
+    non_shorthand_field_patterns,
+    no_mangle_generic_items,
+    overflowing_literals,
+    path_statements,
+    patterns_in_fns_without_body,
+    private_in_public,
+    unconditional_recursion,
+    unused_allocation,
+    unused_comparisons,
+    unused_parens,
+    while_true
+)]
+// TODO: once `tracing` bumps its MSRV to 1.42, remove this allow.
+#![allow(unused)]
+extern crate proc_macro;
+
+use proc_macro2::TokenStream;
+use quote::ToTokens;
+use syn::parse::{Parse, ParseStream};
+use syn::{Attribute, ItemFn, Signature, Visibility};
+
+mod attr;
+mod expand;
+/// Instruments a function to create and enter a `tracing` [span] every time
+/// the function is called.
+///
+/// Unless overriden, a span with the [`INFO`] [level] will be generated.
+/// The generated span's name will be the name of the function.
+/// By default, all arguments to the function are included as fields on the
+/// span. Arguments that are `tracing` [primitive types] implementing the
+/// [`Value` trait] will be recorded as fields of that type. Types which do
+/// not implement `Value` will be recorded using [`std::fmt::Debug`].
+///
+/// [primitive types]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html#foreign-impls
+/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html.
+///
+/// # Overriding Span Attributes
+///
+/// To change the [name] of the generated span, add a `name` argument to the
+/// `#[instrument]` macro, followed by an equals sign and a string literal. For
+/// example:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+///
+/// // The generated span's name will be "my_span" rather than "my_function".
+/// #[instrument(name = "my_span")]
+/// pub fn my_function() {
+///     // ... do something incredibly interesting and important ...
+/// }
+/// ```
+///
+/// To override the [target] of the generated span, add a `target` argument to
+/// the `#[instrument]` macro, followed by an equals sign and a string literal
+/// for the new target. The [module path] is still recorded separately. For
+/// example:
+///
+/// ```
+/// pub mod my_module {
+///     # use tracing_attributes::instrument;
+///     // The generated span's target will be "my_crate::some_special_target",
+///     // rather than "my_crate::my_module".
+///     #[instrument(target = "my_crate::some_special_target")]
+///     pub fn my_function() {
+///         // ... all kinds of neat code in here ...
+///     }
+/// }
+/// ```
+///
+/// Finally, to override the [level] of the generated span, add a `level`
+/// argument, followed by an equals sign and a string literal with the name of
+/// the desired level. Level names are not case sensitive. For example:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// // The span's level will be TRACE rather than INFO.
+/// #[instrument(level = "trace")]
+/// pub fn my_function() {
+///     // ... I have written a truly marvelous implementation of this function,
+///     // which this example is too narrow to contain ...
+/// }
+/// ```
+///
+/// # Skipping Fields
+///
+/// To skip recording one or more arguments to a function or method, pass
+/// the argument's name inside the `skip()` argument on the `#[instrument]`
+/// macro. This can be used when an argument to an instrumented function does
+/// not implement [`fmt::Debug`], or to exclude an argument with a verbose or
+/// costly `Debug` implementation. Note that:
+///
+/// - multiple argument names can be passed to `skip`.
+/// - arguments passed to `skip` do _not_ need to implement `fmt::Debug`.
+///
+/// You can also use `skip_all` to skip all arguments.
+///
+/// ## Examples
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// # use std::collections::HashMap;
+/// // This type doesn't implement `fmt::Debug`!
+/// struct NonDebug;
+///
+/// // `arg` will be recorded, while `non_debug` will not.
+/// #[instrument(skip(non_debug))]
+/// fn my_function(arg: usize, non_debug: NonDebug) {
+///     // ...
+/// }
+///
+/// // These arguments are huge
+/// #[instrument(skip_all)]
+/// fn my_big_data_function(large: Vec<u8>, also_large: HashMap<String, String>) {
+///     // ...
+/// }
+/// ```
+///
+/// Skipping the `self` parameter:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[derive(Debug)]
+/// struct MyType {
+///    data: Vec<u8>, // Suppose this buffer is often quite long...
+/// }
+///
+/// impl MyType {
+///     // Suppose we don't want to print an entire kilobyte of `data`
+///     // every time this is called...
+///     #[instrument(skip(self))]
+///     pub fn my_method(&mut self, an_interesting_argument: usize) {
+///          // ... do something (hopefully, using all that `data`!)
+///     }
+/// }
+/// ```
+///
+/// # Adding Fields
+///
+/// Additional fields (key-value pairs with arbitrary data) may be added to the
+/// generated span using the `fields` argument on the `#[instrument]` macro. Any
+/// Rust expression can be used as a field value in this manner. These
+/// expressions will be evaluated at the beginning of the function's body, so
+/// arguments to the function may be used in these expressions. Field names may
+/// also be specified *without* values. Doing so will result in an [empty field]
+/// whose value may be recorded later within the function body.
+///
+/// This supports the same [field syntax] as the `span!` and `event!` macros.
+///
+/// Note that overlap between the names of fields and (non-skipped) arguments
+/// will result in a compile error.
+///
+/// ## Examples
+///
+/// Adding a new field based on the value of an argument:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+///
+/// // This will record a field named "i" with the value of `i` *and* a field
+/// // named "next" with the value of `i` + 1.
+/// #[instrument(fields(next = i + 1))]
+/// pub fn my_function(i: usize) {
+///     // ...
+/// }
+/// ```
+///
+/// Recording specific properties of a struct as their own fields:
+///
+/// ```
+/// # mod http {
+/// #   pub struct Error;
+/// #   pub struct Response<B> { pub(super) _b: std::marker::PhantomData<B> }
+/// #   pub struct Request<B> { _b: B }
+/// #   impl<B> std::fmt::Debug for Request<B> {
+/// #       fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+/// #           f.pad("request")
+/// #       }
+/// #   }
+/// #   impl<B> Request<B> {
+/// #       pub fn uri(&self) -> &str { "fake" }
+/// #       pub fn method(&self) -> &str { "GET" }
+/// #   }
+/// # }
+/// # use tracing_attributes::instrument;
+///
+/// // This will record the request's URI and HTTP method as their own separate
+/// // fields.
+/// #[instrument(fields(http.uri = req.uri(), http.method = req.method()))]
+/// pub fn handle_request<B>(req: http::Request<B>) -> http::Response<B> {
+///     // ... handle the request ...
+///     # http::Response { _b: std::marker::PhantomData }
+/// }
+/// ```
+///
+/// This can be used in conjunction with `skip` or `skip_all` to record only
+/// some fields of a struct:
+/// ```
+/// # use tracing_attributes::instrument;
+/// // Remember the struct with the very large `data` field from the earlier
+/// // example? Now it also has a `name`, which we might want to include in
+/// // our span.
+/// #[derive(Debug)]
+/// struct MyType {
+///    name: &'static str,
+///    data: Vec<u8>,
+/// }
+///
+/// impl MyType {
+///     // This will skip the `data` field, but will include `self.name`,
+///     // formatted using `fmt::Display`.
+///     #[instrument(skip(self), fields(self.name = %self.name))]
+///     pub fn my_method(&mut self, an_interesting_argument: usize) {
+///          // ... do something (hopefully, using all that `data`!)
+///     }
+/// }
+/// ```
+///
+/// Adding an empty field to be recorded later:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+///
+/// // This function does a very interesting and important mathematical calculation.
+/// // Suppose we want to record both the inputs to the calculation *and* its result...
+/// #[instrument(fields(result))]
+/// pub fn do_calculation(input_1: usize, input_2: usize) -> usize {
+///     // Rerform the calculation.
+///     let result = input_1 + input_2;
+///
+///     // Record the result as part of the current span.
+///     tracing::Span::current().record("result", &result);
+///
+///     // Now, the result will also be included on this event!
+///     tracing::info!("calculation complete!");
+///
+///     // ... etc ...
+///     # 0
+/// }
+/// ```
+///
+/// # Examples
+///
+/// Instrumenting a function:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument]
+/// pub fn my_function(my_arg: usize) {
+///     // This event will be recorded inside a span named `my_function` with the
+///     // field `my_arg`.
+///     tracing::info!("inside my_function!");
+///     // ...
+/// }
+/// ```
+/// Setting the level for the generated span:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(level = "debug")]
+/// pub fn my_function() {
+///     // ...
+/// }
+/// ```
+/// Overriding the generated span's name:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(name = "my_name")]
+/// pub fn my_function() {
+///     // ...
+/// }
+/// ```
+/// Overriding the generated span's target:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(target = "my_target")]
+/// pub fn my_function() {
+///     // ...
+/// }
+/// ```
+/// Overriding the generated span's parent:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(parent = None)]
+/// pub fn my_function() {
+///     // ...
+/// }
+/// ```
+/// ```
+/// # use tracing_attributes::instrument;
+/// // A struct which owns a span handle.
+/// struct MyStruct
+/// {
+///     span: tracing::Span
+/// }
+///
+/// impl MyStruct
+/// {
+///     // Use the struct's `span` field as the parent span
+///     #[instrument(parent = &self.span, skip(self))]
+///     fn my_method(&self) {}
+/// }
+/// ```
+/// Specifying [`follows_from`] relationships:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(follows_from = causes)]
+/// pub fn my_function(causes: &[tracing::Id]) {
+///     // ...
+/// }
+/// ```
+/// Any expression of type `impl IntoIterator<Item = impl Into<Option<Id>>>`
+/// may be provided to `follows_from`; e.g.:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(follows_from = [cause])]
+/// pub fn my_function(cause: &tracing::span::EnteredSpan) {
+///     // ...
+/// }
+/// ```
+///
+///
+/// To skip recording an argument, pass the argument's name to the `skip`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// struct NonDebug;
+///
+/// #[instrument(skip(non_debug))]
+/// fn my_function(arg: usize, non_debug: NonDebug) {
+///     // ...
+/// }
+/// ```
+///
+/// To add an additional context to the span, pass key-value pairs to `fields`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(fields(foo="bar", id=1, show=true))]
+/// fn my_function(arg: usize) {
+///     // ...
+/// }
+/// ```
+///
+/// Adding the `ret` argument to `#[instrument]` will emit an event with the function's
+/// return value when the function returns:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(ret)]
+/// fn my_function() -> i32 {
+///     42
+/// }
+/// ```
+/// The return value event will have the same level as the span generated by `#[instrument]`.
+/// By default, this will be [`INFO`], but if the level is overridden, the event will be at the same
+/// level.
+///
+/// **Note**:  if the function returns a `Result<T, E>`, `ret` will record returned values if and
+/// only if the function returns [`Result::Ok`].
+///
+/// By default, returned values will be recorded using their [`std::fmt::Debug`] implementations.
+/// If a returned value implements [`std::fmt::Display`], it can be recorded using its `Display`
+/// implementation instead, by writing `ret(Display)`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(ret(Display))]
+/// fn my_function() -> i32 {
+///     42
+/// }
+/// ```
+///
+/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, you can add
+/// `err` or `err(Display)` to emit error events when the function returns `Err`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(err)]
+/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
+///     Ok(())
+/// }
+/// ```
+///
+/// By default, error values will be recorded using their `std::fmt::Display` implementations.
+/// If an error implements `std::fmt::Debug`, it can be recorded using its `Debug` implementation
+/// instead, by writing `err(Debug)`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(err(Debug))]
+/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
+///     Ok(())
+/// }
+/// ```
+///
+/// If a `target` is specified, both the `ret` and `err` arguments will emit outputs to
+/// the declared target (or the default channel if `target` is not specified).
+///
+/// The `ret` and `err` arguments can be combined in order to record an event if a
+/// function returns [`Result::Ok`] or [`Result::Err`]:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(err, ret)]
+/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
+///     Ok(())
+/// }
+/// ```
+///
+/// `async fn`s may also be instrumented:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument]
+/// pub async fn my_function() -> Result<(), ()> {
+///     // ...
+///     # Ok(())
+/// }
+/// ```
+///
+/// It also works with [async-trait](https://crates.io/crates/async-trait)
+/// (a crate that allows defining async functions in traits,
+/// something not currently possible in Rust),
+/// and hopefully most libraries that exhibit similar behaviors:
+///
+/// ```
+/// # use tracing::instrument;
+/// use async_trait::async_trait;
+///
+/// #[async_trait]
+/// pub trait Foo {
+///     async fn foo(&self, arg: usize);
+/// }
+///
+/// #[derive(Debug)]
+/// struct FooImpl(usize);
+///
+/// #[async_trait]
+/// impl Foo for FooImpl {
+///     #[instrument(fields(value = self.0, tmp = std::any::type_name::<Self>()))]
+///     async fn foo(&self, arg: usize) {}
+/// }
+/// ```
+///
+/// Note than on `async-trait` <= 0.1.43, references to the `Self`
+/// type inside the `fields` argument were only allowed when the instrumented
+/// function is a method (i.e., the function receives `self` as an argument).
+/// For example, this *used to not work* because the instrument function
+/// didn't receive `self`:
+/// ```
+/// # use tracing::instrument;
+/// use async_trait::async_trait;
+///
+/// #[async_trait]
+/// pub trait Bar {
+///     async fn bar();
+/// }
+///
+/// #[derive(Debug)]
+/// struct BarImpl(usize);
+///
+/// #[async_trait]
+/// impl Bar for BarImpl {
+///     #[instrument(fields(tmp = std::any::type_name::<Self>()))]
+///     async fn bar() {}
+/// }
+/// ```
+/// Instead, you should manually rewrite any `Self` types as the type for
+/// which you implement the trait: `#[instrument(fields(tmp = std::any::type_name::<Bar>()))]`
+/// (or maybe you can just bump `async-trait`).
+///
+/// [span]: https://docs.rs/tracing/latest/tracing/span/index.html
+/// [name]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.name
+/// [target]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.target
+/// [level]: https://docs.rs/tracing/latest/tracing/struct.Level.html
+/// [module path]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.module_path
+/// [`INFO`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.INFO
+/// [empty field]: https://docs.rs/tracing/latest/tracing/field/struct.Empty.html
+/// [field syntax]: https://docs.rs/tracing/latest/tracing/#recording-fields
+/// [`follows_from`]: https://docs.rs/tracing/latest/tracing/struct.Span.html#method.follows_from
+/// [`tracing`]: https://github.com/tokio-rs/tracing
+/// [`fmt::Debug`]: std::fmt::Debug
+#[proc_macro_attribute]
+pub fn instrument(
+    args: proc_macro::TokenStream,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    let args = syn::parse_macro_input!(args as attr::InstrumentArgs);
+    // Cloning a `TokenStream` is cheap since it's reference counted internally.
+    instrument_precise(args.clone(), item.clone())
+        .unwrap_or_else(|_err| instrument_speculative(args, item))
+}
+
+/// Instrument the function, without parsing the function body (instead using the raw tokens).
+fn instrument_speculative(
+    args: attr::InstrumentArgs,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    let input = syn::parse_macro_input!(item as MaybeItemFn);
+    let instrumented_function_name = input.sig.ident.to_string();
+    expand::gen_function(
+        input.as_ref(),
+        args,
+        instrumented_function_name.as_str(),
+        None,
+    )
+    .into()
+}
+
+/// Instrument the function, by fully parsing the function body,
+/// which allows us to rewrite some statements related to async-like patterns.
+fn instrument_precise(
+    args: attr::InstrumentArgs,
+    item: proc_macro::TokenStream,
+) -> Result<proc_macro::TokenStream, syn::Error> {
+    let input = syn::parse::<ItemFn>(item)?;
+    let instrumented_function_name = input.sig.ident.to_string();
+
+    // check for async_trait-like patterns in the block, and instrument
+    // the future instead of the wrapper
+    if let Some(async_like) = expand::AsyncInfo::from_fn(&input) {
+        return async_like.gen_async(args, instrumented_function_name.as_str());
+    }
+
+    let input = MaybeItemFn::from(input);
+
+    Ok(expand::gen_function(
+        input.as_ref(),
+        args,
+        instrumented_function_name.as_str(),
+        None,
+    )
+    .into())
+}
+
+/// This is a more flexible/imprecise `ItemFn` type,
+/// which's block is just a `TokenStream` (it may contain invalid code).
+#[derive(Debug, Clone)]
+struct MaybeItemFn {
+    outer_attrs: Vec<Attribute>,
+    inner_attrs: Vec<Attribute>,
+    vis: Visibility,
+    sig: Signature,
+    block: TokenStream,
+}
+
+impl MaybeItemFn {
+    fn as_ref(&self) -> MaybeItemFnRef<'_, TokenStream> {
+        MaybeItemFnRef {
+            outer_attrs: &self.outer_attrs,
+            inner_attrs: &self.inner_attrs,
+            vis: &self.vis,
+            sig: &self.sig,
+            block: &self.block,
+        }
+    }
+}
+
+/// This parses a `TokenStream` into a `MaybeItemFn`
+/// (just like `ItemFn`, but skips parsing the body).
+impl Parse for MaybeItemFn {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let outer_attrs = input.call(Attribute::parse_outer)?;
+        let vis: Visibility = input.parse()?;
+        let sig: Signature = input.parse()?;
+        let inner_attrs = input.call(Attribute::parse_inner)?;
+        let block: TokenStream = input.parse()?;
+        Ok(Self {
+            outer_attrs,
+            inner_attrs,
+            vis,
+            sig,
+            block,
+        })
+    }
+}
+
+impl From<ItemFn> for MaybeItemFn {
+    fn from(
+        ItemFn {
+            attrs,
+            vis,
+            sig,
+            block,
+        }: ItemFn,
+    ) -> Self {
+        let (outer_attrs, inner_attrs) = attrs
+            .into_iter()
+            .partition(|attr| attr.style == syn::AttrStyle::Outer);
+        Self {
+            outer_attrs,
+            inner_attrs,
+            vis,
+            sig,
+            block: block.to_token_stream(),
+        }
+    }
+}
+
+/// A generic reference type for `MaybeItemFn`,
+/// that takes a generic block type `B` that implements `ToTokens` (eg. `TokenStream`, `Block`).
+#[derive(Debug, Clone)]
+struct MaybeItemFnRef<'a, B: ToTokens> {
+    outer_attrs: &'a Vec<Attribute>,
+    inner_attrs: &'a Vec<Attribute>,
+    vis: &'a Visibility,
+    sig: &'a Signature,
+    block: &'a B,
+}
diff --git a/tests/async_fn.rs b/tests/async_fn.rs
new file mode 100644
index 0000000..c899636
--- /dev/null
+++ b/tests/async_fn.rs
@@ -0,0 +1,462 @@
+use tracing_mock::*;
+
+use std::convert::Infallible;
+use std::{future::Future, pin::Pin, sync::Arc};
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+
+#[instrument]
+async fn test_async_fn(polls: usize) -> Result<(), ()> {
+    let future = PollN::new_ok(polls);
+    tracing::trace!(awaiting = true);
+    future.await
+}
+
+// Reproduces a compile error when returning an `impl Trait` from an
+// instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615)
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument]
+async fn test_ret_impl_trait(n: i32) -> Result<impl Iterator<Item = i32>, ()> {
+    let n = n;
+    Ok((0..10).filter(move |x| *x < n))
+}
+
+// Reproduces a compile error when returning an `impl Trait` from an
+// instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615)
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument(err)]
+async fn test_ret_impl_trait_err(n: i32) -> Result<impl Iterator<Item = i32>, &'static str> {
+    Ok((0..10).filter(move |x| *x < n))
+}
+
+#[instrument]
+async fn test_async_fn_empty() {}
+
+// Reproduces a compile error when an instrumented function body contains inner
+// attributes (https://github.com/tokio-rs/tracing/issues/2294).
+#[deny(unused_variables)]
+#[instrument]
+async fn repro_async_2294() {
+    #![allow(unused_variables)]
+    let i = 42;
+}
+
+// Reproduces https://github.com/tokio-rs/tracing/issues/1613
+#[instrument]
+// LOAD-BEARING `#[rustfmt::skip]`! This is necessary to reproduce the bug;
+// with the rustfmt-generated formatting, the lint will not be triggered!
+#[rustfmt::skip]
+#[deny(clippy::suspicious_else_formatting)]
+async fn repro_1613(var: bool) {
+    println!(
+        "{}",
+        if var { "true" } else { "false" }
+    );
+}
+
+// Reproduces https://github.com/tokio-rs/tracing/issues/1613
+// and https://github.com/rust-lang/rust-clippy/issues/7760
+#[instrument]
+#[deny(clippy::suspicious_else_formatting)]
+async fn repro_1613_2() {
+    // hello world
+    // else
+}
+
+// Reproduces https://github.com/tokio-rs/tracing/issues/1831
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument]
+#[deny(unused_braces)]
+fn repro_1831() -> Pin<Box<dyn Future<Output = ()>>> {
+    Box::pin(async move {})
+}
+
+// This replicates the pattern used to implement async trait methods on nightly using the
+// `type_alias_impl_trait` feature
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument(ret, err)]
+#[deny(unused_braces)]
+#[allow(clippy::manual_async_fn)]
+fn repro_1831_2() -> impl Future<Output = Result<(), Infallible>> {
+    async { Ok(()) }
+}
+
+#[test]
+fn async_fn_only_enters_for_polls() {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("test_async_fn"))
+        .enter(span::mock().named("test_async_fn"))
+        .event(event::mock().with_fields(field::mock("awaiting").with_value(&true)))
+        .exit(span::mock().named("test_async_fn"))
+        .enter(span::mock().named("test_async_fn"))
+        .exit(span::mock().named("test_async_fn"))
+        .drop_span(span::mock().named("test_async_fn"))
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || {
+        block_on_future(async { test_async_fn(2).await }).unwrap();
+    });
+    handle.assert_finished();
+}
+
+#[test]
+fn async_fn_nested() {
+    #[instrument]
+    async fn test_async_fns_nested() {
+        test_async_fns_nested_other().await
+    }
+
+    #[instrument]
+    async fn test_async_fns_nested_other() {
+        tracing::trace!(nested = true);
+    }
+
+    let span = span::mock().named("test_async_fns_nested");
+    let span2 = span::mock().named("test_async_fns_nested_other");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .new_span(span2.clone())
+        .enter(span2.clone())
+        .event(event::mock().with_fields(field::mock("nested").with_value(&true)))
+        .exit(span2.clone())
+        .drop_span(span2)
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async { test_async_fns_nested().await });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn async_fn_with_async_trait() {
+    use async_trait::async_trait;
+
+    // test the correctness of the metadata obtained by #[instrument]
+    // (function name, functions parameters) when async-trait is used
+    #[async_trait]
+    pub trait TestA {
+        async fn foo(&mut self, v: usize);
+    }
+
+    // test nesting of async fns with aync-trait
+    #[async_trait]
+    pub trait TestB {
+        async fn bar(&self);
+    }
+
+    // test skip(self) with async-await
+    #[async_trait]
+    pub trait TestC {
+        async fn baz(&self);
+    }
+
+    #[derive(Debug)]
+    struct TestImpl(usize);
+
+    #[async_trait]
+    impl TestA for TestImpl {
+        #[instrument]
+        async fn foo(&mut self, v: usize) {
+            self.baz().await;
+            self.0 = v;
+            self.bar().await
+        }
+    }
+
+    #[async_trait]
+    impl TestB for TestImpl {
+        #[instrument]
+        async fn bar(&self) {
+            tracing::trace!(val = self.0);
+        }
+    }
+
+    #[async_trait]
+    impl TestC for TestImpl {
+        #[instrument(skip(self))]
+        async fn baz(&self) {
+            tracing::trace!(val = self.0);
+        }
+    }
+
+    let span = span::mock().named("foo");
+    let span2 = span::mock().named("bar");
+    let span3 = span::mock().named("baz");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone()
+                .with_field(field::mock("self"))
+                .with_field(field::mock("v")),
+        )
+        .enter(span.clone())
+        .new_span(span3.clone())
+        .enter(span3.clone())
+        .event(event::mock().with_fields(field::mock("val").with_value(&2u64)))
+        .exit(span3.clone())
+        .drop_span(span3)
+        .new_span(span2.clone().with_field(field::mock("self")))
+        .enter(span2.clone())
+        .event(event::mock().with_fields(field::mock("val").with_value(&5u64)))
+        .exit(span2.clone())
+        .drop_span(span2)
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        let mut test = TestImpl(2);
+        block_on_future(async { test.foo(5).await });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn async_fn_with_async_trait_and_fields_expressions() {
+    use async_trait::async_trait;
+
+    #[async_trait]
+    pub trait Test {
+        async fn call(&mut self, v: usize);
+    }
+
+    #[derive(Clone, Debug)]
+    struct TestImpl;
+
+    impl TestImpl {
+        fn foo(&self) -> usize {
+            42
+        }
+    }
+
+    #[async_trait]
+    impl Test for TestImpl {
+        // check that self is correctly handled, even when using async_trait
+        #[instrument(fields(val=self.foo(), val2=Self::clone(self).foo(), test=%_v+5))]
+        async fn call(&mut self, _v: usize) {}
+    }
+
+    let span = span::mock().named("call");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("_v")
+                    .with_value(&5usize)
+                    .and(field::mock("test").with_value(&tracing::field::debug(10)))
+                    .and(field::mock("val").with_value(&42u64))
+                    .and(field::mock("val2").with_value(&42u64)),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async { TestImpl.call(5).await });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
+    use async_trait::async_trait;
+
+    #[async_trait]
+    pub trait Test {
+        async fn call();
+        async fn call_with_self(&self);
+        async fn call_with_mut_self(&mut self);
+    }
+
+    #[derive(Clone, Debug)]
+    struct TestImpl;
+
+    // we also test sync functions that return futures, as they should be handled just like
+    // async-trait (>= 0.1.44) functions
+    impl TestImpl {
+        #[instrument(fields(Self=std::any::type_name::<Self>()))]
+        fn sync_fun(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
+            let val = self.clone();
+            Box::pin(async move {
+                let _ = val;
+            })
+        }
+    }
+
+    #[async_trait]
+    impl Test for TestImpl {
+        // instrumenting this is currently not possible, see https://github.com/tokio-rs/tracing/issues/864#issuecomment-667508801
+        //#[instrument(fields(Self=std::any::type_name::<Self>()))]
+        async fn call() {}
+
+        #[instrument(fields(Self=std::any::type_name::<Self>()))]
+        async fn call_with_self(&self) {
+            self.sync_fun().await;
+        }
+
+        #[instrument(fields(Self=std::any::type_name::<Self>()))]
+        async fn call_with_mut_self(&mut self) {}
+    }
+
+    //let span = span::mock().named("call");
+    let span2 = span::mock().named("call_with_self");
+    let span3 = span::mock().named("call_with_mut_self");
+    let span4 = span::mock().named("sync_fun");
+    let (subscriber, handle) = subscriber::mock()
+        /*.new_span(span.clone()
+            .with_field(
+                field::mock("Self").with_value(&"TestImpler")))
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)*/
+        .new_span(
+            span2
+                .clone()
+                .with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
+        )
+        .enter(span2.clone())
+        .new_span(
+            span4
+                .clone()
+                .with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
+        )
+        .enter(span4.clone())
+        .exit(span4)
+        .exit(span2.clone())
+        .drop_span(span2)
+        .new_span(
+            span3
+                .clone()
+                .with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
+        )
+        .enter(span3.clone())
+        .exit(span3.clone())
+        .drop_span(span3)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async {
+            TestImpl::call().await;
+            TestImpl.call_with_self().await;
+            TestImpl.call_with_mut_self().await
+        });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn out_of_scope_fields() {
+    // Reproduces tokio-rs/tracing#1296
+
+    struct Thing {
+        metrics: Arc<()>,
+    }
+
+    impl Thing {
+        #[instrument(skip(self, _req), fields(app_id))]
+        fn call(&mut self, _req: ()) -> Pin<Box<dyn Future<Output = Arc<()>> + Send + Sync>> {
+            // ...
+            let metrics = self.metrics.clone();
+            // ...
+            Box::pin(async move {
+                // ...
+                metrics // cannot find value `metrics` in this scope
+            })
+        }
+    }
+
+    let span = span::mock().named("call");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async {
+            let mut my_thing = Thing {
+                metrics: Arc::new(()),
+            };
+            my_thing.call(()).await;
+        });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn manual_impl_future() {
+    #[allow(clippy::manual_async_fn)]
+    #[instrument]
+    fn manual_impl_future() -> impl Future<Output = ()> {
+        async {
+            tracing::trace!(poll = true);
+        }
+    }
+
+    let span = span::mock().named("manual_impl_future");
+    let poll_event = || event::mock().with_fields(field::mock("poll").with_value(&true));
+
+    let (subscriber, handle) = subscriber::mock()
+        // await manual_impl_future
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(poll_event())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async {
+            manual_impl_future().await;
+        });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn manual_box_pin() {
+    #[instrument]
+    fn manual_box_pin() -> Pin<Box<dyn Future<Output = ()>>> {
+        Box::pin(async {
+            tracing::trace!(poll = true);
+        })
+    }
+
+    let span = span::mock().named("manual_box_pin");
+    let poll_event = || event::mock().with_fields(field::mock("poll").with_value(&true));
+
+    let (subscriber, handle) = subscriber::mock()
+        // await manual_box_pin
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(poll_event())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async {
+            manual_box_pin().await;
+        });
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/destructuring.rs b/tests/destructuring.rs
new file mode 100644
index 0000000..09cf1ad
--- /dev/null
+++ b/tests/destructuring.rs
@@ -0,0 +1,213 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[test]
+fn destructure_tuples() {
+    #[instrument]
+    fn my_fn((arg1, arg2): (usize, usize)) {}
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&format_args!("1"))
+                    .and(field::mock("arg2").with_value(&format_args!("2")))
+                    .only(),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn((1, 2));
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn destructure_nested_tuples() {
+    #[instrument]
+    fn my_fn(((arg1, arg2), (arg3, arg4)): ((usize, usize), (usize, usize))) {}
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&format_args!("1"))
+                    .and(field::mock("arg2").with_value(&format_args!("2")))
+                    .and(field::mock("arg3").with_value(&format_args!("3")))
+                    .and(field::mock("arg4").with_value(&format_args!("4")))
+                    .only(),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(((1, 2), (3, 4)));
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn destructure_refs() {
+    #[instrument]
+    fn my_fn(&arg1: &usize) {}
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone()
+                .with_field(field::mock("arg1").with_value(&1usize).only()),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(&1);
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn destructure_tuple_structs() {
+    struct Foo(usize, usize);
+
+    #[instrument]
+    fn my_fn(Foo(arg1, arg2): Foo) {}
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&format_args!("1"))
+                    .and(field::mock("arg2").with_value(&format_args!("2")))
+                    .only(),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(Foo(1, 2));
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn destructure_structs() {
+    struct Foo {
+        bar: usize,
+        baz: usize,
+    }
+
+    #[instrument]
+    fn my_fn(
+        Foo {
+            bar: arg1,
+            baz: arg2,
+        }: Foo,
+    ) {
+        let _ = (arg1, arg2);
+    }
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&format_args!("1"))
+                    .and(field::mock("arg2").with_value(&format_args!("2")))
+                    .only(),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(Foo { bar: 1, baz: 2 });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn destructure_everything() {
+    struct Foo {
+        bar: Bar,
+        baz: (usize, usize),
+        qux: NoDebug,
+    }
+    struct Bar((usize, usize));
+    struct NoDebug;
+
+    #[instrument]
+    fn my_fn(
+        &Foo {
+            bar: Bar((arg1, arg2)),
+            baz: (arg3, arg4),
+            ..
+        }: &Foo,
+    ) {
+        let _ = (arg1, arg2, arg3, arg4);
+    }
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&format_args!("1"))
+                    .and(field::mock("arg2").with_value(&format_args!("2")))
+                    .and(field::mock("arg3").with_value(&format_args!("3")))
+                    .and(field::mock("arg4").with_value(&format_args!("4")))
+                    .only(),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        let foo = Foo {
+            bar: Bar((1, 2)),
+            baz: (3, 4),
+            qux: NoDebug,
+        };
+        let _ = foo.qux; // to eliminate unused field warning
+        my_fn(&foo);
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/err.rs b/tests/err.rs
new file mode 100644
index 0000000..9e6d6b7
--- /dev/null
+++ b/tests/err.rs
@@ -0,0 +1,233 @@
+use tracing::subscriber::with_default;
+use tracing::Level;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+use tracing_subscriber::filter::EnvFilter;
+use tracing_subscriber::layer::SubscriberExt;
+
+use std::convert::TryFrom;
+use std::num::TryFromIntError;
+
+#[instrument(err)]
+fn err() -> Result<u8, TryFromIntError> {
+    u8::try_from(1234)
+}
+
+#[instrument(err)]
+fn err_suspicious_else() -> Result<u8, TryFromIntError> {
+    {}
+    u8::try_from(1234)
+}
+
+#[test]
+fn test() {
+    let span = span::mock().named("err");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(event::mock().at_level(Level::ERROR))
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || err().ok());
+    handle.assert_finished();
+}
+
+#[instrument(err)]
+async fn err_async(polls: usize) -> Result<u8, TryFromIntError> {
+    let future = PollN::new_ok(polls);
+    tracing::trace!(awaiting = true);
+    future.await.ok();
+    u8::try_from(1234)
+}
+
+#[test]
+fn test_async() {
+    let span = span::mock().named("err_async");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("awaiting").with_value(&true))
+                .at_level(Level::TRACE),
+        )
+        .exit(span.clone())
+        .enter(span.clone())
+        .event(event::mock().at_level(Level::ERROR))
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || {
+        block_on_future(async { err_async(2).await }).ok();
+    });
+    handle.assert_finished();
+}
+
+#[instrument(err)]
+fn err_mut(out: &mut u8) -> Result<(), TryFromIntError> {
+    *out = u8::try_from(1234)?;
+    Ok(())
+}
+
+#[test]
+fn test_mut() {
+    let span = span::mock().named("err_mut");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(event::mock().at_level(Level::ERROR))
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || err_mut(&mut 0).ok());
+    handle.assert_finished();
+}
+
+#[instrument(err)]
+async fn err_mut_async(polls: usize, out: &mut u8) -> Result<(), TryFromIntError> {
+    let future = PollN::new_ok(polls);
+    tracing::trace!(awaiting = true);
+    future.await.ok();
+    *out = u8::try_from(1234)?;
+    Ok(())
+}
+
+#[test]
+fn test_mut_async() {
+    let span = span::mock().named("err_mut_async");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("awaiting").with_value(&true))
+                .at_level(Level::TRACE),
+        )
+        .exit(span.clone())
+        .enter(span.clone())
+        .event(event::mock().at_level(Level::ERROR))
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || {
+        block_on_future(async { err_mut_async(2, &mut 0).await }).ok();
+    });
+    handle.assert_finished();
+}
+
+#[test]
+fn impl_trait_return_type() {
+    // Reproduces https://github.com/tokio-rs/tracing/issues/1227
+
+    #[instrument(err)]
+    fn returns_impl_trait(x: usize) -> Result<impl Iterator<Item = usize>, String> {
+        Ok(0..x)
+    }
+
+    let span = span::mock().named("returns_impl_trait");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone()
+                .with_field(field::mock("x").with_value(&10usize).only()),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        for _ in returns_impl_trait(10).unwrap() {
+            // nop
+        }
+    });
+
+    handle.assert_finished();
+}
+
+#[instrument(err(Debug))]
+fn err_dbg() -> Result<u8, TryFromIntError> {
+    u8::try_from(1234)
+}
+
+#[test]
+fn test_err_dbg() {
+    let span = span::mock().named("err_dbg");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock().at_level(Level::ERROR).with_fields(
+                field::mock("error")
+                    // use the actual error value that will be emitted, so
+                    // that this test doesn't break if the standard library
+                    // changes the `fmt::Debug` output from the error type
+                    // in the future.
+                    .with_value(&tracing::field::debug(u8::try_from(1234).unwrap_err())),
+            ),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || err_dbg().ok());
+    handle.assert_finished();
+}
+
+#[test]
+fn test_err_display_default() {
+    let span = span::mock().named("err");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock().at_level(Level::ERROR).with_fields(
+                field::mock("error")
+                    // by default, errors will be emitted with their display values
+                    .with_value(&tracing::field::display(u8::try_from(1234).unwrap_err())),
+            ),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || err().ok());
+    handle.assert_finished();
+}
+
+#[test]
+fn test_err_custom_target() {
+    let filter: EnvFilter = "my_target=error".parse().expect("filter should parse");
+    let span = span::mock().named("error_span").with_target("my_target");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .at_level(Level::ERROR)
+                .with_target("my_target"),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    let subscriber = subscriber.with(filter);
+
+    with_default(subscriber, || {
+        let error_span = tracing::error_span!(target: "my_target", "error_span");
+
+        {
+            let _enter = error_span.enter();
+            tracing::error!(target: "my_target", "This should display")
+        }
+    });
+    handle.assert_finished();
+}
diff --git a/tests/fields.rs b/tests/fields.rs
new file mode 100644
index 0000000..c178fbb
--- /dev/null
+++ b/tests/fields.rs
@@ -0,0 +1,160 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::field::mock;
+use tracing_mock::span::NewSpan;
+use tracing_mock::*;
+
+#[instrument(fields(foo = "bar", dsa = true, num = 1))]
+fn fn_no_param() {}
+
+#[instrument(fields(foo = "bar"))]
+fn fn_param(param: u32) {}
+
+#[instrument(fields(foo = "bar", empty))]
+fn fn_empty_field() {}
+
+#[instrument(fields(len = s.len()))]
+fn fn_expr_field(s: &str) {}
+
+#[instrument(fields(s.len = s.len(), s.is_empty = s.is_empty()))]
+fn fn_two_expr_fields(s: &str) {
+    let _ = s;
+}
+
+#[instrument(fields(%s, s.len = s.len()))]
+fn fn_clashy_expr_field(s: &str) {
+    let _ = s;
+}
+
+#[instrument(fields(s = "s"))]
+fn fn_clashy_expr_field2(s: &str) {
+    let _ = s;
+}
+
+#[instrument(fields(s = &s))]
+fn fn_string(s: String) {
+    let _ = s;
+}
+
+#[derive(Debug)]
+struct HasField {
+    my_field: &'static str,
+}
+
+impl HasField {
+    #[instrument(fields(my_field = self.my_field), skip(self))]
+    fn self_expr_field(&self) {}
+}
+
+#[test]
+fn fields() {
+    let span = span::mock().with_field(
+        mock("foo")
+            .with_value(&"bar")
+            .and(mock("dsa").with_value(&true))
+            .and(mock("num").with_value(&1))
+            .only(),
+    );
+    run_test(span, || {
+        fn_no_param();
+    });
+}
+
+#[test]
+fn expr_field() {
+    let span = span::mock().with_field(
+        mock("s")
+            .with_value(&"hello world")
+            .and(mock("len").with_value(&"hello world".len()))
+            .only(),
+    );
+    run_test(span, || {
+        fn_expr_field("hello world");
+    });
+}
+
+#[test]
+fn two_expr_fields() {
+    let span = span::mock().with_field(
+        mock("s")
+            .with_value(&"hello world")
+            .and(mock("s.len").with_value(&"hello world".len()))
+            .and(mock("s.is_empty").with_value(&false))
+            .only(),
+    );
+    run_test(span, || {
+        fn_two_expr_fields("hello world");
+    });
+}
+
+#[test]
+fn clashy_expr_field() {
+    let span = span::mock().with_field(
+        // Overriding the `s` field should record `s` as a `Display` value,
+        // rather than as a `Debug` value.
+        mock("s")
+            .with_value(&tracing::field::display("hello world"))
+            .and(mock("s.len").with_value(&"hello world".len()))
+            .only(),
+    );
+    run_test(span, || {
+        fn_clashy_expr_field("hello world");
+    });
+
+    let span = span::mock().with_field(mock("s").with_value(&"s").only());
+    run_test(span, || {
+        fn_clashy_expr_field2("hello world");
+    });
+}
+
+#[test]
+fn self_expr_field() {
+    let span = span::mock().with_field(mock("my_field").with_value(&"hello world").only());
+    run_test(span, || {
+        let has_field = HasField {
+            my_field: "hello world",
+        };
+        has_field.self_expr_field();
+    });
+}
+
+#[test]
+fn parameters_with_fields() {
+    let span = span::mock().with_field(
+        mock("foo")
+            .with_value(&"bar")
+            .and(mock("param").with_value(&1u32))
+            .only(),
+    );
+    run_test(span, || {
+        fn_param(1);
+    });
+}
+
+#[test]
+fn empty_field() {
+    let span = span::mock().with_field(mock("foo").with_value(&"bar").only());
+    run_test(span, || {
+        fn_empty_field();
+    });
+}
+
+#[test]
+fn string_field() {
+    let span = span::mock().with_field(mock("s").with_value(&"hello world").only());
+    run_test(span, || {
+        fn_string(String::from("hello world"));
+    });
+}
+
+fn run_test<F: FnOnce() -> T, T>(span: NewSpan, fun: F) {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span)
+        .enter(span::mock())
+        .exit(span::mock())
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, fun);
+    handle.assert_finished();
+}
diff --git a/tests/follows_from.rs b/tests/follows_from.rs
new file mode 100644
index 0000000..da0eec6
--- /dev/null
+++ b/tests/follows_from.rs
@@ -0,0 +1,99 @@
+use tracing::{subscriber::with_default, Id, Level, Span};
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument(follows_from = causes, skip(causes))]
+fn with_follows_from_sync(causes: impl IntoIterator<Item = impl Into<Option<Id>>>) {}
+
+#[instrument(follows_from = causes, skip(causes))]
+async fn with_follows_from_async(causes: impl IntoIterator<Item = impl Into<Option<Id>>>) {}
+
+#[instrument(follows_from = [&Span::current()])]
+fn follows_from_current() {}
+
+#[test]
+fn follows_from_sync_test() {
+    let cause_a = span::mock().named("cause_a");
+    let cause_b = span::mock().named("cause_b");
+    let cause_c = span::mock().named("cause_c");
+    let consequence = span::mock().named("with_follows_from_sync");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(cause_a.clone())
+        .new_span(cause_b.clone())
+        .new_span(cause_c.clone())
+        .new_span(consequence.clone())
+        .follows_from(consequence.clone(), cause_a)
+        .follows_from(consequence.clone(), cause_b)
+        .follows_from(consequence.clone(), cause_c)
+        .enter(consequence.clone())
+        .exit(consequence)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        let cause_a = tracing::span!(Level::TRACE, "cause_a");
+        let cause_b = tracing::span!(Level::TRACE, "cause_b");
+        let cause_c = tracing::span!(Level::TRACE, "cause_c");
+
+        with_follows_from_sync(&[cause_a, cause_b, cause_c])
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn follows_from_async_test() {
+    let cause_a = span::mock().named("cause_a");
+    let cause_b = span::mock().named("cause_b");
+    let cause_c = span::mock().named("cause_c");
+    let consequence = span::mock().named("with_follows_from_async");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(cause_a.clone())
+        .new_span(cause_b.clone())
+        .new_span(cause_c.clone())
+        .new_span(consequence.clone())
+        .follows_from(consequence.clone(), cause_a)
+        .follows_from(consequence.clone(), cause_b)
+        .follows_from(consequence.clone(), cause_c)
+        .enter(consequence.clone())
+        .exit(consequence)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        block_on_future(async {
+            let cause_a = tracing::span!(Level::TRACE, "cause_a");
+            let cause_b = tracing::span!(Level::TRACE, "cause_b");
+            let cause_c = tracing::span!(Level::TRACE, "cause_c");
+
+            with_follows_from_async(&[cause_a, cause_b, cause_c]).await
+        })
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn follows_from_current_test() {
+    let cause = span::mock().named("cause");
+    let consequence = span::mock().named("follows_from_current");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(cause.clone())
+        .enter(cause.clone())
+        .new_span(consequence.clone())
+        .follows_from(consequence.clone(), cause.clone())
+        .enter(consequence.clone())
+        .exit(consequence)
+        .exit(cause)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        tracing::span!(Level::TRACE, "cause").in_scope(follows_from_current)
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/instrument.rs b/tests/instrument.rs
new file mode 100644
index 0000000..7686927
--- /dev/null
+++ b/tests/instrument.rs
@@ -0,0 +1,252 @@
+use tracing::subscriber::with_default;
+use tracing::Level;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+// Reproduces a compile error when an instrumented function body contains inner
+// attributes (https://github.com/tokio-rs/tracing/issues/2294).
+#[deny(unused_variables)]
+#[instrument]
+fn repro_2294() {
+    #![allow(unused_variables)]
+    let i = 42;
+}
+
+#[test]
+fn override_everything() {
+    #[instrument(target = "my_target", level = "debug")]
+    fn my_fn() {}
+
+    #[instrument(level = "debug", target = "my_target")]
+    fn my_other_fn() {}
+
+    let span = span::mock()
+        .named("my_fn")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+    let span2 = span::mock()
+        .named("my_other_fn")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .new_span(span2.clone())
+        .enter(span2.clone())
+        .exit(span2.clone())
+        .drop_span(span2)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn();
+        my_other_fn();
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn fields() {
+    #[instrument(target = "my_target", level = "debug")]
+    fn my_fn(arg1: usize, arg2: bool) {}
+
+    let span = span::mock()
+        .named("my_fn")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+
+    let span2 = span::mock()
+        .named("my_fn")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&2usize)
+                    .and(field::mock("arg2").with_value(&false))
+                    .only(),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .new_span(
+            span2.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&3usize)
+                    .and(field::mock("arg2").with_value(&true))
+                    .only(),
+            ),
+        )
+        .enter(span2.clone())
+        .exit(span2.clone())
+        .drop_span(span2)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(2, false);
+        my_fn(3, true);
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn skip() {
+    struct UnDebug(pub u32);
+
+    #[instrument(target = "my_target", level = "debug", skip(_arg2, _arg3))]
+    fn my_fn(arg1: usize, _arg2: UnDebug, _arg3: UnDebug) {}
+
+    #[instrument(target = "my_target", level = "debug", skip_all)]
+    fn my_fn2(_arg1: usize, _arg2: UnDebug, _arg3: UnDebug) {}
+
+    let span = span::mock()
+        .named("my_fn")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+
+    let span2 = span::mock()
+        .named("my_fn")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+
+    let span3 = span::mock()
+        .named("my_fn2")
+        .at_level(Level::DEBUG)
+        .with_target("my_target");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone()
+                .with_field(field::mock("arg1").with_value(&2usize).only()),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .new_span(
+            span2
+                .clone()
+                .with_field(field::mock("arg1").with_value(&3usize).only()),
+        )
+        .enter(span2.clone())
+        .exit(span2.clone())
+        .drop_span(span2)
+        .new_span(span3.clone())
+        .enter(span3.clone())
+        .exit(span3.clone())
+        .drop_span(span3)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(2, UnDebug(0), UnDebug(1));
+        my_fn(3, UnDebug(0), UnDebug(1));
+        my_fn2(2, UnDebug(0), UnDebug(1));
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn generics() {
+    #[derive(Debug)]
+    struct Foo;
+
+    #[instrument]
+    fn my_fn<S, T: std::fmt::Debug>(arg1: S, arg2: T)
+    where
+        S: std::fmt::Debug,
+    {
+    }
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("arg1")
+                    .with_value(&format_args!("Foo"))
+                    .and(field::mock("arg2").with_value(&format_args!("false"))),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        my_fn(Foo, false);
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn methods() {
+    #[derive(Debug)]
+    struct Foo;
+
+    impl Foo {
+        #[instrument]
+        fn my_fn(&self, arg1: usize) {}
+    }
+
+    let span = span::mock().named("my_fn");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone().with_field(
+                field::mock("self")
+                    .with_value(&format_args!("Foo"))
+                    .and(field::mock("arg1").with_value(&42usize)),
+            ),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        let foo = Foo;
+        foo.my_fn(42);
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn impl_trait_return_type() {
+    #[instrument]
+    fn returns_impl_trait(x: usize) -> impl Iterator<Item = usize> {
+        0..x
+    }
+
+    let span = span::mock().named("returns_impl_trait");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span.clone()
+                .with_field(field::mock("x").with_value(&10usize).only()),
+        )
+        .enter(span.clone())
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        for _ in returns_impl_trait(10) {
+            // nop
+        }
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/levels.rs b/tests/levels.rs
new file mode 100644
index 0000000..b074ea4
--- /dev/null
+++ b/tests/levels.rs
@@ -0,0 +1,96 @@
+use tracing::subscriber::with_default;
+use tracing::Level;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[test]
+fn named_levels() {
+    #[instrument(level = "trace")]
+    fn trace() {}
+
+    #[instrument(level = "Debug")]
+    fn debug() {}
+
+    #[instrument(level = "INFO")]
+    fn info() {}
+
+    #[instrument(level = "WARn")]
+    fn warn() {}
+
+    #[instrument(level = "eRrOr")]
+    fn error() {}
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("trace").at_level(Level::TRACE))
+        .enter(span::mock().named("trace").at_level(Level::TRACE))
+        .exit(span::mock().named("trace").at_level(Level::TRACE))
+        .new_span(span::mock().named("debug").at_level(Level::DEBUG))
+        .enter(span::mock().named("debug").at_level(Level::DEBUG))
+        .exit(span::mock().named("debug").at_level(Level::DEBUG))
+        .new_span(span::mock().named("info").at_level(Level::INFO))
+        .enter(span::mock().named("info").at_level(Level::INFO))
+        .exit(span::mock().named("info").at_level(Level::INFO))
+        .new_span(span::mock().named("warn").at_level(Level::WARN))
+        .enter(span::mock().named("warn").at_level(Level::WARN))
+        .exit(span::mock().named("warn").at_level(Level::WARN))
+        .new_span(span::mock().named("error").at_level(Level::ERROR))
+        .enter(span::mock().named("error").at_level(Level::ERROR))
+        .exit(span::mock().named("error").at_level(Level::ERROR))
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        trace();
+        debug();
+        info();
+        warn();
+        error();
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn numeric_levels() {
+    #[instrument(level = 1)]
+    fn trace() {}
+
+    #[instrument(level = 2)]
+    fn debug() {}
+
+    #[instrument(level = 3)]
+    fn info() {}
+
+    #[instrument(level = 4)]
+    fn warn() {}
+
+    #[instrument(level = 5)]
+    fn error() {}
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("trace").at_level(Level::TRACE))
+        .enter(span::mock().named("trace").at_level(Level::TRACE))
+        .exit(span::mock().named("trace").at_level(Level::TRACE))
+        .new_span(span::mock().named("debug").at_level(Level::DEBUG))
+        .enter(span::mock().named("debug").at_level(Level::DEBUG))
+        .exit(span::mock().named("debug").at_level(Level::DEBUG))
+        .new_span(span::mock().named("info").at_level(Level::INFO))
+        .enter(span::mock().named("info").at_level(Level::INFO))
+        .exit(span::mock().named("info").at_level(Level::INFO))
+        .new_span(span::mock().named("warn").at_level(Level::WARN))
+        .enter(span::mock().named("warn").at_level(Level::WARN))
+        .exit(span::mock().named("warn").at_level(Level::WARN))
+        .new_span(span::mock().named("error").at_level(Level::ERROR))
+        .enter(span::mock().named("error").at_level(Level::ERROR))
+        .exit(span::mock().named("error").at_level(Level::ERROR))
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        trace();
+        debug();
+        info();
+        warn();
+        error();
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/names.rs b/tests/names.rs
new file mode 100644
index 0000000..d97dece
--- /dev/null
+++ b/tests/names.rs
@@ -0,0 +1,63 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument]
+fn default_name() {}
+
+#[instrument(name = "my_name")]
+fn custom_name() {}
+
+// XXX: it's weird that we support both of these forms, but apparently we
+// managed to release a version that accepts both syntax, so now we have to
+// support it! yay!
+#[instrument("my_other_name")]
+fn custom_name_no_equals() {}
+
+#[test]
+fn default_name_test() {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("default_name"))
+        .enter(span::mock().named("default_name"))
+        .exit(span::mock().named("default_name"))
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        default_name();
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn custom_name_test() {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("my_name"))
+        .enter(span::mock().named("my_name"))
+        .exit(span::mock().named("my_name"))
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        custom_name();
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn custom_name_no_equals_test() {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("my_other_name"))
+        .enter(span::mock().named("my_other_name"))
+        .exit(span::mock().named("my_other_name"))
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        custom_name_no_equals();
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/parents.rs b/tests/parents.rs
new file mode 100644
index 0000000..7069b98
--- /dev/null
+++ b/tests/parents.rs
@@ -0,0 +1,102 @@
+use tracing::{subscriber::with_default, Id, Level};
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument]
+fn with_default_parent() {}
+
+#[instrument(parent = parent_span, skip(parent_span))]
+fn with_explicit_parent<P>(parent_span: P)
+where
+    P: Into<Option<Id>>,
+{
+}
+
+#[test]
+fn default_parent_test() {
+    let contextual_parent = span::mock().named("contextual_parent");
+    let child = span::mock().named("with_default_parent");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            contextual_parent
+                .clone()
+                .with_contextual_parent(None)
+                .with_explicit_parent(None),
+        )
+        .new_span(
+            child
+                .clone()
+                .with_contextual_parent(Some("contextual_parent"))
+                .with_explicit_parent(None),
+        )
+        .enter(child.clone())
+        .exit(child.clone())
+        .enter(contextual_parent.clone())
+        .new_span(
+            child
+                .clone()
+                .with_contextual_parent(Some("contextual_parent"))
+                .with_explicit_parent(None),
+        )
+        .enter(child.clone())
+        .exit(child)
+        .exit(contextual_parent)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        let contextual_parent = tracing::span!(Level::TRACE, "contextual_parent");
+
+        with_default_parent();
+
+        contextual_parent.in_scope(|| {
+            with_default_parent();
+        });
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn explicit_parent_test() {
+    let contextual_parent = span::mock().named("contextual_parent");
+    let explicit_parent = span::mock().named("explicit_parent");
+    let child = span::mock().named("with_explicit_parent");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            contextual_parent
+                .clone()
+                .with_contextual_parent(None)
+                .with_explicit_parent(None),
+        )
+        .new_span(
+            explicit_parent
+                .with_contextual_parent(None)
+                .with_explicit_parent(None),
+        )
+        .enter(contextual_parent.clone())
+        .new_span(
+            child
+                .clone()
+                .with_contextual_parent(Some("contextual_parent"))
+                .with_explicit_parent(Some("explicit_parent")),
+        )
+        .enter(child.clone())
+        .exit(child)
+        .exit(contextual_parent)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        let contextual_parent = tracing::span!(Level::INFO, "contextual_parent");
+        let explicit_parent = tracing::span!(Level::INFO, "explicit_parent");
+
+        contextual_parent.in_scope(|| {
+            with_explicit_parent(&explicit_parent);
+        });
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/ret.rs b/tests/ret.rs
new file mode 100644
index 0000000..cfd2de1
--- /dev/null
+++ b/tests/ret.rs
@@ -0,0 +1,255 @@
+use std::convert::TryFrom;
+use std::num::TryFromIntError;
+use tracing_mock::*;
+
+use tracing::{subscriber::with_default, Level};
+use tracing_attributes::instrument;
+use tracing_subscriber::layer::SubscriberExt;
+use tracing_subscriber::EnvFilter;
+
+#[instrument(ret)]
+fn ret() -> i32 {
+    42
+}
+
+#[instrument(target = "my_target", ret)]
+fn ret_with_target() -> i32 {
+    42
+}
+
+#[test]
+fn test() {
+    let span = span::mock().named("ret");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+                .at_level(Level::INFO),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, ret);
+    handle.assert_finished();
+}
+
+#[test]
+fn test_custom_target() {
+    let filter: EnvFilter = "my_target=info".parse().expect("filter should parse");
+    let span = span::mock()
+        .named("ret_with_target")
+        .with_target("my_target");
+
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+                .at_level(Level::INFO)
+                .with_target("my_target"),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    let subscriber = subscriber.with(filter);
+
+    with_default(subscriber, ret_with_target);
+    handle.assert_finished();
+}
+
+#[instrument(level = "warn", ret)]
+fn ret_warn() -> i32 {
+    42
+}
+
+#[test]
+fn test_warn() {
+    let span = span::mock().named("ret_warn");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+                .at_level(Level::WARN),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, ret_warn);
+    handle.assert_finished();
+}
+
+#[instrument(ret)]
+fn ret_mut(a: &mut i32) -> i32 {
+    *a *= 2;
+    tracing::info!(?a);
+    *a
+}
+
+#[test]
+fn test_mut() {
+    let span = span::mock().named("ret_mut");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("a").with_value(&tracing::field::display(2)))
+                .at_level(Level::INFO),
+        )
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::debug(2)))
+                .at_level(Level::INFO),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || ret_mut(&mut 1));
+    handle.assert_finished();
+}
+
+#[instrument(ret)]
+async fn ret_async() -> i32 {
+    42
+}
+
+#[test]
+fn test_async() {
+    let span = span::mock().named("ret_async");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+                .at_level(Level::INFO),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || block_on_future(async { ret_async().await }));
+    handle.assert_finished();
+}
+
+#[instrument(ret)]
+fn ret_impl_type() -> impl Copy {
+    42
+}
+
+#[test]
+fn test_impl_type() {
+    let span = span::mock().named("ret_impl_type");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+                .at_level(Level::INFO),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, ret_impl_type);
+    handle.assert_finished();
+}
+
+#[instrument(ret(Display))]
+fn ret_display() -> i32 {
+    42
+}
+
+#[test]
+fn test_dbg() {
+    let span = span::mock().named("ret_display");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(field::mock("return").with_value(&tracing::field::display(42)))
+                .at_level(Level::INFO),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, ret_display);
+    handle.assert_finished();
+}
+
+#[instrument(err, ret)]
+fn ret_and_err() -> Result<u8, TryFromIntError> {
+    u8::try_from(1234)
+}
+
+#[test]
+fn test_ret_and_err() {
+    let span = span::mock().named("ret_and_err");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(
+                    field::mock("error")
+                        .with_value(&tracing::field::display(u8::try_from(1234).unwrap_err()))
+                        .only(),
+                )
+                .at_level(Level::ERROR),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || ret_and_err().ok());
+    handle.assert_finished();
+}
+
+#[instrument(err, ret)]
+fn ret_and_ok() -> Result<u8, TryFromIntError> {
+    u8::try_from(123)
+}
+
+#[test]
+fn test_ret_and_ok() {
+    let span = span::mock().named("ret_and_ok");
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span.clone())
+        .enter(span.clone())
+        .event(
+            event::mock()
+                .with_fields(
+                    field::mock("return")
+                        .with_value(&tracing::field::debug(u8::try_from(123).unwrap()))
+                        .only(),
+                )
+                .at_level(Level::INFO),
+        )
+        .exit(span.clone())
+        .drop_span(span)
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || ret_and_ok().ok());
+    handle.assert_finished();
+}
diff --git a/tests/targets.rs b/tests/targets.rs
new file mode 100644
index 0000000..363f628
--- /dev/null
+++ b/tests/targets.rs
@@ -0,0 +1,97 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument]
+fn default_target() {}
+
+#[instrument(target = "my_target")]
+fn custom_target() {}
+
+mod my_mod {
+    use tracing_attributes::instrument;
+
+    pub const MODULE_PATH: &str = module_path!();
+
+    #[instrument]
+    pub fn default_target() {}
+
+    #[instrument(target = "my_other_target")]
+    pub fn custom_target() {}
+}
+
+#[test]
+fn default_targets() {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(
+            span::mock()
+                .named("default_target")
+                .with_target(module_path!()),
+        )
+        .enter(
+            span::mock()
+                .named("default_target")
+                .with_target(module_path!()),
+        )
+        .exit(
+            span::mock()
+                .named("default_target")
+                .with_target(module_path!()),
+        )
+        .new_span(
+            span::mock()
+                .named("default_target")
+                .with_target(my_mod::MODULE_PATH),
+        )
+        .enter(
+            span::mock()
+                .named("default_target")
+                .with_target(my_mod::MODULE_PATH),
+        )
+        .exit(
+            span::mock()
+                .named("default_target")
+                .with_target(my_mod::MODULE_PATH),
+        )
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        default_target();
+        my_mod::default_target();
+    });
+
+    handle.assert_finished();
+}
+
+#[test]
+fn custom_targets() {
+    let (subscriber, handle) = subscriber::mock()
+        .new_span(span::mock().named("custom_target").with_target("my_target"))
+        .enter(span::mock().named("custom_target").with_target("my_target"))
+        .exit(span::mock().named("custom_target").with_target("my_target"))
+        .new_span(
+            span::mock()
+                .named("custom_target")
+                .with_target("my_other_target"),
+        )
+        .enter(
+            span::mock()
+                .named("custom_target")
+                .with_target("my_other_target"),
+        )
+        .exit(
+            span::mock()
+                .named("custom_target")
+                .with_target("my_other_target"),
+        )
+        .done()
+        .run_with_handle();
+
+    with_default(subscriber, || {
+        custom_target();
+        my_mod::custom_target();
+    });
+
+    handle.assert_finished();
+}
diff --git a/tests/ui.rs b/tests/ui.rs
new file mode 100644
index 0000000..f11cc01
--- /dev/null
+++ b/tests/ui.rs
@@ -0,0 +1,7 @@
+// Only test on nightly, since UI tests are bound to change over time
+#[rustversion::stable]
+#[test]
+fn async_instrument() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/ui/async_instrument.rs");
+}
diff --git a/tests/ui/async_instrument.rs b/tests/ui/async_instrument.rs
new file mode 100644
index 0000000..5b24574
--- /dev/null
+++ b/tests/ui/async_instrument.rs
@@ -0,0 +1,46 @@
+#![allow(unreachable_code)]
+
+#[tracing::instrument]
+async fn unit() {
+    ""
+}
+
+#[tracing::instrument]
+async fn simple_mismatch() -> String {
+    ""
+}
+
+// FIXME: this span is still pretty poor
+#[tracing::instrument]
+async fn opaque_unsatisfied() -> impl std::fmt::Display {
+    ("",)
+}
+
+struct Wrapper<T>(T);
+
+#[tracing::instrument]
+async fn mismatch_with_opaque() -> Wrapper<impl std::fmt::Display> {
+    ""
+}
+
+#[tracing::instrument]
+async fn early_return_unit() {
+    if true {
+        return "";
+    }
+}
+
+#[tracing::instrument]
+async fn early_return() -> String {
+    if true {
+        return "";
+    }
+    String::new()
+}
+
+#[tracing::instrument]
+async fn extra_semicolon() -> i32 {
+    1;
+}
+
+fn main() {}
diff --git a/tests/ui/async_instrument.stderr b/tests/ui/async_instrument.stderr
new file mode 100644
index 0000000..db6f6b4
--- /dev/null
+++ b/tests/ui/async_instrument.stderr
@@ -0,0 +1,98 @@
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:5:5
+  |
+5 |     ""
+  |     ^^ expected `()`, found `&str`
+  |
+note: return type inferred to be `()` here
+ --> tests/ui/async_instrument.rs:4:10
+  |
+4 | async fn unit() {
+  |          ^^^^
+
+error[E0308]: mismatched types
+  --> tests/ui/async_instrument.rs:10:5
+   |
+10 |     ""
+   |     ^^- help: try using a conversion method: `.to_string()`
+   |     |
+   |     expected struct `String`, found `&str`
+   |
+note: return type inferred to be `String` here
+  --> tests/ui/async_instrument.rs:9:31
+   |
+9  | async fn simple_mismatch() -> String {
+   |                               ^^^^^^
+
+error[E0277]: `(&str,)` doesn't implement `std::fmt::Display`
+  --> tests/ui/async_instrument.rs:14:1
+   |
+14 | #[tracing::instrument]
+   | ^^^^^^^^^^^^^^^^^^^^^^ `(&str,)` cannot be formatted with the default formatter
+   |
+   = help: the trait `std::fmt::Display` is not implemented for `(&str,)`
+   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
+   = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: `(&str,)` doesn't implement `std::fmt::Display`
+  --> tests/ui/async_instrument.rs:15:34
+   |
+15 | async fn opaque_unsatisfied() -> impl std::fmt::Display {
+   |                                  ^^^^^^^^^^^^^^^^^^^^^^ `(&str,)` cannot be formatted with the default formatter
+   |
+   = help: the trait `std::fmt::Display` is not implemented for `(&str,)`
+   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
+
+error[E0308]: mismatched types
+  --> tests/ui/async_instrument.rs:23:5
+   |
+23 |     ""
+   |     ^^ expected struct `Wrapper`, found `&str`
+   |
+   = note: expected struct `Wrapper<_>`
+           found reference `&'static str`
+note: return type inferred to be `Wrapper<_>` here
+  --> tests/ui/async_instrument.rs:22:36
+   |
+22 | async fn mismatch_with_opaque() -> Wrapper<impl std::fmt::Display> {
+   |                                    ^^^^^^^
+help: try wrapping the expression in `Wrapper`
+   |
+23 |     Wrapper("")
+   |     ++++++++  +
+
+error[E0308]: mismatched types
+  --> tests/ui/async_instrument.rs:29:16
+   |
+29 |         return "";
+   |                ^^ expected `()`, found `&str`
+   |
+note: return type inferred to be `()` here
+  --> tests/ui/async_instrument.rs:27:10
+   |
+27 | async fn early_return_unit() {
+   |          ^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+  --> tests/ui/async_instrument.rs:36:16
+   |
+36 |         return "";
+   |                ^^- help: try using a conversion method: `.to_string()`
+   |                |
+   |                expected struct `String`, found `&str`
+   |
+note: return type inferred to be `String` here
+  --> tests/ui/async_instrument.rs:34:28
+   |
+34 | async fn early_return() -> String {
+   |                            ^^^^^^
+
+error[E0308]: mismatched types
+  --> tests/ui/async_instrument.rs:42:35
+   |
+42 |   async fn extra_semicolon() -> i32 {
+   |  ___________________________________^
+43 | |     1;
+   | |      - help: remove this semicolon
+44 | | }
+   | |_^ expected `i32`, found `()`