]> git.lizzy.rs Git - rust.git/commitdiff
Improve diagnostic messages for range patterns.
authorMichael Sproul <micsproul@gmail.com>
Sun, 24 May 2015 04:29:12 +0000 (14:29 +1000)
committerMichael Sproul <micsproul@gmail.com>
Wed, 3 Jun 2015 06:15:15 +0000 (16:15 +1000)
src/compiletest/runtest.rs
src/librustc_typeck/check/_match.rs
src/librustc_typeck/diagnostics.rs
src/test/compile-fail/match-ill-type1.rs [deleted file]
src/test/compile-fail/match-range-fail.rs
src/test/run-pass/match-range-infer.rs [new file with mode: 0644]

index fef36ec2c2f9942b58954987ee7690842090798d..87b6f8f0df6b34ab604428d33124fa23b8d50366 100644 (file)
@@ -24,7 +24,6 @@
 use std::fs::{self, File};
 use std::io::BufReader;
 use std::io::prelude::*;
-use std::iter::repeat;
 use std::net::TcpStream;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Output, ExitStatus};
@@ -928,12 +927,12 @@ fn check_forbid_output(props: &TestProps,
     }
 }
 
-fn check_expected_errors(expected_errors: Vec<errors::ExpectedError> ,
+fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
                          testfile: &Path,
                          proc_res: &ProcRes) {
 
     // true if we found the error in question
-    let mut found_flags: Vec<_> = repeat(false).take(expected_errors.len()).collect();
+    let mut found_flags = vec![false; expected_errors.len()];
 
     if proc_res.status.success() {
         fatal("process did not return an error status");
@@ -954,14 +953,10 @@ fn prefix_matches(line: &str, prefix: &str) -> bool {
         }
     }
 
-    // A multi-line error will have followup lines which will always
-    // start with one of these strings.
+    // A multi-line error will have followup lines which start with a space
+    // or open paren.
     fn continuation( line: &str) -> bool {
-        line.starts_with(" expected") ||
-        line.starts_with("    found") ||
-        //                1234
-        // Should have 4 spaces: see issue 18946
-        line.starts_with("(")
+        line.starts_with(" ") || line.starts_with("(")
     }
 
     // Scan and extract our error/warning messages,
index 63470604084f81a912d35bc69ed03c5ea5e18e1e..0c756cf50083fe1f7ef3f0a7f08ac0e478c5a54e 100644 (file)
@@ -83,41 +83,64 @@ pub fn check_pat<'a, 'tcx>(pcx: &pat_ctxt<'a, 'tcx>,
             demand::suptype(fcx, pat.span, expected, pat_ty);
         }
         ast::PatRange(ref begin, ref end) => {
-            check_expr(fcx, &**begin);
-            check_expr(fcx, &**end);
-
-            let lhs_ty = fcx.expr_ty(&**begin);
-            let rhs_ty = fcx.expr_ty(&**end);
-
-            let lhs_eq_rhs =
-                require_same_types(
-                    tcx, Some(fcx.infcx()), false, pat.span, lhs_ty, rhs_ty,
-                    || "mismatched types in range".to_string());
-
-            let numeric_or_char =
-                lhs_eq_rhs && (ty::type_is_numeric(lhs_ty) || ty::type_is_char(lhs_ty));
-
-            if numeric_or_char {
-                match const_eval::compare_lit_exprs(tcx, &**begin, &**end, Some(lhs_ty),
-                                                    |id| {fcx.item_substs()[&id].substs
-                                                             .clone()}) {
-                    Some(Ordering::Less) |
-                    Some(Ordering::Equal) => {}
-                    Some(Ordering::Greater) => {
-                        span_err!(tcx.sess, begin.span, E0030,
-                            "lower range bound must be less than upper");
-                    }
-                    None => {
-                        span_err!(tcx.sess, begin.span, E0031,
-                            "mismatched types in range");
-                    }
-                }
-            } else {
-                span_err!(tcx.sess, begin.span, E0029,
-                          "only char and numeric types are allowed in range");
+            check_expr(fcx, begin);
+            check_expr(fcx, end);
+
+            let lhs_ty = fcx.expr_ty(begin);
+            let rhs_ty = fcx.expr_ty(end);
+
+            // Check that both end-points are of numeric or char type.
+            let numeric_or_char = |t| ty::type_is_numeric(t) || ty::type_is_char(t);
+            let lhs_compat = numeric_or_char(lhs_ty);
+            let rhs_compat = numeric_or_char(rhs_ty);
+
+            if !lhs_compat || !rhs_compat {
+                let span = if !lhs_compat && !rhs_compat {
+                    pat.span
+                } else if !lhs_compat {
+                    begin.span
+                } else {
+                    end.span
+                };
+
+                // Note: spacing here is intentional, we want a space before "start" and "end".
+                span_err!(tcx.sess, span, E0029,
+                          "only char and numeric types are allowed in range patterns\n \
+                           start type: {}\n end type: {}",
+                          fcx.infcx().ty_to_string(lhs_ty),
+                          fcx.infcx().ty_to_string(rhs_ty)
+                );
+                return;
+            }
+
+            // Check that the types of the end-points can be unified.
+            let types_unify = require_same_types(
+                    tcx, Some(fcx.infcx()), false, pat.span, rhs_ty, lhs_ty,
+                    || "mismatched types in range".to_string()
+            );
+
+            // It's ok to return without a message as `require_same_types` prints an error.
+            if !types_unify {
+                return;
             }
 
-            fcx.write_ty(pat.id, lhs_ty);
+            // Now that we know the types can be unified we find the unified type and use
+            // it to type the entire expression.
+            let common_type = fcx.infcx().resolve_type_vars_if_possible(&lhs_ty);
+
+            fcx.write_ty(pat.id, common_type);
+
+            // Finally we evaluate the constants and check that the range is non-empty.
+            let get_substs = |id| fcx.item_substs()[&id].substs.clone();
+            match const_eval::compare_lit_exprs(tcx, begin, end, Some(&common_type), get_substs) {
+                Some(Ordering::Less) |
+                Some(Ordering::Equal) => {}
+                Some(Ordering::Greater) => {
+                    span_err!(tcx.sess, begin.span, E0030,
+                        "lower range bound must be less than or equal to upper");
+                }
+                None => tcx.sess.span_bug(begin.span, "literals of different types in range pat")
+            }
 
             // subtyping doesn't matter here, as the value is some kind of scalar
             demand::eqtype(fcx, pat.span, expected, lhs_ty);
index edfad77d588df1d40a394cce1cb5b98c243e3368..5e5e972f1b7b25bd3584cb9e823742743b2ee7e5 100644 (file)
@@ -146,6 +146,47 @@ struct Dog {
 ```
 "##,
 
+E0029: r##"
+In a match expression, only numbers and characters can be matched against a
+range. This is because the compiler checks that the range is non-empty at
+compile-time, and is unable to evaluate arbitrary comparison functions. If you
+want to capture values of an orderable type between two end-points, you can use
+a guard.
+
+```
+// The ordering relation for strings can't be evaluated at compile time,
+// so this doesn't work:
+match string {
+    "hello" ... "world" => ...
+    _ => ...
+}
+
+// This is a more general version, using a guard:
+match string {
+    s if s >= "hello" && s <= "world" => ...
+    _ => ...
+}
+```
+"##,
+
+E0030: r##"
+When matching against a range, the compiler verifies that the range is
+non-empty.  Range patterns include both end-points, so this is equivalent to
+requiring the start of the range to be less than or equal to the end of the
+range.
+
+For example:
+
+```
+match 5u32 {
+    // This range is ok, albeit pointless.
+    1 ... 1 => ...
+    // This range is empty, and the compiler can tell.
+    1000 ... 5 => ...
+}
+```
+"##,
+
 E0033: r##"
 This error indicates that a pointer to a trait type cannot be implicitly
 dereferenced by a pattern. Every trait defines a type, but because the
@@ -1107,9 +1148,6 @@ impl Baz for Bar { } // Note: This is OK
 }
 
 register_diagnostics! {
-    E0029,
-    E0030,
-    E0031,
     E0034, // multiple applicable methods in scope
     E0035, // does not take type parameters
     E0036, // incorrect number of type parameters given for this method
diff --git a/src/test/compile-fail/match-ill-type1.rs b/src/test/compile-fail/match-ill-type1.rs
deleted file mode 100644 (file)
index c60ef2e..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-fn main() {
-    match 1 {
-        1...2_usize => 1, //~ ERROR mismatched types in range
-        _ => 2,
-    };
-}
index c3292adfa34139cfab55adc0c204c288f7ef2d07..234b74f76d1e05eb39916d06434236140aca929e 100644 (file)
@@ -8,22 +8,32 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-//error-pattern: lower range bound
-//error-pattern: only char and numeric types
-//error-pattern: mismatched types
-
 fn main() {
     match 5 {
-      6 ... 1 => { }
-      _ => { }
+        6 ... 1 => { }
+        _ => { }
+    };
+    //~^^^ ERROR lower range bound must be less than or equal to upper
+
+    match "wow" {
+        "bar" ... "foo" => { }
     };
+    //~^^ ERROR only char and numeric types are allowed in range
+    //~| start type: &'static str
+    //~| end type: &'static str
 
     match "wow" {
-      "bar" ... "foo" => { }
+        10 ... "what" => ()
     };
+    //~^^ ERROR only char and numeric types are allowed in range
+    //~| start type: _
+    //~| end type: &'static str
 
     match 5 {
-      'c' ... 100 => { }
-      _ => { }
+        'c' ... 100 => { }
+        _ => { }
     };
+    //~^^^ ERROR mismatched types in range
+    //~| expected char
+    //~| found integral variable
 }
diff --git a/src/test/run-pass/match-range-infer.rs b/src/test/run-pass/match-range-infer.rs
new file mode 100644 (file)
index 0000000..74f513e
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright 2015 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that type inference for range patterns works correctly (is bi-directional).
+
+pub fn main() {
+    match 1 {
+        1 ... 3 => {}
+        _ => panic!("should match range")
+    }
+    match 1 {
+        1 ... 3u16 => {}
+        _ => panic!("should match range with inferred start type")
+    }
+    match 1 {
+        1u16 ... 3 => {}
+        _ => panic!("should match range with inferred end type")
+    }
+}