From 1870537f2701e5aa47080a879b63a4d6b391553b Mon Sep 17 00:00:00 2001 From: Dan Robertson Date: Sun, 14 Jul 2019 01:05:52 +0000 Subject: [PATCH] initial implementation of or-pattern parsing Initial implementation of parsing or-patterns e.g., `Some(Foo | Bar)`. This is a partial implementation of RFC 2535. --- .../src/language-features/or-patterns.md | 36 ++++++++++++++ src/librustc/hir/mod.rs | 3 +- src/librustc/hir/print.rs | 5 +- src/librustc_mir/build/matches/mod.rs | 3 +- src/librustc_mir/hair/pattern/_match.rs | 3 ++ src/librustc_mir/hair/pattern/mod.rs | 3 +- src/librustc_typeck/check/_match.rs | 2 +- src/libsyntax/ast.rs | 1 + src/libsyntax/feature_gate.rs | 5 ++ src/libsyntax/parse/mod.rs | 3 ++ src/libsyntax/parse/parser/pat.rs | 41 ++++++++++++++-- src/libsyntax/print/pprust.rs | 47 +++++-------------- src/libsyntax/visit.rs | 2 +- src/libsyntax_pos/symbol.rs | 1 + .../feature-gate/feature-gate-or_patterns.rs | 9 ++++ .../feature-gate-or_patterns.stderr | 12 +++++ src/test/ui/parser/pat-lt-bracket-6.rs | 5 +- src/test/ui/parser/pat-lt-bracket-6.stderr | 6 +-- src/test/ui/parser/pat-lt-bracket-7.rs | 3 +- src/test/ui/parser/pat-lt-bracket-7.stderr | 6 +-- .../recover-for-loop-parens-around-head.rs | 2 +- ...recover-for-loop-parens-around-head.stderr | 4 +- 22 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 src/doc/unstable-book/src/language-features/or-patterns.md create mode 100644 src/test/ui/feature-gate/feature-gate-or_patterns.rs create mode 100644 src/test/ui/feature-gate/feature-gate-or_patterns.stderr diff --git a/src/doc/unstable-book/src/language-features/or-patterns.md b/src/doc/unstable-book/src/language-features/or-patterns.md new file mode 100644 index 0000000000000..8ebacb44d37cc --- /dev/null +++ b/src/doc/unstable-book/src/language-features/or-patterns.md @@ -0,0 +1,36 @@ +# `or_patterns` + +The tracking issue for this feature is: [#54883] + +[#54883]: https://github.com/rust-lang/rust/issues/54883 + +------------------------ + +The `or_pattern` language feature allows `|` to be arbitrarily nested within +a pattern, for example, `Some(A(0) | B(1 | 2))` becomes a valid pattern. + +## Examples + +```rust,ignore +#![feature(or_patterns)] + +pub enum Foo { + Bar, + Baz, + Quux, +} + +pub fn example(maybe_foo: Option) { + match maybe_foo { + Some(Foo::Bar | Foo::Baz) => { + println!("The value contained `Bar` or `Baz`"); + } + Some(_) => { + println!("The value did not contain `Bar` or `Baz`"); + } + None => { + println!("The value was `None`"); + } + } +} +``` diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs index 2ae08568b7f7d..5b15cf9a6c90f 100644 --- a/src/librustc/hir/mod.rs +++ b/src/librustc/hir/mod.rs @@ -978,7 +978,8 @@ pub enum PatKind { TupleStruct(QPath, HirVec>, Option), /// An or-pattern `A | B | C`. - Or(Vec>), + /// Invariant: `pats.len() >= 2`. + Or(HirVec>), /// A path pattern for an unit struct/variant or a (maybe-associated) constant. Path(QPath), diff --git a/src/librustc/hir/print.rs b/src/librustc/hir/print.rs index 157b7c07a9b38..632a13f9183b2 100644 --- a/src/librustc/hir/print.rs +++ b/src/librustc/hir/print.rs @@ -4,7 +4,7 @@ use syntax::source_map::{SourceMap, Spanned}; use syntax::parse::ParseSess; use syntax::print::pp::{self, Breaks}; use syntax::print::pp::Breaks::{Consistent, Inconsistent}; -use syntax::print::pprust::{self, Comments, PrintState, SeparatorSpacing}; +use syntax::print::pprust::{self, Comments, PrintState}; use syntax::symbol::kw; use syntax::util::parser::{self, AssocOp, Fixity}; use syntax_pos::{self, BytePos, FileName}; @@ -1688,8 +1688,7 @@ impl<'a> State<'a> { self.s.word("}"); } PatKind::Or(ref pats) => { - let spacing = SeparatorSpacing::Both; - self.strsep("|", spacing, Inconsistent, &pats[..], |s, p| s.print_pat(&p))?; + self.strsep("|", true, Inconsistent, &pats[..], |s, p| s.print_pat(&p)); } PatKind::Tuple(ref elts, ddpos) => { self.popen(); diff --git a/src/librustc_mir/build/matches/mod.rs b/src/librustc_mir/build/matches/mod.rs index 0dec7ef4f0061..94323b15b696f 100644 --- a/src/librustc_mir/build/matches/mod.rs +++ b/src/librustc_mir/build/matches/mod.rs @@ -658,9 +658,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } PatternKind::Or { ref pats } => { - // FIXME(#47184): extract or handle `pattern_user_ty` somehow for pat in pats { - self.visit_bindings(&pat, &pattern_user_ty.clone(), f); + self.visit_bindings(&pat, pattern_user_ty.clone(), f); } } } diff --git a/src/librustc_mir/hair/pattern/_match.rs b/src/librustc_mir/hair/pattern/_match.rs index ae59244d37f51..222750e602df9 100644 --- a/src/librustc_mir/hair/pattern/_match.rs +++ b/src/librustc_mir/hair/pattern/_match.rs @@ -75,6 +75,9 @@ /// D((r_1, p_(i,2), .., p_(i,n))) /// D((r_2, p_(i,2), .., p_(i,n))) /// +/// Note that the OR-patterns are not always used directly in Rust, but are used to derive +/// the exhaustive integer matching rules, so they're written here for posterity. +/// /// The algorithm for computing `U` /// ------------------------------- /// The algorithm is inductive (on the number of columns: i.e., components of tuple patterns). diff --git a/src/librustc_mir/hair/pattern/mod.rs b/src/librustc_mir/hair/pattern/mod.rs index d2a5793e70363..6caccfddfa422 100644 --- a/src/librustc_mir/hair/pattern/mod.rs +++ b/src/librustc_mir/hair/pattern/mod.rs @@ -176,7 +176,8 @@ pub enum PatternKind<'tcx> { suffix: Vec>, }, - /// or-pattern + /// An or-pattern, e.g. `p | q`. + /// Invariant: `pats.len() >= 2`. Or { pats: Vec>, }, diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs index 2e22fb766751a..fc25eb44cbd88 100644 --- a/src/librustc_typeck/check/_match.rs +++ b/src/librustc_typeck/check/_match.rs @@ -313,7 +313,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { PatKind::Or(ref pats) => { let expected_ty = self.structurally_resolved_type(pat.span, expected); for pat in pats { - self.check_pat_walk(pat, expected, def_bm, false); + self.check_pat_walk(pat, expected, def_bm, discrim_span); } expected_ty } diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 0136c4ff5f936..3d15782df34e2 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -650,6 +650,7 @@ pub enum PatKind { TupleStruct(Path, Vec>), /// An or-pattern `A | B | C`. + /// Invariant: `pats.len() >= 2`. Or(Vec>), /// A possibly qualified path pattern. diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 1a87a903156d2..bbc3ae2822558 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -559,6 +559,9 @@ declare_features! ( // Allows `impl Trait` to be used inside type aliases (RFC 2515). (active, type_alias_impl_trait, "1.38.0", Some(63063), None), + // Allows the use of or-patterns, e.g. `0 | 1`. + (active, or_patterns, "1.38.0", Some(54883), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- @@ -571,6 +574,7 @@ pub const INCOMPLETE_FEATURES: &[Symbol] = &[ sym::impl_trait_in_bindings, sym::generic_associated_types, sym::const_generics, + sym::or_patterns, sym::let_chains, ]; @@ -2443,6 +2447,7 @@ pub fn check_crate(krate: &ast::Crate, gate_all!(let_chains_spans, let_chains, "`let` expressions in this position are experimental"); gate_all!(async_closure_spans, async_closure, "async closures are unstable"); gate_all!(yield_spans, generators, "yield syntax is experimental"); + gate_all!(or_pattern_spans, or_patterns, "or-patterns syntax is experimental"); let visitor = &mut PostExpansionVisitor { context: &ctx, diff --git a/src/libsyntax/parse/mod.rs b/src/libsyntax/parse/mod.rs index 9088f929372c9..b1f3612a839a2 100644 --- a/src/libsyntax/parse/mod.rs +++ b/src/libsyntax/parse/mod.rs @@ -66,6 +66,8 @@ pub struct ParseSess { // Places where `yield e?` exprs were used and should be feature gated. pub yield_spans: Lock>, pub injected_crate_name: Once, + // Places where or-patterns e.g. `Some(Foo | Bar)` were used and should be feature gated. + pub or_pattern_spans: Lock>, } impl ParseSess { @@ -96,6 +98,7 @@ impl ParseSess { async_closure_spans: Lock::new(Vec::new()), yield_spans: Lock::new(Vec::new()), injected_crate_name: Once::new(), + or_pattern_spans: Lock::new(Vec::new()), } } diff --git a/src/libsyntax/parse/parser/pat.rs b/src/libsyntax/parse/parser/pat.rs index c3079d2da0ce7..fd458aec74331 100644 --- a/src/libsyntax/parse/parser/pat.rs +++ b/src/libsyntax/parse/parser/pat.rs @@ -14,7 +14,10 @@ use errors::{Applicability, DiagnosticBuilder}; impl<'a> Parser<'a> { /// Parses a pattern. - pub fn parse_pat(&mut self, expected: Option<&'static str>) -> PResult<'a, P> { + pub fn parse_pat( + &mut self, + expected: Option<&'static str> + ) -> PResult<'a, P> { self.parse_pat_with_range_pat(true, expected) } @@ -97,6 +100,34 @@ impl<'a> Parser<'a> { Ok(()) } + /// Parses a pattern, that may be a or-pattern (e.g. `Some(Foo | Bar)`). + fn parse_pat_with_or(&mut self, expected: Option<&'static str>) -> PResult<'a, P> { + // Parse the first pattern. + let first_pat = self.parse_pat(expected)?; + + // If the next token is not a `|`, this is not an or-pattern and + // we should exit here. + if !self.check(&token::BinOp(token::Or)) { + return Ok(first_pat) + } + + let lo = first_pat.span; + + let mut pats = vec![first_pat]; + + while self.eat(&token::BinOp(token::Or)) { + pats.push(self.parse_pat_with_range_pat( + true, expected + )?); + } + + let or_pattern_span = lo.to(self.prev_span); + + self.sess.or_pattern_spans.borrow_mut().push(or_pattern_span); + + Ok(self.mk_pat(or_pattern_span, PatKind::Or(pats))) + } + /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are /// allowed). fn parse_pat_with_range_pat( @@ -240,7 +271,9 @@ impl<'a> Parser<'a> { /// Parse a tuple or parenthesis pattern. fn parse_pat_tuple_or_parens(&mut self) -> PResult<'a, PatKind> { - let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| p.parse_pat(None))?; + let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| { + p.parse_pat_with_or(None) + })?; // Here, `(pat,)` is a tuple pattern. // For backward compatibility, `(..)` is a tuple pattern as well. @@ -483,7 +516,7 @@ impl<'a> Parser<'a> { err.span_label(self.token.span, msg); return Err(err); } - let (fields, _) = self.parse_paren_comma_seq(|p| p.parse_pat(None))?; + let (fields, _) = self.parse_paren_comma_seq(|p| p.parse_pat_with_or(None))?; Ok(PatKind::TupleStruct(path, fields)) } @@ -627,7 +660,7 @@ impl<'a> Parser<'a> { // Parsing a pattern of the form "fieldname: pat" let fieldname = self.parse_field_name()?; self.bump(); - let pat = self.parse_pat(None)?; + let pat = self.parse_pat_with_or(None)?; hi = pat.span; (pat, fieldname, false) } else { diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index 8dcb7ecf881d0..4dc00af486013 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -431,46 +431,33 @@ impl std::ops::DerefMut for State<'_> { } } -pub enum SeparatorSpacing { - After, - Both, -} - pub trait PrintState<'a>: std::ops::Deref + std::ops::DerefMut { fn comments(&mut self) -> &mut Option>; fn print_ident(&mut self, ident: ast::Ident); fn print_generic_args(&mut self, args: &ast::GenericArgs, colons_before_params: bool); - fn strsep( - &mut self, - sep: &'static str, - spacing: SeparatorSpacing, - b: Breaks, - elts: &[T], - mut op: F - ) -> io::Result<()> + fn strsep(&mut self, sep: &'static str, space_before: bool, + b: Breaks, elts: &[T], mut op: F) where F: FnMut(&mut Self, &T), { self.rbox(0, b); - let mut first = true; - for elt in elts { - if first { - first = false; - } else { - if let SeparatorSpacing::Both = spacing { - self.writer().space(); + if let Some((first, rest)) = elts.split_first() { + op(self, first); + for elt in rest { + if space_before { + self.space(); } self.word_space(sep); + op(self, elt); } - op(self, elt); } self.end(); } - fn commasep(&mut self, b: Breaks, elts: &[T], mut op: F) + fn commasep(&mut self, b: Breaks, elts: &[T], op: F) where F: FnMut(&mut Self, &T), { - self.strsep(",", SeparatorSpacing::After, b, elts, op) + self.strsep(",", false, b, elts, op) } fn maybe_print_comment(&mut self, pos: BytePos) { @@ -2379,8 +2366,7 @@ impl<'a> State<'a> { self.pclose(); } PatKind::Or(ref pats) => { - let spacing = SeparatorSpacing::Both; - self.strsep("|", spacing, Inconsistent, &pats[..], |s, p| s.print_pat(p))?; + self.strsep("|", true, Inconsistent, &pats[..], |s, p| s.print_pat(p)); } PatKind::Path(None, ref path) => { self.print_path(path, true, 0); @@ -2458,16 +2444,7 @@ impl<'a> State<'a> { } fn print_pats(&mut self, pats: &[P]) { - let mut first = true; - for p in pats { - if first { - first = false; - } else { - self.s.space(); - self.word_space("|"); - } - self.print_pat(p); - } + self.strsep("|", true, Inconsistent, pats, |s, p| s.print_pat(p)); } fn print_arm(&mut self, arm: &ast::Arm) { diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs index ce679a5db63ff..91b92d84a811f 100644 --- a/src/libsyntax/visit.rs +++ b/src/libsyntax/visit.rs @@ -462,7 +462,7 @@ pub fn walk_pat<'a, V: Visitor<'a>>(visitor: &mut V, pattern: &'a Pat) { visitor.visit_expr(upper_bound); } PatKind::Wild | PatKind::Rest => {}, - PatKind::Tuple(ref elems) => { + PatKind::Tuple(ref elems) | PatKind::Slice(ref elems) | PatKind::Or(ref elems) => { walk_list!(visitor, visit_pat, elems); diff --git a/src/libsyntax_pos/symbol.rs b/src/libsyntax_pos/symbol.rs index 27fc66d3b09e6..361e01781b1ad 100644 --- a/src/libsyntax_pos/symbol.rs +++ b/src/libsyntax_pos/symbol.rs @@ -469,6 +469,7 @@ symbols! { option_env, opt_out_copy, or, + or_patterns, Ord, Ordering, Output, diff --git a/src/test/ui/feature-gate/feature-gate-or_patterns.rs b/src/test/ui/feature-gate/feature-gate-or_patterns.rs new file mode 100644 index 0000000000000..036a6095965bd --- /dev/null +++ b/src/test/ui/feature-gate/feature-gate-or_patterns.rs @@ -0,0 +1,9 @@ +#![crate_type="lib"] + +pub fn example(x: Option) { + match x { + Some(0 | 1 | 2) => {} + //~^ ERROR: or-patterns syntax is experimental + _ => {} + } +} diff --git a/src/test/ui/feature-gate/feature-gate-or_patterns.stderr b/src/test/ui/feature-gate/feature-gate-or_patterns.stderr new file mode 100644 index 0000000000000..aaabb54c1f017 --- /dev/null +++ b/src/test/ui/feature-gate/feature-gate-or_patterns.stderr @@ -0,0 +1,12 @@ +error[E0658]: or-patterns syntax is experimental + --> $DIR/feature-gate-or_patterns.rs:5:14 + | +LL | Some(0 | 1 | 2) => {} + | ^^^^^^^^^ + | + = note: for more information, see https://github.com/rust-lang/rust/issues/54883 + = help: add `#![feature(or_patterns)]` 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/parser/pat-lt-bracket-6.rs b/src/test/ui/parser/pat-lt-bracket-6.rs index 7b9721830993e..f27caa5d78c8e 100644 --- a/src/test/ui/parser/pat-lt-bracket-6.rs +++ b/src/test/ui/parser/pat-lt-bracket-6.rs @@ -2,8 +2,9 @@ fn main() { struct Test(&'static u8, [u8; 0]); let x = Test(&0, []); - let Test(&desc[..]) = x; //~ ERROR: expected one of `)`, `,`, or `@`, found `[` - //~^ ERROR subslice patterns are unstable + let Test(&desc[..]) = x; + //~^ ERROR: expected one of `)`, `,`, `@`, or `|`, found `[` + //~^^ ERROR subslice patterns are unstable } const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types diff --git a/src/test/ui/parser/pat-lt-bracket-6.stderr b/src/test/ui/parser/pat-lt-bracket-6.stderr index 201465b2c850c..6f08f0a9d95ef 100644 --- a/src/test/ui/parser/pat-lt-bracket-6.stderr +++ b/src/test/ui/parser/pat-lt-bracket-6.stderr @@ -1,8 +1,8 @@ -error: expected one of `)`, `,`, or `@`, found `[` +error: expected one of `)`, `,`, `@`, or `|`, found `[` --> $DIR/pat-lt-bracket-6.rs:5:19 | LL | let Test(&desc[..]) = x; - | ^ expected one of `)`, `,`, or `@` here + | ^ expected one of `)`, `,`, `@`, or `|` here error[E0658]: subslice patterns are unstable --> $DIR/pat-lt-bracket-6.rs:5:20 @@ -14,7 +14,7 @@ LL | let Test(&desc[..]) = x; = help: add `#![feature(slice_patterns)]` to the crate attributes to enable error[E0308]: mismatched types - --> $DIR/pat-lt-bracket-6.rs:9:30 + --> $DIR/pat-lt-bracket-6.rs:10:30 | LL | const RECOVERY_WITNESS: () = 0; | ^ expected (), found integer diff --git a/src/test/ui/parser/pat-lt-bracket-7.rs b/src/test/ui/parser/pat-lt-bracket-7.rs index 020fdb845e8b5..327aef5ad1570 100644 --- a/src/test/ui/parser/pat-lt-bracket-7.rs +++ b/src/test/ui/parser/pat-lt-bracket-7.rs @@ -2,7 +2,8 @@ fn main() { struct Thing(u8, [u8; 0]); let foo = core::iter::empty(); - for Thing(x[]) in foo {} //~ ERROR: expected one of `)`, `,`, or `@`, found `[` + for Thing(x[]) in foo {} + //~^ ERROR: expected one of `)`, `,`, `@`, or `|`, found `[` } const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types diff --git a/src/test/ui/parser/pat-lt-bracket-7.stderr b/src/test/ui/parser/pat-lt-bracket-7.stderr index 17557efa49e80..196f1c0ae914f 100644 --- a/src/test/ui/parser/pat-lt-bracket-7.stderr +++ b/src/test/ui/parser/pat-lt-bracket-7.stderr @@ -1,11 +1,11 @@ -error: expected one of `)`, `,`, or `@`, found `[` +error: expected one of `)`, `,`, `@`, or `|`, found `[` --> $DIR/pat-lt-bracket-7.rs:5:16 | LL | for Thing(x[]) in foo {} - | ^ expected one of `)`, `,`, or `@` here + | ^ expected one of `)`, `,`, `@`, or `|` here error[E0308]: mismatched types - --> $DIR/pat-lt-bracket-7.rs:8:30 + --> $DIR/pat-lt-bracket-7.rs:9:30 | LL | const RECOVERY_WITNESS: () = 0; | ^ expected (), found integer diff --git a/src/test/ui/parser/recover-for-loop-parens-around-head.rs b/src/test/ui/parser/recover-for-loop-parens-around-head.rs index e6c59fcf22dea..c6be2c90667c2 100644 --- a/src/test/ui/parser/recover-for-loop-parens-around-head.rs +++ b/src/test/ui/parser/recover-for-loop-parens-around-head.rs @@ -8,7 +8,7 @@ fn main() { let vec = vec![1, 2, 3]; for ( elem in vec ) { - //~^ ERROR expected one of `)`, `,`, or `@`, found `in` + //~^ ERROR expected one of `)`, `,`, `@`, or `|`, found `in` //~| ERROR unexpected closing `)` const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types } diff --git a/src/test/ui/parser/recover-for-loop-parens-around-head.stderr b/src/test/ui/parser/recover-for-loop-parens-around-head.stderr index c160e646c28b3..1b5b6cca09243 100644 --- a/src/test/ui/parser/recover-for-loop-parens-around-head.stderr +++ b/src/test/ui/parser/recover-for-loop-parens-around-head.stderr @@ -1,8 +1,8 @@ -error: expected one of `)`, `,`, or `@`, found `in` +error: expected one of `)`, `,`, `@`, or `|`, found `in` --> $DIR/recover-for-loop-parens-around-head.rs:10:16 | LL | for ( elem in vec ) { - | ^^ expected one of `)`, `,`, or `@` here + | ^^ expected one of `)`, `,`, `@`, or `|` here error: unexpected closing `)` --> $DIR/recover-for-loop-parens-around-head.rs:10:23