Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of or-patterns #61708

Merged
merged 2 commits into from
Aug 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/doc/unstable-book/src/language-features/or-patterns.md
Original file line number Diff line number Diff line change
@@ -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,
dlrobertson marked this conversation as resolved.
Show resolved Hide resolved
Quux,
}

pub fn example(maybe_foo: Option<Foo>) {
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`");
}
}
}
```
5 changes: 5 additions & 0 deletions src/librustc/cfg/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
self.add_ast_node(pat.hir_id.local_id, &[pats_exit])
}

PatKind::Or(ref pats) => {
let branches: Vec<_> = pats.iter().map(|p| self.pat(p, pred)).collect();
self.add_ast_node(pat.hir_id.local_id, &branches)
}

PatKind::Slice(ref pre, ref vec, ref post) => {
let pre_exit = self.pats_all(pre.iter(), pred);
let vec_exit = self.pats_all(vec.iter(), pre_exit);
Expand Down
1 change: 1 addition & 0 deletions src/librustc/hir/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ pub fn walk_pat<'v, V: Visitor<'v>>(visitor: &mut V, pattern: &'v Pat) {
visitor.visit_pat(&field.pat)
}
}
PatKind::Or(ref pats) => walk_list!(visitor, visit_pat, pats),
PatKind::Tuple(ref tuple_elements, _) => {
walk_list!(visitor, visit_pat, tuple_elements);
}
Expand Down
3 changes: 3 additions & 0 deletions src/librustc/hir/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2669,6 +2669,9 @@ impl<'a> LoweringContext<'a> {
let (pats, ddpos) = self.lower_pat_tuple(pats, "tuple struct");
hir::PatKind::TupleStruct(qpath, pats, ddpos)
}
PatKind::Or(ref pats) => {
hir::PatKind::Or(pats.iter().map(|x| self.lower_pat(x)).collect())
}
PatKind::Path(ref qself, ref path) => {
let qpath = self.lower_qpath(
p.id,
Expand Down
5 changes: 5 additions & 0 deletions src/librustc/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ impl Pat {
PatKind::TupleStruct(_, ref s, _) | PatKind::Tuple(ref s, _) => {
s.iter().all(|p| p.walk_(it))
}
PatKind::Or(ref pats) => pats.iter().all(|p| p.walk_(it)),
PatKind::Box(ref s) | PatKind::Ref(ref s, _) => {
s.walk_(it)
}
Expand Down Expand Up @@ -976,6 +977,10 @@ pub enum PatKind {
/// `0 <= position <= subpats.len()`
TupleStruct(QPath, HirVec<P<Pat>>, Option<usize>),

/// An or-pattern `A | B | C`.
dlrobertson marked this conversation as resolved.
Show resolved Hide resolved
/// Invariant: `pats.len() >= 2`.
Or(HirVec<P<Pat>>),

/// A path pattern for an unit struct/variant or a (maybe-associated) constant.
Path(QPath),

Expand Down
3 changes: 3 additions & 0 deletions src/librustc/hir/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,9 @@ impl<'a> State<'a> {
self.s.space();
self.s.word("}");
}
PatKind::Or(ref pats) => {
self.strsep("|", true, Inconsistent, &pats[..], |s, p| s.print_pat(&p));
}
PatKind::Tuple(ref elts, ddpos) => {
self.popen();
if let Some(ddpos) = ddpos {
Expand Down
6 changes: 6 additions & 0 deletions src/librustc/middle/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,12 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {
}
}

PatKind::Or(ref pats) => {
for pat in pats {
self.cat_pattern_(cmt.clone(), &pat, op)?;
}
}

PatKind::Binding(.., Some(ref subpat)) => {
self.cat_pattern_(cmt, &subpat, op)?;
}
Expand Down
5 changes: 5 additions & 0 deletions src/librustc_mir/build/matches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.visit_bindings(&subpattern.pattern, subpattern_user_ty, f);
}
}
PatternKind::Or { ref pats } => {
for pat in pats {
self.visit_bindings(&pat, pattern_user_ty.clone(), f);
}
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/librustc_mir/build/matches/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
candidate.match_pairs.push(MatchPair::new(place, subpattern));
Ok(())
}

PatternKind::Or { .. } => {
Err(match_pair)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for the vector to be length 1 here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the pattern Some(0 |) will have length 1, in which case we can return Ok(()) here (or normalise it earlier on).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently don't parse a trailing | as a valid pattern, so the vector would be at least length 2 right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, yes, that's true. Can you add a comment above Err saying that the vector will always have length at least 2, and so will always be refutable?

}
}
}
}
2 changes: 2 additions & 0 deletions src/librustc_mir/build/matches/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
PatternKind::AscribeUserType { .. } |
PatternKind::Array { .. } |
PatternKind::Wild |
PatternKind::Or { .. } |
PatternKind::Binding { .. } |
PatternKind::Leaf { .. } |
PatternKind::Deref { .. } => {
Expand Down Expand Up @@ -130,6 +131,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
PatternKind::Slice { .. } |
PatternKind::Array { .. } |
PatternKind::Wild |
PatternKind::Or { .. } |
PatternKind::Binding { .. } |
PatternKind::AscribeUserType { .. } |
PatternKind::Leaf { .. } |
Expand Down
7 changes: 7 additions & 0 deletions src/librustc_mir/hair/pattern/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,9 @@ fn pat_constructors<'tcx>(cx: &mut MatchCheckCtxt<'_, 'tcx>,
Some(vec![Slice(pat_len)])
}
}
PatternKind::Or { .. } => {
bug!("support for or-patterns has not been fully implemented yet.");
}
}
}

