Skip to content

Commit 25d0ef3

Browse files
committed
Improve diagnostic messages for range patterns.
1 parent 448ce12 commit 25d0ef3

File tree

6 files changed

+147
-71
lines changed

6 files changed

+147
-71
lines changed

src/compiletest/runtest.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use std::fmt;
2424
use std::fs::{self, File};
2525
use std::io::BufReader;
2626
use std::io::prelude::*;
27-
use std::iter::repeat;
2827
use std::net::TcpStream;
2928
use std::path::{Path, PathBuf};
3029
use std::process::{Command, Output, ExitStatus};
@@ -928,12 +927,12 @@ fn check_forbid_output(props: &TestProps,
928927
}
929928
}
930929

931-
fn check_expected_errors(expected_errors: Vec<errors::ExpectedError> ,
930+
fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
932931
testfile: &Path,
933932
proc_res: &ProcRes) {
934933

935934
// true if we found the error in question
936-
let mut found_flags: Vec<_> = repeat(false).take(expected_errors.len()).collect();
935+
let mut found_flags = vec![false; expected_errors.len()];
937936

938937
if proc_res.status.success() {
939938
fatal("process did not return an error status");
@@ -954,14 +953,10 @@ fn check_expected_errors(expected_errors: Vec<errors::ExpectedError> ,
954953
}
955954
}
956955

957-
// A multi-line error will have followup lines which will always
958-
// start with one of these strings.
956+
// A multi-line error will have followup lines which start with a space
957+
// or open paren.
959958
fn continuation( line: &str) -> bool {
960-
line.starts_with(" expected") ||
961-
line.starts_with(" found") ||
962-
// 1234
963-
// Should have 4 spaces: see issue 18946
964-
line.starts_with("(")
959+
line.starts_with(" ") || line.starts_with("(")
965960
}
966961

967962
// Scan and extract our error/warning messages,

src/librustc_typeck/check/_match.rs

+56-33
Original file line numberDiff line numberDiff line change
@@ -83,41 +83,64 @@ pub fn check_pat<'a, 'tcx>(pcx: &pat_ctxt<'a, 'tcx>,
8383
demand::suptype(fcx, pat.span, expected, pat_ty);
8484
}
8585
ast::PatRange(ref begin, ref end) => {
86-
check_expr(fcx, &**begin);
87-
check_expr(fcx, &**end);
88-
89-
let lhs_ty = fcx.expr_ty(&**begin);
90-
let rhs_ty = fcx.expr_ty(&**end);
91-
92-
let lhs_eq_rhs =
93-
require_same_types(
94-
tcx, Some(fcx.infcx()), false, pat.span, lhs_ty, rhs_ty,
95-
|| "mismatched types in range".to_string());
96-
97-
let numeric_or_char =
98-
lhs_eq_rhs && (ty::type_is_numeric(lhs_ty) || ty::type_is_char(lhs_ty));
99-
100-
if numeric_or_char {
101-
match const_eval::compare_lit_exprs(tcx, &**begin, &**end, Some(lhs_ty),
102-
|id| {fcx.item_substs()[&id].substs
103-
.clone()}) {
104-
Some(Ordering::Less) |
105-
Some(Ordering::Equal) => {}
106-
Some(Ordering::Greater) => {
107-
span_err!(tcx.sess, begin.span, E0030,
108-
"lower range bound must be less than upper");
109-
}
110-
None => {
111-
span_err!(tcx.sess, begin.span, E0031,
112-
"mismatched types in range");
113-
}
114-
}
115-
} else {
116-
span_err!(tcx.sess, begin.span, E0029,
117-
"only char and numeric types are allowed in range");
86+
check_expr(fcx, begin);
87+
check_expr(fcx, end);
88+
89+
let lhs_ty = fcx.expr_ty(begin);
90+
let rhs_ty = fcx.expr_ty(end);
91+
92+
// Check that both end-points are of numeric or char type.
93+
let numeric_or_char = |t| ty::type_is_numeric(t) || ty::type_is_char(t);
94+
let lhs_compat = numeric_or_char(lhs_ty);
95+
let rhs_compat = numeric_or_char(rhs_ty);
96+
97+
if !lhs_compat || !rhs_compat {
98+
let span = if !lhs_compat && !rhs_compat {
99+
pat.span
100+
} else if !lhs_compat {
101+
begin.span
102+
} else {
103+
end.span
104+
};
105+
106+
// Note: spacing here is intentional, we want a space before "start" and "end".
107+
span_err!(tcx.sess, span, E0029,
108+
"only char and numeric types are allowed in range patterns\n \
109+
start type: {}\n end type: {}",
110+
fcx.infcx().ty_to_string(lhs_ty),
111+
fcx.infcx().ty_to_string(rhs_ty)
112+
);
113+
return;
114+
}
115+
116+
// Check that the types of the end-points can be unified.
117+
let types_unify = require_same_types(
118+
tcx, Some(fcx.infcx()), false, pat.span, rhs_ty, lhs_ty,
119+
|| "mismatched types in range".to_string()
120+
);
121+
122+
// It's ok to return without a message as `require_same_types` prints an error.
123+
if !types_unify {
124+
return;
118125
}
119126

120-
fcx.write_ty(pat.id, lhs_ty);
127+
// Now that we know the types can be unified we find the unified type and use
128+
// it to type the entire expression.
129+
let common_type = fcx.infcx().resolve_type_vars_if_possible(&lhs_ty);
130+
131+
fcx.write_ty(pat.id, common_type);
132+
133+
// Finally we evaluate the constants and check that the range is non-empty.
134+
let get_substs = |id| fcx.item_substs()[&id].substs.clone();
135+
match const_eval::compare_lit_exprs(tcx, begin, end, Some(&common_type), get_substs) {
136+
Some(Ordering::Less) |
137+
Some(Ordering::Equal) => {}
138+
Some(Ordering::Greater) => {
139+
span_err!(tcx.sess, begin.span, E0030,
140+
"lower range bound must be less than or equal to upper");
141+
}
142+
None => tcx.sess.span_bug(begin.span, "literals of different types in range pat")
143+
}
121144

122145
// subtyping doesn't matter here, as the value is some kind of scalar
123146
demand::eqtype(fcx, pat.span, expected, lhs_ty);

src/librustc_typeck/diagnostics.rs

+41-3
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,47 @@ match d {
146146
```
147147
"##,
148148

149+
E0029: r##"
150+
In a match expression, only numbers and characters can be matched against a
151+
range. This is because the compiler checks that the range is non-empty at
152+
compile-time, and is unable to evaluate arbitrary comparison functions. If you
153+
want to capture values of an orderable type between two end-points, you can use
154+
a guard.
155+
156+
```
157+
// The ordering relation for strings can't be evaluated at compile time,
158+
// so this doesn't work:
159+
match string {
160+
"hello" ... "world" => ...
161+
_ => ...
162+
}
163+
164+
// This is a more general version, using a guard:
165+
match string {
166+
s if s >= "hello" && s <= "world" => ...
167+
_ => ...
168+
}
169+
```
170+
"##,
171+
172+
E0030: r##"
173+
When matching against a range, the compiler verifies that the range is
174+
non-empty. Range patterns include both end-points, so this is equivalent to
175+
requiring the start of the range to be less than or equal to the end of the
176+
range.
177+
178+
For example:
179+
180+
```
181+
match 5u32 {
182+
// This range is ok, albeit pointless.
183+
1 ... 1 => ...
184+
// This range is empty, and the compiler can tell.
185+
1000 ... 5 => ...
186+
}
187+
```
188+
"##,
189+
149190
E0033: r##"
150191
This error indicates that a pointer to a trait type cannot be implicitly
151192
dereferenced by a pattern. Every trait defines a type, but because the
@@ -1107,9 +1148,6 @@ For more information see the [opt-in builtin traits RFC](https://github.com/rust
11071148
}
11081149

11091150
register_diagnostics! {
1110-
E0029,
1111-
E0030,
1112-
E0031,
11131151
E0034, // multiple applicable methods in scope
11141152
E0035, // does not take type parameters
11151153
E0036, // incorrect number of type parameters given for this method

src/test/compile-fail/match-ill-type1.rs

-16
This file was deleted.

src/test/compile-fail/match-range-fail.rs

+19-9
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,32 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
//error-pattern: lower range bound
12-
//error-pattern: only char and numeric types
13-
//error-pattern: mismatched types
14-
1511
fn main() {
1612
match 5 {
17-
6 ... 1 => { }
18-
_ => { }
13+
6 ... 1 => { }
14+
_ => { }
15+
};
16+
//~^^^ ERROR lower range bound must be less than or equal to upper
17+
18+
match "wow" {
19+
"bar" ... "foo" => { }
1920
};
21+
//~^^ ERROR only char and numeric types are allowed in range
22+
//~| start type: &'static str
23+
//~| end type: &'static str
2024

2125
match "wow" {
22-
"bar" ... "foo" => { }
26+
10 ... "what" => ()
2327
};
28+
//~^^ ERROR only char and numeric types are allowed in range
29+
//~| start type: _
30+
//~| end type: &'static str
2431

2532
match 5 {
26-
'c' ... 100 => { }
27-
_ => { }
33+
'c' ... 100 => { }
34+
_ => { }
2835
};
36+
//~^^^ ERROR mismatched types in range
37+
//~| expected char
38+
//~| found integral variable
2939
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Test that type inference for range patterns works correctly (is bi-directional).
12+
13+
pub fn main() {
14+
match 1 {
15+
1 ... 3 => {}
16+
_ => panic!("should match range")
17+
}
18+
match 1 {
19+
1 ... 3u16 => {}
20+
_ => panic!("should match range with inferred start type")
21+
}
22+
match 1 {
23+
1u16 ... 3 => {}
24+
_ => panic!("should match range with inferred end type")
25+
}
26+
}

0 commit comments

Comments
 (0)