about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorSimon Sapin <simon.sapin@exyr.org>2019-10-16 22:40:37 +0200
committerSimon Sapin <simon.sapin@exyr.org>2019-10-23 15:35:34 +0200
commitf69293ae808dea61a2dacee6057ca5bb0d7dc817 (patch)
tree1a6c703a7cb180933e1ca12c6d2ee5de8484bc00 /src
parentf466f52c1bf8f2e4454e31c683a88625ad4b4033 (diff)
downloadrust-f69293ae808dea61a2dacee6057ca5bb0d7dc817.tar.gz
rust-f69293ae808dea61a2dacee6057ca5bb0d7dc817.zip
Add `core::macros::matches!( $expr, $pat ) -> bool`
# Motivation

This macro is:

* General-purpose (not domain-specific)
* Simple (the implementation is short)
* Very popular [on crates.io](https://crates.io/crates/matches)
  (currently 37th in all-time downloads)
* The two previous points combined make it number one in
  [left-pad index](https://twitter.com/bascule/status/1184523027888988160)
  score

As such, I feel it is a good candidate for inclusion in the standard library.

In fact I already felt that way five years ago:
https://github.com/rust-lang/rust/pull/14685
(Although the proof of popularity was not as strong at the time.)

Back then, the main concern was that this macro may not be quite
universally-enough useful to belong in the prelude.

# API

Therefore, this PR adds the macro such that using it requires one of:

```
use core::macros::matches;
use std::macros::matches;
```

Like arms of a `match` expression,
the macro supports multiple patterns separated by `|`
and optionally followed by `if` and a guard expression:

```
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
```

# Implementation constraints

A combination of reasons make it tricky
for a standard library macro not to be in the prelude.

Currently, all public `macro_rules` macros in the standard library macros
end up “in the prelude” of every crate not through `use std::prelude::v1::*;`
like for other kinds of items,
but through `#[macro_use]` on `extern crate std;`.
(Both are injected by `src/libsyntax_ext/standard_library_imports.rs`.)

`#[macro_use]` seems to import every macro that is available
at the top-level of a crate, even if through a `pub use` re-export.

Therefore, for `matches!` not to be in the prelude, we need it to be
inside of a module rather than at the root of `core` or `std`.

However, the only way to make a `macro_rules` macro public
outside of the crate where it is defined
appears to be `#[macro_export]`.
This exports the macro at the root of the crate
regardless of which module defines it.
See [macro scoping](
https://doc.rust-lang.org/reference/macros-by-example.html#scoping-exporting-and-importing)
in the reference.

Therefore, the macro needs to be defined in a crate
that is not `core` or `std`.

# Implementation

This PR adds a new `matches_macro` crate as a private implementation detail
of the standard library.
This crate is `#![no_core]` so that libcore can depend on it.
It contains a `macro_rules` definition with `#[macro_export]`.

libcore and libstd each have a new public `macros` module
that contains a `pub use` re-export of the macro.
Both the module and the macro are unstable, for now.

The existing private `macros` modules are renamed `prelude_macros`,
though their respective source remains in `macros.rs` files.
Diffstat (limited to 'src')
-rw-r--r--src/libcore/Cargo.toml3
-rw-r--r--src/libcore/lib.rs12
-rw-r--r--src/libcore/prelude/v1.rs2
-rw-r--r--src/libmatches_macro/Cargo.toml10
-rw-r--r--src/libmatches_macro/lib.rs29
-rw-r--r--src/libstd/lib.rs12
-rw-r--r--src/test/ui/macros/unknown-builtin.stderr2
-rw-r--r--src/test/ui/matches_macro_imported.rs13
-rw-r--r--src/test/ui/matches_macro_not_in_the_prelude.rs7
-rw-r--r--src/test/ui/matches_macro_not_in_the_prelude.stderr8
10 files changed, 94 insertions, 4 deletions
diff --git a/src/libcore/Cargo.toml b/src/libcore/Cargo.toml
index ac07ffb14fe..65f7a42824b 100644
--- a/src/libcore/Cargo.toml
+++ b/src/libcore/Cargo.toml
@@ -20,6 +20,9 @@ path = "../libcore/tests/lib.rs"
 name = "corebenches"
 path = "../libcore/benches/lib.rs"
 
+[dependencies]
+matches_macro = { path = "../libmatches_macro" }
+
 [dev-dependencies]
 rand = "0.7"
 
diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs
index 30e8dddff85..a5af32250e6 100644
--- a/src/libcore/lib.rs
+++ b/src/libcore/lib.rs
@@ -85,6 +85,7 @@
 #![feature(iter_once_with)]
 #![feature(lang_items)]
 #![feature(link_llvm_intrinsics)]
+#![feature(matches_macro)]
 #![feature(never_type)]
 #![feature(nll)]
 #![feature(exhaustive_patterns)]
@@ -134,7 +135,16 @@
 use prelude::v1::*;
 
 #[macro_use]
-mod macros;
+#[path = "macros.rs"]
+mod prelude_macros;
+
+/// Macros that are not in the prelude and need to be imported explicitly
+#[unstable(feature = "matches_macro", issue = "0")]
+pub mod macros {
+    #[unstable(feature = "matches_macro", issue = "0")]
+    #[doc(inline)]
+    pub use matches_macro::matches;
+}
 
 #[macro_use]
 mod internal_macros;
diff --git a/src/libcore/prelude/v1.rs b/src/libcore/prelude/v1.rs
index 7cc279a9ef2..285f8d6e077 100644
--- a/src/libcore/prelude/v1.rs
+++ b/src/libcore/prelude/v1.rs
@@ -82,7 +82,7 @@ pub use crate::{
 #[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
 #[allow(deprecated)]
 #[doc(no_inline)]
-pub use crate::macros::builtin::{
+pub use crate::prelude_macros::builtin::{
     RustcDecodable,
     RustcEncodable,
     bench,
diff --git a/src/libmatches_macro/Cargo.toml b/src/libmatches_macro/Cargo.toml
new file mode 100644
index 00000000000..3ed0aa60350
--- /dev/null
+++ b/src/libmatches_macro/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+authors = ["The Rust Project Developers"]
+name = "matches_macro"
+version = "0.0.0"
+autotests = false
+autobenches = false
+edition = "2018"
+
+[lib]
+path = "lib.rs"
diff --git a/src/libmatches_macro/lib.rs b/src/libmatches_macro/lib.rs
new file mode 100644
index 00000000000..0e3552ed4ea
--- /dev/null
+++ b/src/libmatches_macro/lib.rs
@@ -0,0 +1,29 @@
+#![no_core]
+#![feature(no_core)]
+#![feature(staged_api)]
+#![doc(test(no_crate_inject))]
+
+/// Returns whether the given expression matches (any of) the given pattern(s).
+///
+/// # Examples
+///
+/// ```
+/// #![feature(matches_macro)]
+/// use std::macros::matches;
+///
+/// let foo = 'f';
+/// assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
+///
+/// let bar = Some(4);
+/// assert!(matches!(bar, Some(x) if x > 2));
+/// ```
+#[macro_export]
+#[unstable(feature = "matches_macro", issue = "0")]
+macro_rules! matches {
+    ($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )?) => {
+        match $expression {
+            $( $pattern )|+ $( if $guard )? => true,
+            _ => false
+        }
+    }
+}
diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs
index 93d3e4ea3df..4c079108e2e 100644
--- a/src/libstd/lib.rs
+++ b/src/libstd/lib.rs
@@ -276,6 +276,7 @@
 #![feature(linkage)]
 #![feature(log_syntax)]
 #![feature(manually_drop_take)]
+#![feature(matches_macro)]
 #![feature(maybe_uninit_ref)]
 #![feature(maybe_uninit_slice)]
 #![feature(needs_panic_runtime)]
@@ -353,7 +354,16 @@ extern crate cfg_if;
 
 // The standard macros that are not built-in to the compiler.
 #[macro_use]
-mod macros;
+#[path = "macros.rs"]
+mod prelude_macros;
+
+/// Macros that are not in the prelude and need to be imported explicitly
+#[unstable(feature = "matches_macro", issue = "0")]
+pub mod macros {
+    #[unstable(feature = "matches_macro", issue = "0")]
+    #[doc(inline)]
+    pub use core::macros::matches;
+}
 
 // The Rust prelude
 pub mod prelude;
diff --git a/src/test/ui/macros/unknown-builtin.stderr b/src/test/ui/macros/unknown-builtin.stderr
index 33b7b055b4e..27992b466ba 100644
--- a/src/test/ui/macros/unknown-builtin.stderr
+++ b/src/test/ui/macros/unknown-builtin.stderr
@@ -5,7 +5,7 @@ LL | macro_rules! unknown { () => () }
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: cannot find a built-in macro with name `line`
-  --> <::core::macros::builtin::line macros>:1:1
+  --> <::core::prelude_macros::builtin::line macros>:1:1
    |
 LL | () => { }
    | ^^^^^^^^^
diff --git a/src/test/ui/matches_macro_imported.rs b/src/test/ui/matches_macro_imported.rs
new file mode 100644
index 00000000000..76b7e692cee
--- /dev/null
+++ b/src/test/ui/matches_macro_imported.rs
@@ -0,0 +1,13 @@
+// run-pass
+
+#![feature(matches_macro)]
+
+use std::macros::matches;
+
+fn main() {
+    let foo = 'f';
+    assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
+
+    let foo = '_';
+    assert!(!matches!(foo, 'A'..='Z' | 'a'..='z'));
+}
diff --git a/src/test/ui/matches_macro_not_in_the_prelude.rs b/src/test/ui/matches_macro_not_in_the_prelude.rs
new file mode 100644
index 00000000000..489c7b86645
--- /dev/null
+++ b/src/test/ui/matches_macro_not_in_the_prelude.rs
@@ -0,0 +1,7 @@
+#![feature(matches_macro)]
+
+fn main() {
+    let foo = 'f';
+    assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
+    //~^ Error: cannot find macro `matches` in this scope
+}
diff --git a/src/test/ui/matches_macro_not_in_the_prelude.stderr b/src/test/ui/matches_macro_not_in_the_prelude.stderr
new file mode 100644
index 00000000000..0abe29a835b
--- /dev/null
+++ b/src/test/ui/matches_macro_not_in_the_prelude.stderr
@@ -0,0 +1,8 @@
+error: cannot find macro `matches` in this scope
+  --> $DIR/matches_macro_not_in_the_prelude.rs:5:13
+   |
+LL |     assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
+   |             ^^^^^^^
+
+error: aborting due to previous error
+