Expand Down Expand Up @@ -1884,6 +1887,10 @@ fn specialize<'p, 'a: 'p, 'tcx>(
"unexpected ctor {:?} for slice pat", constructor)
}
}

PatternKind::Or { .. } => {
bug!("support for or-patterns has not been fully implemented yet.");
}
};
debug!("specialize({:#?}, {:#?}) = {:#?}", r[0], wild_patterns, head);

Expand Down
48 changes: 37 additions & 11 deletions src/librustc_mir/hair/pattern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ pub enum PatternKind<'tcx> {
slice: Option<Pattern<'tcx>>,
suffix: Vec<Pattern<'tcx>>,
},

/// An or-pattern, e.g. `p | q`.
/// Invariant: `pats.len() >= 2`.
Or {
pats: Vec<Pattern<'tcx>>,
},
}

#[derive(Copy, Clone, Debug, PartialEq)]
Expand All @@ -186,6 +192,18 @@ pub struct PatternRange<'tcx> {

impl<'tcx> fmt::Display for Pattern<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Printing lists is a chore.
let mut first = true;
let mut start_or_continue = |s| {
if first {
first = false;
""
} else {
s
}
};
let mut start_or_comma = || start_or_continue(", ");

match *self.kind {
PatternKind::Wild => write!(f, "_"),
PatternKind::AscribeUserType { ref subpattern, .. } =>
Expand Down Expand Up @@ -224,9 +242,6 @@ impl<'tcx> fmt::Display for Pattern<'tcx> {
}
};

let mut first = true;
let mut start_or_continue = || if first { first = false; "" } else { ", " };

if let Some(variant) = variant {
write!(f, "{}", variant.ident)?;

Expand All @@ -241,12 +256,12 @@ impl<'tcx> fmt::Display for Pattern<'tcx> {
continue;
}
let name = variant.fields[p.field.index()].ident;
write!(f, "{}{}: {}", start_or_continue(), name, p.pattern)?;
write!(f, "{}{}: {}", start_or_comma(), name, p.pattern)?;
printed += 1;
}

if printed < variant.fields.len() {
write!(f, "{}..", start_or_continue())?;
write!(f, "{}..", start_or_comma())?;
}

return write!(f, " }}");
Expand All @@ -257,7 +272,7 @@ impl<'tcx> fmt::Display for Pattern<'tcx> {
if num_fields != 0 || variant.is_none() {
write!(f, "(")?;
for i in 0..num_fields {
write!(f, "{}", start_or_continue())?;
write!(f, "{}", start_or_comma())?;

// Common case: the field is where we expect it.
if let Some(p) = subpatterns.get(i) {
Expand Down Expand Up @@ -305,25 +320,29 @@ impl<'tcx> fmt::Display for Pattern<'tcx> {
}
PatternKind::Slice { ref prefix, ref slice, ref suffix } |
PatternKind::Array { ref prefix, ref slice, ref suffix } => {
let mut first = true;
let mut start_or_continue = || if first { first = false; "" } else { ", " };
write!(f, "[")?;
for p in prefix {
write!(f, "{}{}", start_or_continue(), p)?;
write!(f, "{}{}", start_or_comma(), p)?;
}
if let Some(ref slice) = *slice {
write!(f, "{}", start_or_continue())?;
write!(f, "{}", start_or_comma())?;
match *slice.kind {
PatternKind::Wild => {}
_ => write!(f, "{}", slice)?
}
write!(f, "..")?;
}
for p in suffix {
write!(f, "{}{}", start_or_continue(), p)?;
write!(f, "{}{}", start_or_comma(), p)?;
}
write!(f, "]")
}
PatternKind::Or { ref pats } => {
for pat in pats {
write!(f, "{}{}", start_or_continue(" | "), pat)?;
}
Ok(())
}
}
}
}
Expand Down Expand Up @@ -655,6 +674,12 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {

self.lower_variant_or_leaf(res, pat.hir_id, pat.span, ty, subpatterns)
}

PatKind::Or(ref pats) => {
PatternKind::Or {
pats: pats.iter().map(|p| self.lower_pattern(p)).collect(),
}
}
};

