From: Jakub Wieczorek Date: Wed, 25 Jun 2014 17:07:37 +0000 (+0200) Subject: Change exhaustiveness analysis to permit multiple constructors per pattern X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=9b3f9d94441340f0cdf6ec59aab739baef0f1ac0;p=rust.git Change exhaustiveness analysis to permit multiple constructors per pattern Slice patterns are different from the rest in that a single slice pattern does not have a distinct constructor if it contains a variable-length subslice pattern. For example, the pattern [a, b, ..tail] can match a slice of length 2, 3, 4 and so on. As a result, the decision tree for exhaustiveness and redundancy analysis should explore each of those constructors separately to determine if the pattern could be useful when specialized for any of them. --- diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs index 27b826b9d1a..1400e207ab1 100644 --- a/src/librustc/middle/check_match.rs +++ b/src/librustc/middle/check_match.rs @@ -8,17 +8,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![allow(non_camel_case_types)] - use middle::const_eval::{compare_const_vals, const_bool, const_float, const_nil, const_val}; use middle::const_eval::{eval_const_expr, lookup_const_by_id}; use middle::def::*; use middle::pat_util::*; use middle::ty::*; use middle::ty; - +use std::fmt; use std::gc::{Gc, GC}; -use std::iter; +use std::iter::AdditiveIterator; +use std::iter::range_inclusive; use syntax::ast::*; use syntax::ast_util::{is_unguarded, walk_pat}; use syntax::codemap::{Span, Spanned, DUMMY_SP}; @@ -28,7 +27,71 @@ use syntax::visit::{Visitor, FnKind}; use util::ppaux::ty_to_str; -type Matrix = Vec>>; +struct Matrix(Vec>>); + +/// Pretty-printer for matrices of patterns, example: +/// ++++++++++++++++++++++++++ +/// + _ + [] + +/// ++++++++++++++++++++++++++ +/// + true + [First] + +/// ++++++++++++++++++++++++++ +/// + true + [Second(true)] + +/// ++++++++++++++++++++++++++ +/// + false + [_] + +/// ++++++++++++++++++++++++++ +/// + _ + [_, _, ..tail] + +/// ++++++++++++++++++++++++++ +impl fmt::Show for Matrix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "\n")); + + let &Matrix(ref m) = self; + let pretty_printed_matrix: Vec> = m.iter().map(|row| { + row.iter().map(|&pat| pat_to_str(pat)).collect::>() + }).collect(); + + let column_count = m.iter().map(|row| row.len()).max().unwrap_or(0u); + assert!(m.iter().all(|row| row.len() == column_count)); + let column_widths: Vec = range(0, column_count).map(|col| { + pretty_printed_matrix.iter().map(|row| row.get(col).len()).max().unwrap_or(0u) + }).collect(); + + let total_width = column_widths.iter().map(|n| *n).sum() + column_count * 3 + 1; + let br = String::from_char(total_width, '+'); + try!(write!(f, "{}\n", br)); + for row in pretty_printed_matrix.move_iter() { + try!(write!(f, "+")); + for (column, pat_str) in row.move_iter().enumerate() { + try!(write!(f, " ")); + f.width = Some(*column_widths.get(column)); + try!(f.pad(pat_str.as_slice())); + try!(write!(f, " +")); + } + try!(write!(f, "\n")); + try!(write!(f, "{}\n", br)); + } + Ok(()) + } +} + +struct MatchCheckCtxt<'a> { + tcx: &'a ty::ctxt +} + +#[deriving(Clone, PartialEq)] +enum Constructor { + /// The constructor of all patterns that don't vary by constructor, + /// e.g. struct patterns and fixed-length arrays. + Single, + /// Enum variants. + Variant(DefId), + /// Literal values. + ConstantValue(const_val), + /// Ranges of literal values (2..5). + ConstantRange(const_val, const_val), + /// Array patterns of length n. + Slice(uint) +} #[deriving(Clone)] enum Usefulness { @@ -50,22 +113,6 @@ fn useful(self) -> Option>> { } } -fn def_to_path(tcx: &ty::ctxt, id: DefId) -> Path { - ty::with_path(tcx, id, |mut path| Path { - global: false, - segments: path.last().map(|elem| PathSegment { - identifier: Ident::new(elem.name()), - lifetimes: vec!(), - types: OwnedSlice::empty() - }).move_iter().collect(), - span: DUMMY_SP, - }) -} - -struct MatchCheckCtxt<'a> { - tcx: &'a ty::ctxt, -} - impl<'a> Visitor<()> for MatchCheckCtxt<'a> { fn visit_expr(&mut self, ex: &Expr, _: ()) { check_expr(self, ex); @@ -78,11 +125,8 @@ fn visit_fn(&mut self, fk: &FnKind, fd: &FnDecl, b: &Block, s: Span, _: NodeId, } } -pub fn check_crate(tcx: &ty::ctxt, - krate: &Crate) { - let mut cx = MatchCheckCtxt { - tcx: tcx, - }; +pub fn check_crate(tcx: &ty::ctxt, krate: &Crate) { + let mut cx = MatchCheckCtxt { tcx: tcx, }; visit::walk_crate(&mut cx, krate, ()); @@ -116,12 +160,12 @@ fn check_expr(cx: &mut MatchCheckCtxt, ex: &Expr) { // If the type *is* empty, it's vacuously exhaustive return; } - let m: Matrix = arms + let m: Matrix = Matrix(arms .iter() .filter(|&arm| is_unguarded(arm)) .flat_map(|arm| arm.pats.iter()) .map(|pat| vec!(pat.clone())) - .collect(); + .collect()); check_exhaustive(cx, ex.span, &m); }, _ => () @@ -130,7 +174,7 @@ fn check_expr(cx: &mut MatchCheckCtxt, ex: &Expr) { // Check for unreachable patterns fn check_arms(cx: &MatchCheckCtxt, arms: &[Arm]) { - let mut seen = Vec::new(); + let mut seen = Matrix(vec!()); for arm in arms.iter() { for pat in arm.pats.iter() { // Check that we do not match against a static NaN (#6804) @@ -161,7 +205,11 @@ fn check_arms(cx: &MatchCheckCtxt, arms: &[Arm]) { NotUseful => cx.tcx.sess.span_err(pat.span, "unreachable pattern"), _ => () } - if arm.guard.is_none() { seen.push(v); } + if arm.guard.is_none() { + let Matrix(mut rows) = seen; + rows.push(v); + seen = Matrix(rows); + } } } } @@ -175,10 +223,6 @@ fn raw_pat(p: Gc) -> Gc { fn check_exhaustive(cx: &MatchCheckCtxt, sp: Span, m: &Matrix) { match is_useful(cx, m, [wild()], ConstructWitness) { - NotUseful => { - // This is good, wildcard pattern isn't reachable - return; - } Useful(pats) => { let witness = match pats.as_slice() { [witness] => witness, @@ -188,38 +232,58 @@ fn check_exhaustive(cx: &MatchCheckCtxt, sp: Span, m: &Matrix) { let msg = format!("non-exhaustive patterns: `{0}` not covered", pat_to_str(&*witness)); cx.tcx.sess.span_err(sp, msg.as_slice()); } + NotUseful => { + // This is good, wildcard pattern isn't reachable + } } } -#[deriving(Clone, PartialEq)] -enum ctor { - single, - variant(DefId), - val(const_val), - range(const_val, const_val), - vec(uint) -} - fn const_val_to_expr(value: &const_val) -> Gc { let node = match value { &const_bool(b) => LitBool(b), &const_nil => LitNil, _ => unreachable!() }; - box(GC) Expr { + box (GC) Expr { id: 0, node: ExprLit(box(GC) Spanned { node: node, span: DUMMY_SP }), span: DUMMY_SP } } -fn construct_witness(cx: &MatchCheckCtxt, ctor: &ctor, pats: Vec>, lty: ty::t) -> Gc { - let pat = match ty::get(lty).sty { +fn def_to_path(tcx: &ty::ctxt, id: DefId) -> Path { + ty::with_path(tcx, id, |mut path| Path { + global: false, + segments: path.last().map(|elem| PathSegment { + identifier: Ident::new(elem.name()), + lifetimes: vec!(), + types: OwnedSlice::empty() + }).move_iter().collect(), + span: DUMMY_SP, + }) +} + +/// Constructs a partial witness for a pattern given a list of +/// patterns expanded by the specialization step. +/// +/// When a pattern P is discovered to be useful, this function is used bottom-up +/// to reconstruct a complete witness, e.g. a pattern P' that covers a subset +/// of values, V, where each value in that set is not covered by any previously +/// used patterns and is covered by the pattern P'. Examples: +/// +/// left_ty: tuple of 3 elements +/// pats: [10, 20, _] => (10, 20, _) +/// +/// left_ty: struct X { a: (bool, &'static str), b: uint} +/// pats: [(false, "foo"), 42] => X { a: (false, "foo"), b: 42 } +fn construct_witness(cx: &MatchCheckCtxt, ctor: &Constructor, + pats: Vec>, left_ty: ty::t) -> Gc { + let pat = match ty::get(left_ty).sty { ty::ty_tup(_) => PatTup(pats), ty::ty_enum(cid, _) | ty::ty_struct(cid, _) => { let (vid, is_structure) = match ctor { - &variant(vid) => (vid, + &Variant(vid) => (vid, ty::enum_variant_with_id(cx.tcx, cid, vid).arg_names.is_some()), _ => (cid, true) }; @@ -235,103 +299,95 @@ fn construct_witness(cx: &MatchCheckCtxt, ctor: &ctor, pats: Vec>, lty: } else { PatEnum(def_to_path(cx.tcx, vid), Some(pats)) } - }, + } ty::ty_rptr(_, ty::mt { ty: ty, .. }) => { match ty::get(ty).sty { + ty::ty_vec(_, Some(n)) => match ctor { + &Single => { + assert_eq!(pats.len(), n); + PatVec(pats, None, vec!()) + }, + _ => unreachable!() + }, ty::ty_vec(_, None) => match ctor { - &vec(_) => PatVec(pats, None, vec!()), + &Slice(n) => { + assert_eq!(pats.len(), n); + PatVec(pats, None, vec!()) + }, _ => unreachable!() }, ty::ty_str => PatWild, + _ => { assert_eq!(pats.len(), 1); PatRegion(pats.get(0).clone()) } } - }, + } ty::ty_box(_) => { assert_eq!(pats.len(), 1); PatBox(pats.get(0).clone()) - }, + } + + ty::ty_vec(_, Some(len)) => { + assert_eq!(pats.len(), len); + PatVec(pats, None, vec!()) + } _ => { - match ctor { - &vec(_) => PatVec(pats, None, vec!()), - &val(ref v) => PatLit(const_val_to_expr(v)), + match *ctor { + ConstantValue(ref v) => PatLit(const_val_to_expr(v)), _ => PatWild } } }; - box(GC) Pat { + box (GC) Pat { id: 0, node: pat, span: DUMMY_SP } } -fn missing_constructor(cx: &MatchCheckCtxt, m: &Matrix, left_ty: ty::t) -> Option { - let used_constructors: Vec = m.iter() - .filter_map(|r| pat_ctor_id(cx, left_ty, *r.get(0))) +fn missing_constructor(cx: &MatchCheckCtxt, &Matrix(ref rows): &Matrix, + left_ty: ty::t, max_slice_length: uint) -> Option { + let used_constructors: Vec = rows.iter() + .flat_map(|row| pat_constructors(cx, *row.get(0), left_ty, max_slice_length).move_iter()) .collect(); - - all_constructors(cx, m, left_ty) + all_constructors(cx, left_ty, max_slice_length) .move_iter() .find(|c| !used_constructors.contains(c)) } -fn all_constructors(cx: &MatchCheckCtxt, m: &Matrix, left_ty: ty::t) -> Vec { - // This produces a list of all vector constructors that we would expect to appear - // in an exhaustive set of patterns. Because such a list would normally be infinite, - // we narrow it down to only those constructors that actually appear in the inspected - // column, plus, any that are missing and not covered by a pattern with a destructured slice. - fn vec_constructors(m: &Matrix) -> Vec { - let max_vec_len = m.iter().map(|r| match r.get(0).node { - PatVec(ref before, _, ref after) => before.len() + after.len(), - _ => 0u - }).max().unwrap_or(0u); - let min_vec_len_with_slice = m.iter().map(|r| match r.get(0).node { - PatVec(ref before, Some(_), ref after) => before.len() + after.len(), - _ => max_vec_len + 1 - }).min().unwrap_or(max_vec_len + 1); - let other_lengths = m.iter().map(|r| match r.get(0).node { - PatVec(ref before, _, ref after) => before.len() + after.len(), - _ => 0u - }).filter(|&len| len > min_vec_len_with_slice); - iter::range_inclusive(0u, min_vec_len_with_slice) - .chain(other_lengths) - .map(|len| vec(len)) - .collect() - } - +/// This determines the set of all possible constructors of a pattern matching +/// values of type `left_ty`. For vectors, this would normally be an infinite set +/// but is instead bounded by the maximum fixed length of slice patterns in +/// the column of patterns being analyzed. +fn all_constructors(cx: &MatchCheckCtxt, left_ty: ty::t, + max_slice_length: uint) -> Vec { match ty::get(left_ty).sty { ty::ty_bool => - [true, false].iter().map(|b| val(const_bool(*b))).collect(), + [true, false].iter().map(|b| ConstantValue(const_bool(*b))).collect(), ty::ty_nil => - vec!(val(const_nil)), + vec!(ConstantValue(const_nil)), ty::ty_rptr(_, ty::mt { ty: ty, .. }) => match ty::get(ty).sty { - ty::ty_vec(_, None) => vec_constructors(m), - _ => vec!(single) + ty::ty_vec(_, None) => + range_inclusive(0, max_slice_length).map(|length| Slice(length)).collect(), + _ => vec!(Single) }, ty::ty_enum(eid, _) => ty::enum_variants(cx.tcx, eid) .iter() - .map(|va| variant(va.id)) + .map(|va| Variant(va.id)) .collect(), - ty::ty_vec(_, None) => - vec_constructors(m), - - ty::ty_vec(_, Some(n)) => - vec!(vec(n)), - _ => - vec!(single) + vec!(Single) } } @@ -348,15 +404,16 @@ fn vec_constructors(m: &Matrix) -> Vec { // Note: is_useful doesn't work on empty types, as the paper notes. // So it assumes that v is non-empty. -fn is_useful(cx: &MatchCheckCtxt, m: &Matrix, v: &[Gc], - witness: WitnessPreference) -> Usefulness { - if m.len() == 0u { +fn is_useful(cx: &MatchCheckCtxt, m @ &Matrix(ref rows): &Matrix, + v: &[Gc], witness: WitnessPreference) -> Usefulness { + debug!("{:}", m); + if rows.len() == 0u { return Useful(vec!()); } - if m.get(0).len() == 0u { + if rows.get(0).len() == 0u { return NotUseful; } - let real_pat = match m.iter().find(|r| r.get(0).id != 0) { + let real_pat = match rows.iter().find(|r| r.get(0).id != 0) { Some(r) => { match r.get(0).node { // An arm of the form `ref x @ sub_pat` has type @@ -374,10 +431,16 @@ fn is_useful(cx: &MatchCheckCtxt, m: &Matrix, v: &[Gc], ty::pat_ty(cx.tcx, &*real_pat) }; - match pat_ctor_id(cx, left_ty, v[0]) { - None => match missing_constructor(cx, m, left_ty) { + let max_slice_length = rows.iter().filter_map(|row| match row.get(0).node { + PatVec(ref before, _, ref after) => Some(before.len() + after.len()), + _ => None + }).max().map_or(0, |v| v + 1); + + let constructors = pat_constructors(cx, v[0], left_ty, max_slice_length); + if constructors.is_empty() { + match missing_constructor(cx, m, left_ty, max_slice_length) { None => { - all_constructors(cx, m, left_ty).move_iter().filter_map(|c| { + all_constructors(cx, left_ty, max_slice_length).move_iter().filter_map(|c| { is_useful_specialized(cx, m, v, c.clone(), left_ty, witness).useful().map(|pats| { Useful(match witness { @@ -400,14 +463,15 @@ fn is_useful(cx: &MatchCheckCtxt, m: &Matrix, v: &[Gc], }).nth(0).unwrap_or(NotUseful) }, - Some(ctor) => { - let matrix = m.iter().filter_map(|r| default(cx, r.as_slice())).collect(); + Some(constructor) => { + let matrix = Matrix(rows.iter().filter_map(|r| + default(cx, r.as_slice())).collect()); match is_useful(cx, &matrix, v.tail(), witness) { Useful(pats) => Useful(match witness { ConstructWitness => { - let arity = constructor_arity(cx, &ctor, left_ty); + let arity = constructor_arity(cx, &constructor, left_ty); let wild_pats = Vec::from_elem(arity, wild()); - let enum_pat = construct_witness(cx, &ctor, wild_pats, left_ty); + let enum_pat = construct_witness(cx, &constructor, wild_pats, left_ty); (vec!(enum_pat)).append(pats.as_slice()) } LeaveOutWitness => vec!() @@ -415,64 +479,82 @@ fn is_useful(cx: &MatchCheckCtxt, m: &Matrix, v: &[Gc], result => result } } - }, - - Some(v0_ctor) => is_useful_specialized(cx, m, v, v0_ctor, left_ty, witness) + } + } else { + constructors.move_iter().filter_map(|c| { + is_useful_specialized(cx, m, v, c.clone(), left_ty, witness) + .useful().map(|pats| Useful(pats)) + }).nth(0).unwrap_or(NotUseful) } } -fn is_useful_specialized(cx: &MatchCheckCtxt, m: &Matrix, v: &[Gc], - ctor: ctor, lty: ty::t, witness: WitnessPreference) -> Usefulness { +fn is_useful_specialized(cx: &MatchCheckCtxt, &Matrix(ref m): &Matrix, v: &[Gc], + ctor: Constructor, lty: ty::t, witness: WitnessPreference) -> Usefulness { let arity = constructor_arity(cx, &ctor, lty); - let matrix = m.iter().filter_map(|r| { + let matrix = Matrix(m.iter().filter_map(|r| { specialize(cx, r.as_slice(), &ctor, arity) - }).collect(); + }).collect()); match specialize(cx, v, &ctor, arity) { Some(v) => is_useful(cx, &matrix, v.as_slice(), witness), None => NotUseful } } -fn pat_ctor_id(cx: &MatchCheckCtxt, left_ty: ty::t, p: Gc) -> Option { +/// Determines the constructors that the given pattern can be specialized to. +/// +/// In most cases, there's only one constructor that a specific pattern +/// represents, such as a specific enum variant or a specific literal value. +/// Slice patterns, however, can match slices of different lengths. For instance, +/// `[a, b, ..tail]` can match a slice of length 2, 3, 4 and so on. +/// +/// On the other hand, a wild pattern and an identifier pattern cannot be +/// specialized in any way. +fn pat_constructors(cx: &MatchCheckCtxt, p: Gc, + left_ty: ty::t, max_slice_length: uint) -> Vec { let pat = raw_pat(p); match pat.node { PatIdent(..) => match cx.tcx.def_map.borrow().find(&pat.id) { Some(&DefStatic(did, false)) => { let const_expr = lookup_const_by_id(cx.tcx, did).unwrap(); - Some(val(eval_const_expr(cx.tcx, &*const_expr))) + vec!(ConstantValue(eval_const_expr(cx.tcx, &*const_expr))) }, - Some(&DefVariant(_, id, _)) => Some(variant(id)), - _ => None + Some(&DefVariant(_, id, _)) => vec!(Variant(id)), + _ => vec!() }, PatEnum(..) => match cx.tcx.def_map.borrow().find(&pat.id) { Some(&DefStatic(did, false)) => { let const_expr = lookup_const_by_id(cx.tcx, did).unwrap(); - Some(val(eval_const_expr(cx.tcx, &*const_expr))) + vec!(ConstantValue(eval_const_expr(cx.tcx, &*const_expr))) }, - Some(&DefVariant(_, id, _)) => Some(variant(id)), - _ => Some(single) + Some(&DefVariant(_, id, _)) => vec!(Variant(id)), + _ => vec!(Single) }, PatStruct(..) => match cx.tcx.def_map.borrow().find(&pat.id) { - Some(&DefVariant(_, id, _)) => Some(variant(id)), - _ => Some(single) + Some(&DefVariant(_, id, _)) => vec!(Variant(id)), + _ => vec!(Single) }, PatLit(expr) => - Some(val(eval_const_expr(cx.tcx, &*expr))), + vec!(ConstantValue(eval_const_expr(cx.tcx, &*expr))), PatRange(lo, hi) => - Some(range(eval_const_expr(cx.tcx, &*lo), eval_const_expr(cx.tcx, &*hi))), - PatVec(ref before, _, ref after) => match ty::get(left_ty).sty { - ty::ty_vec(_, Some(n)) => - Some(vec(n)), - _ => - Some(vec(before.len() + after.len())) - }, + vec!(ConstantRange(eval_const_expr(cx.tcx, &*lo), eval_const_expr(cx.tcx, &*hi))), + PatVec(ref before, ref slice, ref after) => + match ty::get(left_ty).sty { + ty::ty_vec(_, Some(_)) => vec!(Single), + _ => if slice.is_some() { + range_inclusive(before.len() + after.len(), max_slice_length) + .map(|length| Slice(length)) + .collect() + } else { + vec!(Slice(before.len() + after.len())) + } + }, PatBox(_) | PatTup(_) | PatRegion(..) => - Some(single), + vec!(Single), PatWild | PatWildMulti => - None, + vec!(), PatMac(_) => cx.tcx.sess.bug("unexpanded macro") } @@ -482,53 +564,53 @@ fn is_wild(cx: &MatchCheckCtxt, p: Gc) -> bool { let pat = raw_pat(p); match pat.node { PatWild | PatWildMulti => true, - PatIdent(_, _, _) => { + PatIdent(_, _, _) => match cx.tcx.def_map.borrow().find(&pat.id) { Some(&DefVariant(_, _, _)) | Some(&DefStatic(..)) => false, _ => true - } - } + }, + PatVec(ref before, Some(_), ref after) => + before.is_empty() && after.is_empty(), _ => false } } -fn constructor_arity(cx: &MatchCheckCtxt, ctor: &ctor, ty: ty::t) -> uint { +/// This computes the arity of a constructor. The arity of a constructor +/// is how many subpattern patterns of that constructor should be expanded to. +/// +/// For instance, a tuple pattern (_, 42u, Some([])) has the arity of 3. +/// A struct pattern's arity is the number of fields it contains, etc. +fn constructor_arity(cx: &MatchCheckCtxt, ctor: &Constructor, ty: ty::t) -> uint { match ty::get(ty).sty { ty::ty_tup(ref fs) => fs.len(), ty::ty_box(_) | ty::ty_uniq(_) => 1u, ty::ty_rptr(_, ty::mt { ty: ty, .. }) => match ty::get(ty).sty { ty::ty_vec(_, None) => match *ctor { - vec(n) => n, - _ => 0u + Slice(length) => length, + _ => unreachable!() }, ty::ty_str => 0u, _ => 1u }, ty::ty_enum(eid, _) => { match *ctor { - variant(id) => enum_variant_with_id(cx.tcx, eid, id).args.len(), + Variant(id) => enum_variant_with_id(cx.tcx, eid, id).args.len(), _ => unreachable!() } } ty::ty_struct(cid, _) => ty::lookup_struct_fields(cx.tcx, cid).len(), - ty::ty_vec(_, _) => match *ctor { - vec(n) => n, - _ => 0u - }, + ty::ty_vec(_, Some(n)) => n, _ => 0u } } -fn wild() -> Gc { - box(GC) Pat {id: 0, node: PatWild, span: DUMMY_SP} -} - -fn range_covered_by_constructor(ctor_id: &ctor, from: &const_val, to: &const_val) -> Option { - let (c_from, c_to) = match *ctor_id { - val(ref value) => (value, value), - range(ref from, ref to) => (from, to), - single => return Some(true), - _ => unreachable!() +fn range_covered_by_constructor(ctor: &Constructor, + from: &const_val,to: &const_val) -> Option { + let (c_from, c_to) = match *ctor { + ConstantValue(ref value) => (value, value), + ConstantRange(ref from, ref to) => (from, to), + Single => return Some(true), + _ => unreachable!() }; let cmp_from = compare_const_vals(c_from, from); let cmp_to = compare_const_vals(c_to, to); @@ -538,22 +620,30 @@ fn range_covered_by_constructor(ctor_id: &ctor, from: &const_val, to: &const_val } } +/// This is the main specialization step. It expands the first pattern in the given row +/// into `arity` patterns based on the constructor. For most patterns, the step is trivial, +/// for instance tuple patterns are flattened and box patterns expand into their inner pattern. +/// +/// OTOH, slice patterns with a subslice pattern (..tail) can be expanded into multiple +/// different patterns. +/// Structure patterns with a partial wild pattern (Foo { a: 42, .. }) have their missing +/// fields filled with wild patterns. fn specialize(cx: &MatchCheckCtxt, r: &[Gc], - ctor_id: &ctor, arity: uint) -> Option>> { + constructor: &Constructor, arity: uint) -> Option>> { let &Pat { - id: ref pat_id, node: ref n, span: ref pat_span + id: pat_id, node: ref node, span: pat_span } = &(*raw_pat(r[0])); - let head: Option>> = match n { - &PatWild => { - Some(Vec::from_elem(arity, wild())) - } - &PatWildMulti => { - Some(Vec::from_elem(arity, wild())) - } + let head: Option>> = match node { + &PatWild => + Some(Vec::from_elem(arity, wild())), + + &PatWildMulti => + Some(Vec::from_elem(arity, wild())), + &PatIdent(_, _, _) => { - let opt_def = cx.tcx.def_map.borrow().find_copy(pat_id); + let opt_def = cx.tcx.def_map.borrow().find_copy(&pat_id); match opt_def { - Some(DefVariant(_, id, _)) => if *ctor_id == variant(id) { + Some(DefVariant(_, id, _)) => if *constructor == Variant(id) { Some(vec!()) } else { None @@ -561,11 +651,11 @@ fn specialize(cx: &MatchCheckCtxt, r: &[Gc], Some(DefStatic(did, _)) => { let const_expr = lookup_const_by_id(cx.tcx, did).unwrap(); let e_v = eval_const_expr(cx.tcx, &*const_expr); - match range_covered_by_constructor(ctor_id, &e_v, &e_v) { + match range_covered_by_constructor(constructor, &e_v, &e_v) { Some(true) => Some(vec!()), Some(false) => None, None => { - cx.tcx.sess.span_err(*pat_span, "mismatched types between arms"); + cx.tcx.sess.span_err(pat_span, "mismatched types between arms"); None } } @@ -575,22 +665,23 @@ fn specialize(cx: &MatchCheckCtxt, r: &[Gc], } } } + &PatEnum(_, ref args) => { - let def = cx.tcx.def_map.borrow().get_copy(pat_id); + let def = cx.tcx.def_map.borrow().get_copy(&pat_id); match def { DefStatic(did, _) => { let const_expr = lookup_const_by_id(cx.tcx, did).unwrap(); let e_v = eval_const_expr(cx.tcx, &*const_expr); - match range_covered_by_constructor(ctor_id, &e_v, &e_v) { + match range_covered_by_constructor(constructor, &e_v, &e_v) { Some(true) => Some(vec!()), Some(false) => None, None => { - cx.tcx.sess.span_err(*pat_span, "mismatched types between arms"); + cx.tcx.sess.span_err(pat_span, "mismatched types between arms"); None } } } - DefVariant(_, id, _) if *ctor_id != variant(id) => None, + DefVariant(_, id, _) if *constructor != Variant(id) => None, DefVariant(..) | DefFn(..) | DefStruct(..) => { Some(match args { &Some(ref args) => args.clone(), @@ -603,9 +694,9 @@ fn specialize(cx: &MatchCheckCtxt, r: &[Gc], &PatStruct(_, ref pattern_fields, _) => { // Is this a struct or an enum variant? - let def = cx.tcx.def_map.borrow().get_copy(pat_id); + let def = cx.tcx.def_map.borrow().get_copy(&pat_id); let class_id = match def { - DefVariant(_, variant_id, _) => if *ctor_id == variant(variant_id) { + DefVariant(_, variant_id, _) => if *constructor == Variant(variant_id) { Some(variant_id) } else { None @@ -633,11 +724,11 @@ fn specialize(cx: &MatchCheckCtxt, r: &[Gc], &PatLit(ref expr) => { let expr_value = eval_const_expr(cx.tcx, &**expr); - match range_covered_by_constructor(ctor_id, &expr_value, &expr_value) { + match range_covered_by_constructor(constructor, &expr_value, &expr_value) { Some(true) => Some(vec!()), Some(false) => None, None => { - cx.tcx.sess.span_err(*pat_span, "mismatched types between arms"); + cx.tcx.sess.span_err(pat_span, "mismatched types between arms"); None } } @@ -646,41 +737,42 @@ fn specialize(cx: &MatchCheckCtxt, r: &[Gc], &PatRange(ref from, ref to) => { let from_value = eval_const_expr(cx.tcx, &**from); let to_value = eval_const_expr(cx.tcx, &**to); - match range_covered_by_constructor(ctor_id, &from_value, &to_value) { + match range_covered_by_constructor(constructor, &from_value, &to_value) { Some(true) => Some(vec!()), Some(false) => None, None => { - cx.tcx.sess.span_err(*pat_span, "mismatched types between arms"); + cx.tcx.sess.span_err(pat_span, "mismatched types between arms"); None } } } &PatVec(ref before, ref slice, ref after) => { - match *ctor_id { - vec(_) => { - let num_elements = before.len() + after.len(); - if num_elements < arity && slice.is_some() { - let mut result = Vec::new(); - result.push_all(before.as_slice()); - result.grow_fn(arity - num_elements, |_| wild()); - result.push_all(after.as_slice()); - Some(result) - } else if num_elements == arity { - let mut result = Vec::new(); - result.push_all(before.as_slice()); - result.push_all(after.as_slice()); - Some(result) - } else { - None - } - } + match *constructor { + // Fixed-length vectors. + Single => { + let mut pats = before.clone(); + pats.grow_fn(arity - before.len() - after.len(), |_| wild()); + pats.push_all(after.as_slice()); + Some(pats) + }, + Slice(length) if before.len() + after.len() <= length && slice.is_some() => { + let mut pats = before.clone(); + pats.grow_fn(arity - before.len() - after.len(), |_| wild()); + pats.push_all(after.as_slice()); + Some(pats) + }, + Slice(length) if before.len() + after.len() == length => { + let mut pats = before.clone(); + pats.push_all(after.as_slice()); + Some(pats) + }, _ => None } } &PatMac(_) => { - cx.tcx.sess.span_err(*pat_span, "unexpanded macro"); + cx.tcx.sess.span_err(pat_span, "unexpanded macro"); None } }; @@ -740,7 +832,7 @@ fn check_fn(cx: &mut MatchCheckCtxt, } fn is_refutable(cx: &MatchCheckCtxt, pat: Gc) -> Option> { - let pats = vec!(vec!(pat)); + let pats = Matrix(vec!(vec!(pat))); is_useful(cx, &pats, [wild()], ConstructWitness) .useful() .map(|pats| { diff --git a/src/librustc/middle/pat_util.rs b/src/librustc/middle/pat_util.rs index 44ed0192d1d..24d97f5aac3 100644 --- a/src/librustc/middle/pat_util.rs +++ b/src/librustc/middle/pat_util.rs @@ -12,9 +12,10 @@ use middle::resolve; use std::collections::HashMap; +use std::gc::{Gc, GC}; use syntax::ast::*; use syntax::ast_util::{path_to_ident, walk_pat}; -use syntax::codemap::Span; +use syntax::codemap::{Span, DUMMY_SP}; pub type PatIdMap = HashMap; @@ -111,3 +112,7 @@ pub fn simple_identifier<'a>(pat: &'a Pat) -> Option<&'a Path> { } } } + +pub fn wild() -> Gc { + box (GC) Pat { id: 0, node: PatWild, span: DUMMY_SP } +} diff --git a/src/test/compile-fail/non-exhaustive-match-nested.rs b/src/test/compile-fail/non-exhaustive-match-nested.rs index 483168bb8bc..439c82a6df0 100644 --- a/src/test/compile-fail/non-exhaustive-match-nested.rs +++ b/src/test/compile-fail/non-exhaustive-match-nested.rs @@ -1,4 +1,4 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -11,10 +11,19 @@ enum t { a(u), b } enum u { c, d } +fn match_nested_vecs<'a, T>(l1: Option<&'a [T]>, l2: Result<&'a [T], ()>) -> &'static str { + match (l1, l2) { //~ ERROR non-exhaustive patterns: `(Some([]), Err(_))` not covered + (Some([]), Ok([])) => "Some(empty), Ok(empty)", + (Some([_, ..]), Ok(_)) | (Some([_, ..]), Err(())) => "Some(non-empty), any", + (None, Ok([])) | (None, Err(())) | (None, Ok([_])) => "None, Ok(less than one element)", + (None, Ok([_, _, ..])) => "None, Ok(at least two elements)" + } +} + fn main() { - let x = a(c); - match x { //~ ERROR non-exhaustive patterns: `a(c)` not covered - a(d) => { fail!("hello"); } - b => { fail!("goodbye"); } + let x = a(c); + match x { //~ ERROR non-exhaustive patterns: `a(c)` not covered + a(d) => { fail!("hello"); } + b => { fail!("goodbye"); } } } diff --git a/src/test/run-pass/issue-15104.rs b/src/test/run-pass/issue-15104.rs new file mode 100644 index 00000000000..d2711339ccb --- /dev/null +++ b/src/test/run-pass/issue-15104.rs @@ -0,0 +1,21 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + assert_eq!(count_members(&[1, 2, 3, 4]), 4); +} + +fn count_members(v: &[uint]) -> uint { + match v { + [] => 0, + [_] => 1, + [_x, ..xs] => 1 + count_members(xs) + } +} diff --git a/src/test/run-pass/match-vec-alternatives.rs b/src/test/run-pass/match-vec-alternatives.rs new file mode 100644 index 00000000000..ffbc4e85cb6 --- /dev/null +++ b/src/test/run-pass/match-vec-alternatives.rs @@ -0,0 +1,82 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn match_vecs<'a, T>(l1: &'a [T], l2: &'a [T]) -> &'static str { + match (l1, l2) { + ([], []) => "both empty", + ([], [..]) | ([..], []) => "one empty", + ([..], [..]) => "both non-empty" + } +} + +fn match_vecs_cons<'a, T>(l1: &'a [T], l2: &'a [T]) -> &'static str { + match (l1, l2) { + ([], []) => "both empty", + ([], [_, ..]) | ([_, ..], []) => "one empty", + ([_, ..], [_, ..]) => "both non-empty" + } +} + +fn match_vecs_snoc<'a, T>(l1: &'a [T], l2: &'a [T]) -> &'static str { + match (l1, l2) { + ([], []) => "both empty", + ([], [.., _]) | ([.., _], []) => "one empty", + ([.., _], [.., _]) => "both non-empty" + } +} + +fn match_nested_vecs_cons<'a, T>(l1: Option<&'a [T]>, l2: Result<&'a [T], ()>) -> &'static str { + match (l1, l2) { + (Some([]), Ok([])) => "Some(empty), Ok(empty)", + (Some([_, ..]), Ok(_)) | (Some([_, ..]), Err(())) => "Some(non-empty), any", + (None, Ok([])) | (None, Err(())) | (None, Ok([_])) => "None, Ok(less than one element)", + (None, Ok([_, _, ..])) => "None, Ok(at least two elements)", + _ => "other" + } +} + +fn match_nested_vecs_snoc<'a, T>(l1: Option<&'a [T]>, l2: Result<&'a [T], ()>) -> &'static str { + match (l1, l2) { + (Some([]), Ok([])) => "Some(empty), Ok(empty)", + (Some([.., _]), Ok(_)) | (Some([.., _]), Err(())) => "Some(non-empty), any", + (None, Ok([])) | (None, Err(())) | (None, Ok([_])) => "None, Ok(less than one element)", + (None, Ok([.., _, _])) => "None, Ok(at least two elements)", + _ => "other" + } +} + +fn main() { + assert_eq!(match_vecs(&[1i, 2], &[2i, 3]), "both non-empty"); + assert_eq!(match_vecs(&[], &[1i, 2, 3, 4]), "one empty"); + assert_eq!(match_vecs::(&[], &[]), "both empty"); + assert_eq!(match_vecs(&[1i, 2, 3], &[]), "one empty"); + + assert_eq!(match_vecs_cons(&[1i, 2], &[2i, 3]), "both non-empty"); + assert_eq!(match_vecs_cons(&[], &[1i, 2, 3, 4]), "one empty"); + assert_eq!(match_vecs_cons::(&[], &[]), "both empty"); + assert_eq!(match_vecs_cons(&[1i, 2, 3], &[]), "one empty"); + + assert_eq!(match_vecs_snoc(&[1i, 2], &[2i, 3]), "both non-empty"); + assert_eq!(match_vecs_snoc(&[], &[1i, 2, 3, 4]), "one empty"); + assert_eq!(match_vecs_snoc::(&[], &[]), "both empty"); + assert_eq!(match_vecs_snoc(&[1i, 2, 3], &[]), "one empty"); + + assert_eq!(match_nested_vecs_cons(None, Ok(&[4u, 2u])), "None, Ok(at least two elements)"); + assert_eq!(match_nested_vecs_cons::(None, Err(())), "None, Ok(less than one element)"); + assert_eq!(match_nested_vecs_cons::(Some(&[]), Ok(&[])), "Some(empty), Ok(empty)"); + assert_eq!(match_nested_vecs_cons(Some(&[1i]), Err(())), "Some(non-empty), any"); + assert_eq!(match_nested_vecs_cons(Some(&[(42i, ())]), Ok(&[(1i, ())])), "Some(non-empty), any"); + + assert_eq!(match_nested_vecs_snoc(None, Ok(&[4u, 2u])), "None, Ok(at least two elements)"); + assert_eq!(match_nested_vecs_snoc::(None, Err(())), "None, Ok(less than one element)"); + assert_eq!(match_nested_vecs_snoc::(Some(&[]), Ok(&[])), "Some(empty), Ok(empty)"); + assert_eq!(match_nested_vecs_snoc(Some(&[1i]), Err(())), "Some(non-empty), any"); + assert_eq!(match_nested_vecs_snoc(Some(&[(42i, ())]), Ok(&[(1i, ())])), "Some(non-empty), any"); +}