From f69293ae808dea61a2dacee6057ca5bb0d7dc817 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 16 Oct 2019 22:40:37 +0200 Subject: [PATCH 1/4] Add `core::macros::matches!( $expr, $pat ) -> bool` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 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. --- Cargo.lock | 5 ++++ src/libcore/Cargo.toml | 3 ++ src/libcore/lib.rs | 12 +++++++- src/libcore/prelude/v1.rs | 2 +- src/libmatches_macro/Cargo.toml | 10 +++++++ src/libmatches_macro/lib.rs | 29 +++++++++++++++++++ src/libstd/lib.rs | 12 +++++++- src/test/ui/macros/unknown-builtin.stderr | 2 +- src/test/ui/matches_macro_imported.rs | 13 +++++++++ .../ui/matches_macro_not_in_the_prelude.rs | 7 +++++ .../matches_macro_not_in_the_prelude.stderr | 8 +++++ 11 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/libmatches_macro/Cargo.toml create mode 100644 src/libmatches_macro/lib.rs create mode 100644 src/test/ui/matches_macro_imported.rs create mode 100644 src/test/ui/matches_macro_not_in_the_prelude.rs create mode 100644 src/test/ui/matches_macro_not_in_the_prelude.stderr diff --git a/Cargo.lock b/Cargo.lock index 3f37a1b7eb091..343b101990a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,6 +585,7 @@ checksum = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" name = "core" version = "0.0.0" dependencies = [ + "matches_macro", "rand 0.7.0", ] @@ -1900,6 +1901,10 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "matches_macro" +version = "0.0.0" + [[package]] name = "mdbook" version = "0.3.1" diff --git a/src/libcore/Cargo.toml b/src/libcore/Cargo.toml index ac07ffb14febd..65f7a42824bf7 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 30e8dddff85ad..a5af32250e672 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 7cc279a9ef2ec..285f8d6e077f4 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 0000000000000..3ed0aa60350df --- /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 0000000000000..0e3552ed4ea98 --- /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 93d3e4ea3df22..4c079108e2e16 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 33b7b055b4e4b..27992b466baac 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 0000000000000..76b7e692ceea3 --- /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 0000000000000..489c7b866459f --- /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 0000000000000..0abe29a835b85 --- /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 + From 7472cd46aa6c004568d12a71ecd90c9c45b69fb0 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 23 Oct 2019 15:30:04 +0200 Subject: [PATCH 2/4] Move the `matches!` macro to the prelude --- Cargo.lock | 5 ---- src/libcore/Cargo.toml | 3 -- src/libcore/lib.rs | 12 +------- src/libcore/macros.rs | 24 +++++++++++++++ src/libcore/prelude/v1.rs | 2 +- src/libmatches_macro/Cargo.toml | 10 ------- src/libmatches_macro/lib.rs | 29 ------------------- src/libstd/lib.rs | 12 ++------ src/test/ui/macros/unknown-builtin.stderr | 2 +- src/test/ui/matches_macro_imported.rs | 13 --------- .../ui/matches_macro_not_in_the_prelude.rs | 7 ----- .../matches_macro_not_in_the_prelude.stderr | 8 ----- 12 files changed, 29 insertions(+), 98 deletions(-) delete mode 100644 src/libmatches_macro/Cargo.toml delete mode 100644 src/libmatches_macro/lib.rs delete mode 100644 src/test/ui/matches_macro_imported.rs delete mode 100644 src/test/ui/matches_macro_not_in_the_prelude.rs delete mode 100644 src/test/ui/matches_macro_not_in_the_prelude.stderr diff --git a/Cargo.lock b/Cargo.lock index 343b101990a44..3f37a1b7eb091 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,7 +585,6 @@ checksum = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" name = "core" version = "0.0.0" dependencies = [ - "matches_macro", "rand 0.7.0", ] @@ -1901,10 +1900,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -[[package]] -name = "matches_macro" -version = "0.0.0" - [[package]] name = "mdbook" version = "0.3.1" diff --git a/src/libcore/Cargo.toml b/src/libcore/Cargo.toml index 65f7a42824bf7..ac07ffb14febd 100644 --- a/src/libcore/Cargo.toml +++ b/src/libcore/Cargo.toml @@ -20,9 +20,6 @@ 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 a5af32250e672..30e8dddff85ad 100644 --- a/src/libcore/lib.rs +++ b/src/libcore/lib.rs @@ -85,7 +85,6 @@ #![feature(iter_once_with)] #![feature(lang_items)] #![feature(link_llvm_intrinsics)] -#![feature(matches_macro)] #![feature(never_type)] #![feature(nll)] #![feature(exhaustive_patterns)] @@ -135,16 +134,7 @@ use prelude::v1::*; #[macro_use] -#[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; -} +mod macros; #[macro_use] mod internal_macros; diff --git a/src/libcore/macros.rs b/src/libcore/macros.rs index 1320e63df0635..f2775ffa7ca94 100644 --- a/src/libcore/macros.rs +++ b/src/libcore/macros.rs @@ -238,6 +238,30 @@ macro_rules! debug_assert_ne { ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); }) } +/// Returns whether the given expression matches (any of) the given pattern(s). +/// +/// # Examples +/// +/// ``` +/// #![feature(matches_macro)] +/// +/// 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 + } + } +} + /// Unwraps a result or propagates its error. /// /// The `?` operator was added to replace `try!` and should be used instead. diff --git a/src/libcore/prelude/v1.rs b/src/libcore/prelude/v1.rs index 285f8d6e077f4..7cc279a9ef2ec 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::prelude_macros::builtin::{ +pub use crate::macros::builtin::{ RustcDecodable, RustcEncodable, bench, diff --git a/src/libmatches_macro/Cargo.toml b/src/libmatches_macro/Cargo.toml deleted file mode 100644 index 3ed0aa60350df..0000000000000 --- a/src/libmatches_macro/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[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 deleted file mode 100644 index 0e3552ed4ea98..0000000000000 --- a/src/libmatches_macro/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![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 4c079108e2e16..d0cb0104f6cba 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -354,16 +354,7 @@ extern crate cfg_if; // The standard macros that are not built-in to the compiler. #[macro_use] -#[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; -} +mod macros; // The Rust prelude pub mod prelude; @@ -537,6 +528,7 @@ pub use core::{ writeln, // Unstable todo, + matches, }; // Re-export built-in macros defined through libcore. diff --git a/src/test/ui/macros/unknown-builtin.stderr b/src/test/ui/macros/unknown-builtin.stderr index 27992b466baac..33b7b055b4e4b 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::prelude_macros::builtin::line macros>:1:1 + --> <::core::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 deleted file mode 100644 index 76b7e692ceea3..0000000000000 --- a/src/test/ui/matches_macro_imported.rs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 deleted file mode 100644 index 489c7b866459f..0000000000000 --- a/src/test/ui/matches_macro_not_in_the_prelude.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![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 deleted file mode 100644 index 0abe29a835b85..0000000000000 --- a/src/test/ui/matches_macro_not_in_the_prelude.stderr +++ /dev/null @@ -1,8 +0,0 @@ -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 - From f7ebe193389b4c63d9c93d79fff59568985e1763 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 23 Oct 2019 15:34:24 +0200 Subject: [PATCH 3/4] Add tracking issue for the `matches!` macro https://github.com/rust-lang/rust/issues/65721 --- src/libcore/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcore/macros.rs b/src/libcore/macros.rs index f2775ffa7ca94..6e19878ef17d3 100644 --- a/src/libcore/macros.rs +++ b/src/libcore/macros.rs @@ -252,7 +252,7 @@ macro_rules! debug_assert_ne { /// assert!(matches!(bar, Some(x) if x > 2)); /// ``` #[macro_export] -#[unstable(feature = "matches_macro", issue = "0")] +#[unstable(feature = "matches_macro", issue = "65721")] macro_rules! matches { ($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )?) => { match $expression { From e76a1846153209b15dfbf697a33c25214bb753b3 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 23 Oct 2019 15:42:52 +0200 Subject: [PATCH 4/4] Document guard expressions in `matches!` --- src/libcore/macros.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libcore/macros.rs b/src/libcore/macros.rs index 6e19878ef17d3..35558e3abcddd 100644 --- a/src/libcore/macros.rs +++ b/src/libcore/macros.rs @@ -238,7 +238,10 @@ macro_rules! debug_assert_ne { ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); }) } -/// Returns whether the given expression matches (any of) the given pattern(s). +/// Returns whether the given expression matches any of the given patterns. +/// +/// Like in a `match` expression, the pattern can be optionally followed by `if` +/// and a guard expression that has access to names bound by the pattern. /// /// # Examples ///