Pattern {
Expand Down Expand Up @@ -1436,6 +1461,7 @@ impl<'tcx> PatternFoldable<'tcx> for PatternKind<'tcx> {
slice: slice.fold_with(folder),
suffix: suffix.fold_with(folder)
},
PatternKind::Or { ref pats } => PatternKind::Or { pats: pats.fold_with(folder) },
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/librustc_typeck/check/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let is_non_ref_pat = match pat.node {
PatKind::Struct(..) |
PatKind::TupleStruct(..) |
PatKind::Or(_) |
PatKind::Tuple(..) |
PatKind::Box(_) |
PatKind::Range(..) |
Expand Down Expand Up @@ -309,6 +310,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
PatKind::Struct(ref qpath, ref fields, etc) => {
self.check_pat_struct(pat, qpath, fields, etc, expected, def_bm, discrim_span)
}
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, discrim_span);
}
expected_ty
}
PatKind::Tuple(ref elements, ddpos) => {
let mut expected_len = elements.len();
if ddpos.is_some() {
Expand Down
3 changes: 3 additions & 0 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4107,6 +4107,9 @@ fn name_from_pat(p: &hir::Pat) -> String {
if etc { ", .." } else { "" }
)
}
PatKind::Or(ref pats) => {
pats.iter().map(|p| name_from_pat(&**p)).collect::<Vec<String>>().join(" | ")
}
PatKind::Tuple(ref elts, _) => format!("({})", elts.iter().map(|p| name_from_pat(&**p))
.collect::<Vec<String>>().join(", ")),
PatKind::Box(ref p) => name_from_pat(&**p),
Expand Down
11 changes: 8 additions & 3 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,9 +572,10 @@ impl Pat {
match &self.node {
PatKind::Ident(_, _, Some(p)) => p.walk(it),
PatKind::Struct(_, fields, _) => fields.iter().all(|field| field.pat.walk(it)),
PatKind::TupleStruct(_, s) | PatKind::Tuple(s) | PatKind::Slice(s) => {
s.iter().all(|p| p.walk(it))
}
PatKind::TupleStruct(_, s)
| PatKind::Tuple(s)
| PatKind::Slice(s)
| PatKind::Or(s) => s.iter().all(|p| p.walk(it)),
PatKind::Box(s) | PatKind::Ref(s, _) | PatKind::Paren(s) => s.walk(it),
PatKind::Wild
| PatKind::Rest
Expand Down Expand Up @@ -648,6 +649,10 @@ pub enum PatKind {
/// A tuple struct/variant pattern (`Variant(x, y, .., z)`).
TupleStruct(Path, Vec<P<Pat>>),

/// An or-pattern `A | B | C`.
dlrobertson marked this conversation as resolved.
Show resolved Hide resolved
/// Invariant: `pats.len() >= 2`.
Or(Vec<P<Pat>>),
Centril marked this conversation as resolved.
Show resolved Hide resolved

/// A possibly qualified path pattern.
/// Unqualified path patterns `A::B::C` can legally refer to variants, structs, constants
/// or associated constants. Qualified path patterns `<A>::B::C`/`<A as Trait>::B::C` can
Expand Down
5 changes: 5 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// -------------------------------------------------------------------------
Expand All @@ -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,
];

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions src/libsyntax/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,15 +1050,16 @@ pub fn noop_visit_pat<T: MutVisitor>(pat: &mut P<Pat>, vis: &mut T) {
vis.visit_span(span);
};
}
PatKind::Tuple(elems) => visit_vec(elems, |elem| vis.visit_pat(elem)),
PatKind::Box(inner) => vis.visit_pat(inner),
PatKind::Ref(inner, _mutbl) => vis.visit_pat(inner),
PatKind::Range(e1, e2, Spanned { span: _, node: _ }) => {
vis.visit_expr(e1);
vis.visit_expr(e2);
vis.visit_span(span);
}
PatKind::Slice(elems) => visit_vec(elems, |elem| vis.visit_pat(elem)),
PatKind::Tuple(elems)
| PatKind::Slice(elems)
| PatKind::Or(elems) => visit_vec(elems, |elem| vis.visit_pat(elem)),
PatKind::Paren(inner) => vis.visit_pat(inner),
PatKind::Mac(mac) => vis.visit_mac(mac),
}
Expand Down
Loading