diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index 06056d6ed2040..16a3aecfc186b 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -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 + match $val { + tmp => { + eprintln!("[{}:{}] {} = {:#?}", + file!(), line!(), stringify!($val), &tmp); + tmp + } + } + } +} + #[macro_export] #[unstable(feature = "await_macro", issue = "50547")] #[allow_internal_unstable] diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-expected-behavior.rs b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-expected-behavior.rs new file mode 100644 index 0000000000000..f7216c57e42b8 --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-expected-behavior.rs @@ -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 { + 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` will be faithfully formatted according to `{:#?}`. + let a = Point { x: 42, y: 24 }; + let b: Point = dbg!(Point { x: 42, y: 24 }); // test stringify!(..) + let c: Point = 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) { + 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); + } +} diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.rs b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.rs new file mode 100644 index 0000000000000..b237c6f147bf7 --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.rs @@ -0,0 +1,5 @@ +// Feature gate test for `dbg!(..)`. + +fn main() { + dbg!(1); +} diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.stderr b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.stderr new file mode 100644 index 0000000000000..64df1e196d285 --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-feature-gate.stderr @@ -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`. diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.nll.stderr b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.nll.stderr new file mode 100644 index 0000000000000..bf99fef3bbd2f --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.nll.stderr @@ -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`. diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.rs b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.rs new file mode 100644 index 0000000000000..bcf508d9af5d7 --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.rs @@ -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); +} diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr new file mode 100644 index 0000000000000..1064317438515 --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr @@ -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`. diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.rs b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.rs new file mode 100644 index 0000000000000..8e6f3b226fc1e --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.rs @@ -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); +} diff --git a/src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.stderr b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.stderr new file mode 100644 index 0000000000000..a3b6a1761b991 --- /dev/null +++ b/src/test/ui/rfc-2361-dbg-macro/dbg-macro-requires-debug.stderr @@ -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`.