about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-02-28 07:10:05 +0000
committerbors <bors@rust-lang.org>2018-02-28 07:10:05 +0000
commitddab91a5debadfda47c057117c8b498a31abaae7 (patch)
treeedc968b35701226774a6865271ee8bdcee15093e
parent89e5a0796e0c5de52cd1d6c7541e5c2c7c47f2cd (diff)
parentaf503be3685ae925edefe96d0f160369520e7b5a (diff)
downloadrust-ddab91a5debadfda47c057117c8b498a31abaae7.tar.gz
rust-ddab91a5debadfda47c057117c8b498a31abaae7.zip
Auto merge of #48056 - ExpHP:macro-commas, r=dtolnay
Comprehensively support trailing commas in std/core macros

I carefully organized the changes into four commits:

* Test cases
* Fixes for `macro_rules!` macros
* Fixes for builtin macros
* Docs for builtins

**I can easily scale this back to just the first two commits for now if such is desired.**

### Breaking (?) changes

* This fixes #48042, which is a breaking change that I hope people can agree is just a bugfix for an extremely dark corner case.

* To fix five of the builtins, this changes `syntax::ext::base::get_single_str_from_tts` to accept a trailing comma, and revises the documentation so that this aspect is not surprising. **I made this change under the (hopefully correct) understanding that `libsyntax` is private rustc implementation detail.** After reviewing all call sites (which were, you guessed it, *precisely those five macros*), I believe the revised semantics are closer to the intended spirit of the function.

### Changes which may require concensus

Up until now, it could be argued that some or all the following macros did not conceptually take a comma-separated list, because they only took one argument:

  * **`cfg(unix,)`** (most notable since cfg! is unique in taking a meta tag)
  * **`include{,_bytes,_str}("file.rs",)`**  (in item form this might be written as "`include!{"file.rs",}`" which is even slightly more odd)
  * **`compile_error("message",);`**
  * **`option_env!("PATH",)`**
  * **`try!(Ok(()),)`**

So I think these particular changes may require some sort of consensus.  **All of the fixes for builtins are included this list, so if we want to defer these decisions to later then I can scale this PR back to just the first two commits.**

### Other notes/general requests for comment

