Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the dbg!(..) macro #54317

Merged
merged 10 commits into from
Sep 25, 2018
120 changes: 120 additions & 0 deletions src/libstd/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,126 @@ macro_rules! eprintln {
})
}

/// A macro for quick and dirty debugging with which you can inspect
/// the value of a given expression. An example:
///
/// ```rust
/// #![feature(dbg_macro)]
///
/// let a = 2;
/// let b = dbg!(a * 2) + 1;
/// // ^-- prints: [src/main.rs:4] a * 2 = 4
/// assert_eq!(b, 5);
/// ```
///
/// The macro works by using the `Debug` implementation of the type of
/// the given expression to print the value to [stderr] along with the
/// source location of the macro invocation as well as the source code
/// of the expression.
///
/// Invoking the macro on an expression moves and takes ownership of it
/// before returning the evaluated expression unchanged. If the type
/// of the expression does not implement `Copy` and you don't want
/// to give up ownership, you can instead borrow with `dbg!(&expr)`
/// for some expression `expr`.
///
/// Note that the macro is intended as a debugging tool and therefore you
/// should avoid having uses of it in version control for longer periods.
/// Use cases involving debug output that should be added to version control
/// may be better served by macros such as `debug!` from the `log` crate.
///
/// # Stability
///
/// The exact output printed by this macro should not be relied upon
/// and is subject to future changes.
///
/// # Panics
///
/// Panics if writing to `io::stderr` fails.
///
/// # Further examples
///
/// With a method call:
///
/// ```rust
/// #![feature(dbg_macro)]
///
/// fn foo(n: usize) {
/// if let Some(_) = dbg!(n.checked_sub(4)) {
/// // ...
/// }
/// }
///
/// foo(3)
/// ```
///
/// This prints to [stderr]:
///
/// ```text,ignore
/// [src/main.rs:4] n.checked_sub(4) = None
/// ```
///
/// Naive factorial implementation:
///
/// ```rust
/// #![feature(dbg_macro)]
///
/// fn factorial(n: u32) -> u32 {
/// if dbg!(n <= 1) {
/// dbg!(1)
/// } else {
/// dbg!(n * factorial(n - 1))
/// }
/// }
///
/// dbg!(factorial(4));
/// ```
///
/// This prints to [stderr]:
///
/// ```text,ignore
/// [src/main.rs:3] n <= 1 = false
/// [src/main.rs:3] n <= 1 = false
/// [src/main.rs:3] n <= 1 = false
/// [src/main.rs:3] n <= 1 = true
/// [src/main.rs:4] 1 = 1
/// [src/main.rs:5] n * factorial(n - 1) = 2
/// [src/main.rs:5] n * factorial(n - 1) = 6
/// [src/main.rs:5] n * factorial(n - 1) = 24
/// [src/main.rs:11] factorial(4) = 24
/// ```
///
/// The `dbg!(..)` macro moves the input:
///
/// ```compile_fail
/// #![feature(dbg_macro)]
///
/// /// A wrapper around `usize` which importantly is not Copyable.
/// #[derive(Debug)]
/// struct NoCopy(usize);
///
/// let a = NoCopy(42);
/// let _ = dbg!(a); // <-- `a` is moved here.
/// let _ = dbg!(a); // <-- `a` is moved again; error!
/// ```
///
/// [stderr]: https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)
#[macro_export]
#[unstable(feature = "dbg_macro", issue = "54306")]
macro_rules! dbg {
($val:expr) => {
// Use of `match` here is intentional because it affects the lifetimes
// of temporaries - https://stackoverflow.com/a/48732525/1063961
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a Stack Overflow the best kind of link for this documentation? may be worth "archiving" as a blog post somewhere on the forge for example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can always update this to something better later -- I'd welcome a follow up PR if you have ideas :)
I think it is sufficient for now.

match $val {
tmp => {
eprintln!("[{}:{}] {} = {:#?}",
file!(), line!(), stringify!($val), &tmp);
tmp
}
}
}
}

#[macro_export]
#[unstable(feature = "await_macro", issue = "50547")]
#[allow_internal_unstable]
Expand Down
121 changes: 121 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-expected-behavior.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// run-pass
// ignore-cloudabi no processes
// ignore-emscripten no processes

// Tests ensuring that `dbg!(expr)` has the expected run-time behavior.
// as well as some compile time properties we expect.

#![feature(dbg_macro)]

#[derive(Copy, Clone, Debug)]
struct Unit;

#[derive(Copy, Clone, Debug, PartialEq)]
struct Point<T> {
x: T,
y: T,
}

#[derive(Debug, PartialEq)]
struct NoCopy(usize);