* Do we have a big checklist somewhere of "things to do when adding macros?" My hope is for `run-pass/macro-comma-support.rs` to remain comprehensive.
* Originally I wanted the tests to also comprehensively forbid double trailing commas.  However, this didn't work out too well: [see this gist and the giant FIXME in it](https://gist.github.com/ExpHP/6fc40e82f3d73267c4e590a9a94966f1#file-compile-fail_macro-comma-support-rs-L33-L50)
* I did not touch `select!`. It appears to me to be a complete mess, and its trailing comma mishaps are only the tip of the iceberg.
* There are [some compile-fail test cases](https://github.com/ExpHP/rust/blob/5fa97c35da2f0ee/src/test/compile-fail/macro-comma-behavior.rs#L49-L52) that didn't seem to work (rustc emits errors, but compile-fail doesn't acknowledge them), so they are disabled. Any clues? (Possibly related: These happen to be precisely the set of errors which are tagged by rustc as "this error originates in a macro outside of the current crate".)

---

Fixes #48042
Closes #46241
-rw-r--r--src/libcore/macros.rs42
-rw-r--r--src/libstd/macros.rs28
-rw-r--r--src/libsyntax/ext/base.rs6
-rw-r--r--src/libsyntax_ext/cfg.rs2
-rw-r--r--src/test/compile-fail/macro-comma-behavior.rs101
-rw-r--r--src/test/compile-fail/macro-comma-support.rs20
-rw-r--r--src/test/run-pass/auxiliary/macro-comma-support.rs11
-rw-r--r--src/test/run-pass/macro-comma-behavior.rs98
-rw-r--r--src/test/run-pass/macro-comma-support.rs359
9 files changed, 653 insertions, 14 deletions
diff --git a/src/libcore/macros.rs b/src/libcore/macros.rs
index cc5cf6523a9..52eb9f29d57 100644
--- a/src/libcore/macros.rs
+++ b/src/libcore/macros.rs
@@ -19,7 +19,10 @@ macro_rules! panic {
     ($msg:expr) => ({
         $crate::panicking::panic(&($msg, file!(), line!(), __rust_unstable_column!()))
     });
-    ($fmt:expr, $($arg:tt)*) => ({
+    ($msg:expr,) => (
+        panic!($msg)
+    );
+    ($fmt:expr, $($arg:tt)+) => ({
         $crate::panicking::panic_fmt(format_args!($fmt, $($arg)*),
                                      &(file!(), line!(), __rust_unstable_column!()))
     });
@@ -79,6 +82,9 @@ macro_rules! assert {
             panic!(concat!("assertion failed: ", stringify!($cond)))
         }
     );
+    ($cond:expr,) => (
+        assert!($cond)
+    );
     ($cond:expr, $($arg:tt)+) => (
         if !$cond {
             panic!($($arg)+)
@@ -359,7 +365,8 @@ macro_rules! try {
         $crate::result::Result::Err(err) => {
             return $crate::result::Result::Err($crate::convert::From::from(err))
         }
-    })
+    });
+    ($expr:expr,) => (try!($expr));
 }
 
 /// Write formatted data into a buffer.
@@ -456,6 +463,9 @@ macro_rules! writeln {
     ($dst:expr) => (
         write!($dst, "\n")
     );
+    ($dst:expr,) => (
+        writeln!($dst)
+    );
     ($dst:expr, $fmt:expr) => (
         write!($dst, concat!($fmt, "\n"))
     );
@@ -524,6 +534,9 @@ macro_rules! unreachable {
     ($msg:expr) => ({
         unreachable!("{}", $msg)
     });
+    ($msg:expr,) => ({
+        unreachable!($msg)
+    });
     ($fmt:expr, $($arg:tt)*) => ({
         panic!(concat!("internal error: entered unreachable code: ", $fmt), $($arg)*)
     });
@@ -603,7 +616,10 @@ mod builtin {
     #[stable(feature = "compile_error_macro", since = "1.20.0")]
     #[macro_export]
     #[cfg(dox)]
-    macro_rules! compile_error { ($msg:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! compile_error {
+        ($msg:expr) => ({ /* compiler built-in */ });
+        ($msg:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// The core macro for formatted string creation & output.
     ///
@@ -639,7 +655,10 @@ mod builtin {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
     #[cfg(dox)]
-    macro_rules! option_env { ($name:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! option_env {
+        ($name:expr) => ({ /* compiler built-in */ });
+        ($name:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// Concatenate identifiers into one identifier.
     ///
@@ -715,7 +734,10 @@ mod builtin {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
     #[cfg(dox)]
-    macro_rules! include_str { ($file:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! include_str {
+        ($file:expr) => ({ /* compiler built-in */ });
+        ($file:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// Includes a file as a reference to a byte array.
     ///
@@ -725,7 +747,10 @@ mod builtin {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
     #[cfg(dox)]
-    macro_rules! include_bytes { ($file:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! include_bytes {
+        ($file:expr) => ({ /* compiler built-in */ });
+        ($file:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// Expands to a string that represents the current module path.
     ///
@@ -755,5 +780,8 @@ mod builtin {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
     #[cfg(dox)]
-    macro_rules! include { ($file:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! include {
+        ($file:expr) => ({ /* compiler built-in */ });
+        ($file:expr,) => ({ /* compiler built-in */ });
+    }
 }
diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs
index f058b1caef5..a18c811d196 100644
--- a/src/libstd/macros.rs
+++ b/src/libstd/macros.rs
@@ -68,6 +68,9 @@ macro_rules! panic {
     ($msg:expr) => ({
         $crate::rt::begin_panic($msg, &(file!(), line!(), __rust_unstable_column!()))
     });
+    ($msg:expr,) => ({
+        panic!($msg)
+    });
     ($fmt:expr, $($arg:tt)+) => ({
         $crate::rt::begin_panic_fmt(&format_args!($fmt, $($arg)+),
                                     &(file!(), line!(), __rust_unstable_column!()))
@@ -312,7 +315,10 @@ pub mod builtin {
     /// ```
     #[stable(feature = "compile_error_macro", since = "1.20.0")]
     #[macro_export]
-    macro_rules! compile_error { ($msg:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! compile_error {
+        ($msg:expr) => ({ /* compiler built-in */ });
+        ($msg:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// The core macro for formatted string creation & output.
     ///
@@ -400,7 +406,10 @@ pub mod builtin {
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
-    macro_rules! option_env { ($name:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! option_env {
+        ($name:expr) => ({ /* compiler built-in */ });
+        ($name:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// Concatenate identifiers into one identifier.
     ///
@@ -580,7 +589,10 @@ pub mod builtin {
     /// Compiling 'main.rs' and running the resulting binary will print "adiรณs".
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
-    macro_rules! include_str { ($file:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! include_str {
+        ($file:expr) => ({ /* compiler built-in */ });
+        ($file:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// Includes a file as a reference to a byte array.
     ///
@@ -614,7 +626,10 @@ pub mod builtin {
     /// Compiling 'main.rs' and running the resulting binary will print "adiรณs".
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
-    macro_rules! include_bytes { ($file:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! include_bytes {
+        ($file:expr) => ({ /* compiler built-in */ });
+        ($file:expr,) => ({ /* compiler built-in */ });
+    }
 
     /// Expands to a string that represents the current module path.
     ///
@@ -700,7 +715,10 @@ pub mod builtin {
     /// "๐Ÿ™ˆ๐Ÿ™Š๐Ÿ™‰๐Ÿ™ˆ๐Ÿ™Š๐Ÿ™‰".
     #[stable(feature = "rust1", since = "1.0.0")]
     #[macro_export]
-    macro_rules! include { ($file:expr) => ({ /* compiler built-in */ }) }
+    macro_rules! include {
+        ($file:expr) => ({ /* compiler built-in */ });
+        ($file:expr,) => ({ /* compiler built-in */ });
+    }
 }
 
 /// A macro for defining #[cfg] if-else statements.
diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs
index 025aa94ce06..520ec942e42 100644
--- a/src/libsyntax/ext/base.rs
+++ b/src/libsyntax/ext/base.rs
@@ -890,8 +890,8 @@ pub fn check_zero_tts(cx: &ExtCtxt,
     }
 }
 
-/// Extract the string literal from the first token of `tts`. If this
-/// is not a string literal, emit an error and return None.
+/// Interpreting `tts` as a comma-separated sequence of expressions,
+/// expect exactly one string literal, or emit an error and return None.
 pub fn get_single_str_from_tts(cx: &mut ExtCtxt,
                                sp: Span,
                                tts: &[tokenstream::TokenTree],
@@ -903,6 +903,8 @@ pub fn get_single_str_from_tts(cx: &mut ExtCtxt,
         return None
     }
     let ret = panictry!(p.parse_expr());
+    let _ = p.eat(&token::Comma);
+
     if p.token != token::Eof {
         cx.span_err(sp, &format!("{} takes 1 argument", name));
     }
diff --git a/src/libsyntax_ext/cfg.rs b/src/libsyntax_ext/cfg.rs
index 1d8dc406468..1eeba9b30b8 100644
--- a/src/libsyntax_ext/cfg.rs
+++ b/src/libsyntax_ext/cfg.rs
@@ -28,6 +28,8 @@ pub fn expand_cfg<'cx>(cx: &mut ExtCtxt,
     let mut p = cx.new_parser_from_tts(tts);
     let cfg = panictry!(p.parse_meta_item());
 
+    let _ = p.eat(&token::Comma);
+
     if !p.eat(&token::Eof) {
         cx.span_err(sp, "expected 1 cfg-pattern");
         return DummyResult::expr(sp);
diff --git a/src/test/compile-fail/macro-comma-behavior.rs b/src/test/compile-fail/macro-comma-behavior.rs
new file mode 100644
index 00000000000..620e57b463d
--- /dev/null
+++ b/src/test/compile-fail/macro-comma-behavior.rs
@@ -0,0 +1,101 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Companion test to the similarly-named file in run-pass.
+
+// compile-flags: -C debug_assertions=yes
+// revisions: std core
+
+#![cfg_attr(core, no_std)]
+
+#[cfg(std)] use std::fmt;
+#[cfg(core)] use core::fmt;
+
+// (see documentation of the similarly-named test in run-pass)
+fn to_format_or_not_to_format() {
+    let falsum = || false;
+
+    // assert!(true, "{}",); // see run-pass
+
+    assert_eq!(1, 1, "{}",);
+    //[core]~^ ERROR no arguments
+    //[std]~^^ ERROR no arguments
+    assert_ne!(1, 2, "{}",);
+    //[core]~^ ERROR no arguments
+    //[std]~^^ ERROR no arguments
+
+    // debug_assert!(true, "{}",); // see run-pass
+
+    debug_assert_eq!(1, 1, "{}",);
+    //[core]~^ ERROR no arguments
+    //[std]~^^ ERROR no arguments
+    debug_assert_ne!(1, 2, "{}",);
+    //[core]~^ ERROR no arguments
+    //[std]~^^ ERROR no arguments
+
+    #[cfg(std)] {
+        eprint!("{}",);
+        //[std]~^ ERROR no arguments
+    }
+
+    #[cfg(std)] {
+        // FIXME: compile-fail says "expected error not found" even though
+        //        rustc does emit an error
+        // eprintln!("{}",);
+        // <DISABLED> [std]~^ ERROR no arguments
+    }
+
+    #[cfg(std)] {
+        format!("{}",);
+        //[std]~^ ERROR no arguments
+    }
+
+    format_args!("{}",);
+    //[core]~^ ERROR no arguments
+    //[std]~^^ ERROR no arguments
+
+    // if falsum() { panic!("{}",); } // see run-pass
+
+    #[cfg(std)] {
+        print!("{}",);
+        //[std]~^ ERROR no arguments
+    }
+
+    #[cfg(std)] {
+        // FIXME: compile-fail says "expected error not found" even though
+        //        rustc does emit an error
+        // println!("{}",);
+        // <DISABLED> [std]~^ ERROR no arguments
+    }
+
+    unimplemented!("{}",);
+    //[core]~^ ERROR no arguments
+    //[std]~^^ ERROR no arguments
+
+    // if falsum() { unreachable!("{}",); } // see run-pass
+
+    struct S;
+    impl fmt::Display for S {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            write!(f, "{}",)?;
+            //[core]~^ ERROR no arguments
+            //[std]~^^ ERROR no arguments
+
+            // FIXME: compile-fail says "expected error not found" even though
+            //        rustc does emit an error
+            // writeln!(f, "{}",)?;
+            // <DISABLED> [core]~^ ERROR no arguments
+            // <DISABLED> [std]~^^ ERROR no arguments
+            Ok(())
+        }
+    }
+}
+
+fn main() {}
diff --git a/src/test/compile-fail/macro-comma-support.rs b/src/test/compile-fail/macro-comma-support.rs
new file mode 100644
index 00000000000..e5fe9b4dd7f
--- /dev/null
+++ b/src/test/compile-fail/macro-comma-support.rs
@@ -0,0 +1,20 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// This is a companion to the similarly-named test in run-pass.
+//
+// It tests macros that unavoidably produce compile errors.
+
+fn compile_error() {
+    compile_error!("lel"); //~ ERROR lel
+    compile_error!("lel",); //~ ERROR lel
+}
+
+fn main() {}
diff --git a/src/test/run-pass/auxiliary/macro-comma-support.rs b/src/test/run-pass/auxiliary/macro-comma-support.rs
new file mode 100644
index 00000000000..6eafd520a72
--- /dev/null
+++ b/src/test/run-pass/auxiliary/macro-comma-support.rs
@@ -0,0 +1,11 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+()
diff --git a/src/test/run-pass/macro-comma-behavior.rs b/src/test/run-pass/macro-comma-behavior.rs
new file mode 100644
index 00000000000..2a434009e13
--- /dev/null
+++ b/src/test/run-pass/macro-comma-behavior.rs
@@ -0,0 +1,98 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Ideally, any macro call with a trailing comma should behave
+// identically to a call without the comma.
+//
+// This checks the behavior of macros with trailing commas in key
+// places where regressions in behavior seem highly possible (due
+// to it being e.g. a place where the addition of an argument
+// causes it to go down a code path with subtly different behavior).
+//
+// There is a companion test in compile-fail.
+
+// compile-flags: --test -C debug_assertions=yes
+// revisions: std core
+
+#![cfg_attr(core, no_std)]
+
+#[cfg(std)] use std::fmt;
+#[cfg(core)] use core::fmt;
+
+// an easy mistake in the implementation of 'assert!'
+// would cause this to say "explicit panic"
+#[test]
+#[should_panic(expected = "assertion failed")]
+fn assert_1arg() {
+    assert!(false,);
+}
+
+// same as 'assert_1arg'
+#[test]
+#[should_panic(expected = "assertion failed")]
+fn debug_assert_1arg() {
+    debug_assert!(false,);
+}
+
+// make sure we don't accidentally forward to `write!("text")`
+#[cfg(std)]
+#[test]
+fn writeln_1arg() {
+    use fmt::Write;
+
+    let mut s = String::new();
+    writeln!(&mut s,).unwrap();
+    assert_eq!(&s, "\n");
+}
+
+// A number of format_args-like macros have special-case treatment
+// for a single message string, which is not formatted.
+//
+// This test ensures that the addition of a trailing comma does not
+// suddenly cause these strings to get formatted when they otherwise
+// would not be. This is an easy mistake to make by having such a macro
+// accept ", $($tok:tt)*" instead of ", $($tok:tt)+" after its minimal
+// set of arguments.
+//
+// (Example: Issue #48042)
+#[test]
+fn to_format_or_not_to_format() {
+    // ("{}" is the easiest string to test because if this gets
+    // sent to format_args!, it'll simply fail to compile.
+    // "{{}}" is an example of an input that could compile and
+    // produce an incorrect program, but testing the panics
+    // would be burdensome.)
+    let falsum = || false;
+
+    assert!(true, "{}",);
+
+    // assert_eq!(1, 1, "{}",); // see compile-fail
+    // assert_ne!(1, 2, "{}",); // see compile-fail
+
+    debug_assert!(true, "{}",);
+
+    // debug_assert_eq!(1, 1, "{}",); // see compile-fail
+    // debug_assert_ne!(1, 2, "{}",); // see compile-fail
+    // eprint!("{}",); // see compile-fail
+    // eprintln!("{}",); // see compile-fail
+    // format!("{}",); // see compile-fail
+    // format_args!("{}",); // see compile-fail
+
+    if falsum() { panic!("{}",); }
+
+    // print!("{}",); // see compile-fail
+    // println!("{}",); // see compile-fail
+    // unimplemented!("{}",); // see compile-fail
+
+    if falsum() { unreachable!("{}",); }
+
+    // write!(&mut stdout, "{}",); // see compile-fail
+    // writeln!(&mut stdout, "{}",); // see compile-fail
+}
diff --git a/src/test/run-pass/macro-comma-support.rs b/src/test/run-pass/macro-comma-support.rs
new file mode 100644
index 00000000000..bfd911002a0
--- /dev/null
+++ b/src/test/run-pass/macro-comma-support.rs
@@ -0,0 +1,359 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// This is meant to be a comprehensive test of invocations with/without
+// trailing commas (or other, similar optionally-trailing separators).
+// Every macro is accounted for, even those not tested in this file.
+// (There will be a note indicating why).
+
+// std and core are both tested because they may contain separate
+// implementations for some macro_rules! macros as an implementation
+// detail.
+
+// ignore-pretty issue #37195
+
+// compile-flags: --test -C debug_assertions=yes
+// revisions: std core
+
+#![cfg_attr(core, no_std)]
+
+#![feature(concat_idents)]
+
+#[cfg(std)] use std::fmt;
+#[cfg(core)] use core::fmt;
+
+#[test]
+fn assert() {
+    assert!(true);
+    assert!(true,);
+    assert!(true, "hello");
+    assert!(true, "hello",);
+    assert!(true, "hello {}", "world");
+    assert!(true, "hello {}", "world",);
+}
+
+#[test]
+fn assert_eq() {
+    assert_eq!(1, 1);
+    assert_eq!(1, 1,);
+    assert_eq!(1, 1, "hello");
+    assert_eq!(1, 1, "hello",);
+    assert_eq!(1, 1, "hello {}", "world");
+    assert_eq!(1, 1, "hello {}", "world",);
+}
+
+#[test]
+fn assert_ne() {
+    assert_ne!(1, 2);
+    assert_ne!(1, 2,);
+    assert_ne!(1, 2, "hello");
+    assert_ne!(1, 2, "hello",);
+    assert_ne!(1, 2, "hello {}", "world");
+    assert_ne!(1, 2, "hello {}", "world",);
+}
+
+#[test]
+fn cfg() {
+    let _ = cfg!(pants);
+    let _ = cfg!(pants,);
+    let _ = cfg!(pants = "pants");
+    let _ = cfg!(pants = "pants",);
+    let _ = cfg!(all(pants));
+    let _ = cfg!(all(pants),);
+    let _ = cfg!(all(pants,));
+    let _ = cfg!(all(pants,),);
+}
+
+#[test]
+fn column() {
+    let _ = column!();
+}
+
+// compile_error! is in a companion to this test in compile-fail
+
+#[test]
+fn concat() {
+    let _ = concat!();
+    let _ = concat!("hello");
+    let _ = concat!("hello",);
+    let _ = concat!("hello", " world");
+    let _ = concat!("hello", " world",);
+}
+
+#[test]
+fn concat_idents() {
+    fn foo() {}
+    fn foobar() {}
+
+    concat_idents!(foo)();
+    concat_idents!(foo,)();
+    concat_idents!(foo, bar)();
+    concat_idents!(foo, bar,)();
+}
+
+#[test]
+fn debug_assert() {
+    debug_assert!(true);
+    debug_assert!(true, );
+    debug_assert!(true, "hello");
+    debug_assert!(true, "hello",);
+    debug_assert!(true, "hello {}", "world");
+    debug_assert!(true, "hello {}", "world",);
+}
+
+#[test]
+fn debug_assert_eq() {
+    debug_assert_eq!(1, 1);
+    debug_assert_eq!(1, 1,);
+    debug_assert_eq!(1, 1, "hello");
+    debug_assert_eq!(1, 1, "hello",);
+    debug_assert_eq!(1, 1, "hello {}", "world");
+    debug_assert_eq!(1, 1, "hello {}", "world",);
+}
+
+#[test]
+fn debug_assert_ne() {
+    debug_assert_ne!(1, 2);
+    debug_assert_ne!(1, 2,);
+    debug_assert_ne!(1, 2, "hello");
+    debug_assert_ne!(1, 2, "hello",);
+    debug_assert_ne!(1, 2, "hello {}", "world");
+    debug_assert_ne!(1, 2, "hello {}", "world",);
+}
+
+#[test]
+fn env() {
+    let _ = env!("PATH");
+    let _ = env!("PATH",);
+    let _ = env!("PATH", "not found");
+    let _ = env!("PATH", "not found",);
+}
+
+#[cfg(std)]
+#[test]
+fn eprint() {
+    eprint!("hello");
+    eprint!("hello",);
+    eprint!("hello {}", "world");
+    eprint!("hello {}", "world",);
+}
+
+#[cfg(std)]
+#[test]
+fn eprintln() {
+    eprintln!();
+    eprintln!("hello");
+    eprintln!("hello",);
+    eprintln!("hello {}", "world");
+    eprintln!("hello {}", "world",);
+}
+
+#[test]
+fn file() {
+    let _ = file!();
+}
+
+#[cfg(std)]
+#[test]
+fn format() {
+    let _ = format!("hello");
+    let _ = format!("hello",);
+    let _ = format!("hello {}", "world");
+    let _ = format!("hello {}", "world",);
+}
+
+#[test]
+fn format_args() {
+    let _ = format_args!("hello");
+    let _ = format_args!("hello",);
+    let _ = format_args!("hello {}", "world");
+    let _ = format_args!("hello {}", "world",);
+}
+
+#[test]
+fn include() {
+    let _ = include!("auxiliary/macro-comma-support.rs");
+    let _ = include!("auxiliary/macro-comma-support.rs",);
+}
+
+#[test]
+fn include_bytes() {
+    let _ = include_bytes!("auxiliary/macro-comma-support.rs");
+    let _ = include_bytes!("auxiliary/macro-comma-support.rs",);
+}
+
+#[test]
+fn include_str() {
+    let _ = include_str!("auxiliary/macro-comma-support.rs");
+    let _ = include_str!("auxiliary/macro-comma-support.rs",);
+}
+
+#[test]
+fn line() {
+    let _ = line!();
+}
+
+#[test]
+fn module_path() {
+    let _ = module_path!();
+}
+
+#[test]
+fn option_env() {
+    let _ = option_env!("PATH");
+    let _ = option_env!("PATH",);
+}
+
+#[test]
+fn panic() {
+    // prevent 'unreachable code' warnings
+    let falsum = || false;
+
+    if falsum() { panic!(); }
+    if falsum() { panic!("hello"); }
+    if falsum() { panic!("hello",); }
+    if falsum() { panic!("hello {}", "world"); }
+    if falsum() { panic!("hello {}", "world",); }
+}
+
+#[cfg(std)]
+#[test]
+fn print() {
+    print!("hello");
+    print!("hello",);
+    print!("hello {}", "world");
+    print!("hello {}", "world",);
+}
+
+#[cfg(std)]
+#[test]
+fn println() {
+    println!();
+    println!("hello");
+    println!("hello",);
+    println!("hello {}", "world");
+    println!("hello {}", "world",);
+}
+
+// select! is too troublesome and unlikely to be stabilized
+
+// stringify! is N/A
+
+#[cfg(std)]
+#[test]
+fn thread_local() {
+    // this has an optional trailing *semicolon*
+    thread_local! {
+        #[allow(unused)] pub static A: () = ()
+    }
+
+    thread_local! {
+        #[allow(unused)] pub static AA: () = ();
+    }
+
+    thread_local! {
+        #[allow(unused)] pub static AAA: () = ();
+        #[allow(unused)] pub static AAAA: () = ()
+    }
+
+    thread_local! {
+        #[allow(unused)] pub static AAAAG: () = ();
+        #[allow(unused)] pub static AAAAGH: () = ();
+    }
+}
+
+#[test]
+fn try() {
+    fn inner() -> Result<(), ()> {
+        try!(Ok(()));
+        try!(Ok(()),);
+        Ok(())
+    }
+
+    inner().unwrap();
+}
+
+#[test]
+fn unimplemented() {
+    // prevent 'unreachable code' warnings
+    let falsum = || false;
+
+    if falsum() { unimplemented!(); }
+    if falsum() { unimplemented!("hello"); }
+    if falsum() { unimplemented!("hello",); }
+    if falsum() { unimplemented!("hello {}", "world"); }
+    if falsum() { unimplemented!("hello {}", "world",); }
+}
+
+#[test]
+fn unreachable() {
+    // prevent 'unreachable code' warnings
+    let falsum = || false;
+
+    if falsum() { unreachable!(); }
+    if falsum() { unreachable!("hello"); }
+    if falsum() { unreachable!("hello",); }
+    if falsum() { unreachable!("hello {}", "world"); }
+    if falsum() { unreachable!("hello {}", "world",); }
+}
+
+#[cfg(std)]
+#[test]
+fn vec() {
+    let _: Vec<()> = vec![];
+    let _ = vec![0];
+    let _ = vec![0,];
+    let _ = vec![0, 1];
+    let _ = vec![0, 1,];
+}
+
+// give a test body access to a fmt::Formatter, which seems
+// to be the easiest way to use 'write!' on core.
+macro_rules! test_with_formatter {
+    (
+        #[test]
+        fn $fname:ident($f:ident: &mut fmt::Formatter) $block:block
+    ) => {
+        #[test]
+        fn $fname() {
+            struct Struct;
+            impl fmt::Display for Struct {
+                fn fmt(&self, $f: &mut fmt::Formatter) -> fmt::Result {
+                    Ok($block)
+                }
+            }
+
+            // suppress "unused"
+            assert!(true, "{}", Struct);
+        }
+    };
+}
+
+test_with_formatter! {
+    #[test]
+    fn write(f: &mut fmt::Formatter) {
+        let _ = write!(f, "hello");
+        let _ = write!(f, "hello",);
+        let _ = write!(f, "hello {}", "world");
+        let _ = write!(f, "hello {}", "world",);
+    }
+}
+
+test_with_formatter! {
+    #[test]
+    fn writeln(f: &mut fmt::Formatter) {
+        let _ = writeln!(f);
+        let _ = writeln!(f,);
+        let _ = writeln!(f, "hello");
+        let _ = writeln!(f, "hello",);
+        let _ = writeln!(f, "hello {}", "world");
+        let _ = writeln!(f, "hello {}", "world",);
+    }
+}