fn test() {
let a: Unit = dbg!(Unit);
let _: Unit = dbg!(a);
// We can move `a` because it's Copy.
drop(a);

// `Point<T>` will be faithfully formatted according to `{:#?}`.
let a = Point { x: 42, y: 24 };
let b: Point<u8> = dbg!(Point { x: 42, y: 24 }); // test stringify!(..)
let c: Point<u8> = dbg!(b);
// Identity conversion:
assert_eq!(a, b);
assert_eq!(a, c);
// We can move `b` because it's Copy.
drop(b);

// Test that we can borrow and that successive applications is still identity.
let a = NoCopy(1337);
let b: &NoCopy = dbg!(dbg!(&a));
assert_eq!(&a, b);

// Test involving lifetimes of temporaries:
fn f<'a>(x: &'a u8) -> &'a u8 { x }
let a: &u8 = dbg!(f(&42));
assert_eq!(a, &42);

// Test side effects:
let mut foo = 41;
assert_eq!(7331, dbg!({
foo += 1;
eprintln!("before");
7331
}));
assert_eq!(foo, 42);
}

fn validate_stderr(stderr: Vec<String>) {
assert_eq!(stderr, &[
":23] Unit = Unit",

":24] a = Unit",

":30] Point{x: 42, y: 24,} = Point {",
" x: 42,",
" y: 24",
"}",

":31] b = Point {",
" x: 42,",
" y: 24",
"}",

":40] &a = NoCopy(",
" 1337",
")",

":40] dbg!(& a) = NoCopy(",
" 1337",
")",
":45] f(&42) = 42",

"before",
":50] { foo += 1; eprintln!(\"before\"); 7331 } = 7331",
]);
}

fn main() {
// The following is a hack to deal with compiletest's inability
// to check the output (to stdout) of run-pass tests.
use std::env;
use std::process::Command;

let mut args = env::args();
let prog = args.next().unwrap();
let child = args.next();
if let Some("child") = child.as_ref().map(|s| &**s) {
// Only run the test if we've been spawned as 'child'
test()
} else {
// This essentially spawns as 'child' to run the tests
// and then it collects output of stderr and checks the output
// against what we expect.
let out = Command::new(&prog).arg("child").output().unwrap();
assert!(out.status.success());
assert!(out.stdout.is_empty());

let stderr = String::from_utf8(out.stderr).unwrap();
let stderr = stderr.lines().map(|mut s| {
if s.starts_with("[") {
// Strip `[` and file path:
s = s.trim_start_matches("[");
assert!(s.starts_with(file!()));
s = s.trim_start_matches(file!());
}
s.to_owned()
}).collect();

validate_stderr(stderr);
}
}
5 changes: 5 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Feature gate test for `dbg!(..)`.

fn main() {
dbg!(1);
}
11 changes: 11 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error[E0658]: macro dbg! is unstable (see issue #54306)
--> $DIR/dbg-macro-feature-gate.rs:4:5
|
LL | dbg!(1);
| ^^^^^^^^
|
= help: add #![feature(dbg_macro)] to the crate attributes to enable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0658`.
14 changes: 14 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.nll.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0382]: use of moved value: `a`
--> $DIR/dbg-macro-move-semantics.rs:11:18
|
LL | let _ = dbg!(a);
| ------- value moved here
LL | let _ = dbg!(a);
| ^ value used here after move
|
= note: move occurs because `a` has type `NoCopy`, which does not implement the `Copy` trait
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
12 changes: 12 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Test ensuring that `dbg!(expr)` will take ownership of the argument.

#![feature(dbg_macro)]

#[derive(Debug)]
struct NoCopy(usize);

fn main() {
let a = NoCopy(0);
let _ = dbg!(a);
let _ = dbg!(a);
}
25 changes: 25 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error[E0382]: use of moved value: `a`
--> $DIR/dbg-macro-move-semantics.rs:11:18
|
LL | let _ = dbg!(a);
| ------- value moved here
LL | let _ = dbg!(a);
| ^ value used here after move
|
= note: move occurs because `a` has type `NoCopy`, which does not implement the `Copy` trait
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error[E0382]: use of moved value: `a`
--> $DIR/dbg-macro-move-semantics.rs:11:13
|
LL | let _ = dbg!(a);
| ------- value moved here
LL | let _ = dbg!(a);
| ^^^^^^^ value used here after move
|
= note: move occurs because `a` has type `NoCopy`, which does not implement the `Copy` trait
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0382`.
9 changes: 9 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Test ensuring that `dbg!(expr)` requires the passed type to implement `Debug`.

#![feature(dbg_macro)]

struct NotDebug;

fn main() {
let _: NotDebug = dbg!(NotDebug);
}
15 changes: 15 additions & 0 deletions src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0277]: `NotDebug` doesn't implement `std::fmt::Debug`
--> $DIR/dbg-macro-requires-debug.rs:8:23
|
LL | let _: NotDebug = dbg!(NotDebug);
| ^^^^^^^^^^^^^^ `NotDebug` cannot be formatted using `{:?}`
|
= help: the trait `std::fmt::Debug` is not implemented for `NotDebug`
= note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`
= note: required because of the requirements on the impl of `std::fmt::Debug` for `&NotDebug`
= note: required by `std::fmt::Debug::fmt`
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.